From b57dde157207da80e76322520ebd3795e3a194a8 Mon Sep 17 00:00:00 2001 From: Ming Qiu Date: Thu, 23 Nov 2023 08:02:04 +0000 Subject: [PATCH] Allow sparse option for Kopia & Restic restore Signed-off-by: Ming Qiu --- changelogs/unreleased/7141-qiuming-best | 1 + config/crd/v1/bases/velero.io_backups.yaml | 17 ++-- .../v1/bases/velero.io_podvolumebackups.yaml | 12 ++- .../v1/bases/velero.io_podvolumerestores.yaml | 6 ++ config/crd/v1/bases/velero.io_restores.yaml | 8 ++ config/crd/v1/bases/velero.io_schedules.yaml | 18 ++-- config/crd/v1/crds/crds.go | 10 +-- .../v2alpha1/bases/velero.io_datauploads.yaml | 8 -- config/crd/v2alpha1/crds/crds.go | 2 +- design/velero-uploader-configuration.md | 41 +++++++++- pkg/apis/velero/shared/uploader_config.go | 23 ------ pkg/apis/velero/v1/backup_types.go | 14 ++-- pkg/apis/velero/v1/pod_volume_backup_types.go | 6 +- pkg/apis/velero/v1/pod_volume_restore_type.go | 5 ++ pkg/apis/velero/v1/restore_types.go | 11 +++ pkg/apis/velero/v1/zz_generated.deepcopy.go | 58 ++++++++++++- pkg/apis/velero/v2alpha1/data_upload_types.go | 3 - .../velero/v2alpha1/zz_generated.deepcopy.go | 1 - pkg/builder/backup_builder.go | 5 +- pkg/builder/restore_builder.go | 6 ++ pkg/cmd/cli/nodeagent/server.go | 1 + pkg/cmd/cli/restore/create.go | 8 ++ pkg/cmd/cli/restore/create_test.go | 5 +- pkg/cmd/const.go | 1 + pkg/cmd/util/output/backup_describer.go | 4 +- pkg/cmd/util/output/restore_describer.go | 5 ++ pkg/cmd/util/output/schedule_describer.go | 2 +- pkg/controller/data_download_controller.go | 2 +- pkg/controller/data_upload_controller.go | 2 +- pkg/controller/data_upload_controller_test.go | 5 +- .../pod_volume_backup_controller.go | 2 +- .../pod_volume_backup_controller_test.go | 5 +- .../pod_volume_restore_controller.go | 2 +- pkg/datapath/file_system.go | 9 +- pkg/datapath/file_system_test.go | 7 +- pkg/datapath/mocks/types.go | 22 +++-- pkg/datapath/types.go | 5 +- pkg/podvolume/backupper.go | 22 ++++- pkg/podvolume/restorer.go | 23 +++++- pkg/uploader/kopia/snapshot.go | 38 +++++++-- pkg/uploader/kopia/snapshot_test.go | 11 ++- pkg/uploader/provider/kopia.go | 6 +- pkg/uploader/provider/kopia_test.go | 23 +++--- pkg/uploader/provider/mocks/Provider.go | 24 +++--- pkg/uploader/provider/provider.go | 4 +- pkg/uploader/provider/restic.go | 47 +++++++++-- pkg/uploader/provider/restic_test.go | 50 +++++++++-- pkg/util/uploaderconfig/uploaderconfig.go | 82 +++++++++++++++++++ 48 files changed, 494 insertions(+), 178 deletions(-) create mode 100644 changelogs/unreleased/7141-qiuming-best delete mode 100644 pkg/apis/velero/shared/uploader_config.go create mode 100644 pkg/util/uploaderconfig/uploaderconfig.go diff --git a/changelogs/unreleased/7141-qiuming-best b/changelogs/unreleased/7141-qiuming-best new file mode 100644 index 000000000..cf7fc0dda --- /dev/null +++ b/changelogs/unreleased/7141-qiuming-best @@ -0,0 +1 @@ +Allow sparse option for Kopia & Restic restore diff --git a/config/crd/v1/bases/velero.io_backups.yaml b/config/crd/v1/bases/velero.io_backups.yaml index 9099eb13f..84769dcba 100644 --- a/config/crd/v1/bases/velero.io_backups.yaml +++ b/config/crd/v1/bases/velero.io_backups.yaml @@ -35,6 +35,14 @@ spec: spec: description: BackupSpec defines the specification for a Velero backup. properties: + backupConfig: + description: BackupConfig defines the configuration for the backup. + properties: + parallelFilesUpload: + description: ParallelFilesUpload is the number of files parallel + uploads to perform when using the uploader. + type: integer + type: object csiSnapshotTimeout: description: CSISnapshotTimeout specifies the time used to wait for CSI VolumeSnapshot status turns to ReadyToUse during creation, before @@ -477,15 +485,6 @@ spec: description: TTL is a time.Duration-parseable string describing how long the Backup should be retained for. type: string - uploaderConfig: - description: UploaderConfig specifies the configuration for the uploader. - nullable: true - properties: - parallelFilesUpload: - description: ParallelFilesUpload is the number of files parallel - uploads to perform when using the uploader. - type: integer - type: object volumeSnapshotLocations: description: VolumeSnapshotLocations is a list containing names of VolumeSnapshotLocations associated with this backup. diff --git a/config/crd/v1/bases/velero.io_podvolumebackups.yaml b/config/crd/v1/bases/velero.io_podvolumebackups.yaml index a642f3949..6279a0c17 100644 --- a/config/crd/v1/bases/velero.io_podvolumebackups.yaml +++ b/config/crd/v1/bases/velero.io_podvolumebackups.yaml @@ -121,13 +121,11 @@ spec: description: Tags are a map of key-value pairs that should be applied to the volume backup as tags. type: object - uploaderConfig: - description: UploaderConfig specifies the configuration for the uploader. - properties: - parallelFilesUpload: - description: ParallelFilesUpload is the number of files parallel - uploads to perform when using the uploader. - type: integer + uploaderSettings: + additionalProperties: + type: string + description: UploaderSettings are a map of key-value pairs that should + be applied to the uploader configuration. type: object uploaderType: description: UploaderType is the type of the uploader to handle the diff --git a/config/crd/v1/bases/velero.io_podvolumerestores.yaml b/config/crd/v1/bases/velero.io_podvolumerestores.yaml index 6ea3eca97..afb9d1025 100644 --- a/config/crd/v1/bases/velero.io_podvolumerestores.yaml +++ b/config/crd/v1/bases/velero.io_podvolumerestores.yaml @@ -119,6 +119,12 @@ spec: description: SourceNamespace is the original namespace for namaspace mapping. type: string + uploaderSettings: + additionalProperties: + type: string + description: UploaderSettings are a map of key-value pairs that should + be applied to the uploader configuration. + type: object uploaderType: description: UploaderType is the type of the uploader to handle the data transfer. diff --git a/config/crd/v1/bases/velero.io_restores.yaml b/config/crd/v1/bases/velero.io_restores.yaml index 81b71ed35..3802c8aa6 100644 --- a/config/crd/v1/bases/velero.io_restores.yaml +++ b/config/crd/v1/bases/velero.io_restores.yaml @@ -387,6 +387,14 @@ spec: - name type: object x-kubernetes-map-type: atomic + restoreConfig: + description: RestoreConfig specifies the configuration for the restore. + properties: + writeSparseFiles: + description: WriteSparseFiles is a flag to indicate whether write + files sparsely or not. + type: boolean + type: object restorePVs: description: RestorePVs specifies whether to restore all included PVs from snapshot diff --git a/config/crd/v1/bases/velero.io_schedules.yaml b/config/crd/v1/bases/velero.io_schedules.yaml index 0cf3cbe52..4d2f57cc4 100644 --- a/config/crd/v1/bases/velero.io_schedules.yaml +++ b/config/crd/v1/bases/velero.io_schedules.yaml @@ -65,6 +65,14 @@ spec: description: Template is the definition of the Backup to be run on the provided schedule properties: + backupConfig: + description: BackupConfig defines the configuration for the backup. + properties: + parallelFilesUpload: + description: ParallelFilesUpload is the number of files parallel + uploads to perform when using the uploader. + type: integer + type: object csiSnapshotTimeout: description: CSISnapshotTimeout specifies the time used to wait for CSI VolumeSnapshot status turns to ReadyToUse during creation, @@ -514,16 +522,6 @@ spec: description: TTL is a time.Duration-parseable string describing how long the Backup should be retained for. type: string - uploaderConfig: - description: UploaderConfig specifies the configuration for the - uploader. - nullable: true - properties: - parallelFilesUpload: - description: ParallelFilesUpload is the number of files parallel - uploads to perform when using the uploader. - type: integer - type: object volumeSnapshotLocations: description: VolumeSnapshotLocations is a list containing names of VolumeSnapshotLocations associated with this backup. diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index fff600dc1..1c1cc08ca 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -30,14 +30,14 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VAo\xe46\x0f\xbdϯ \xf6;\xec\xe5\xb3g\xb7\xbd\x14\xbem\xd3\x16\b\x9a\x04A\x12\xe4Nۜ\x19mdI\x95\xa8I\xa7E\xff{A\xc9\xcexl'\xb3Y\xa0\xbaY\xa2\x1e\xc9G>ZEQ\xacЩG\xf2AYS\x01:E\x7f2\x19\xf9\n\xe5\xd3O\xa1Tv\xbd\xff\xbczR\xa6\xad\xe0\"\x06\xb6\xdd\x1d\x05\x1b}C\xbf\xd0F\x19\xc5ʚUG\x8c-2V+\x004\xc62\xcav\x90O\x80\xc6\x1a\xf6Vk\xf2ŖL\xf9\x14k\xaa\xa3\xd2-\xf9\x04>\xb8\xde\x7f*?\xffP~Z\x01\x18쨂\x1a\x9b\xa7\xe8<9\x1b\x14[\xaf(\x94{\xd2\xe4m\xa9\xec*8j\x04}\xebmt\x15\x1c\x0f\xf2\xed\xdes\x8e\xfa\xe7\x04t7\x00\x1dґV\x81\x7f_<\xbeR\x81\x93\x89\xd3ѣ^\n$\x1d\ae\xb6Q\xa3\x9f\x19\x88\x83\xd0XG\x15\xdcH,\x0e\x1bjW\x00}\xa6)\xb6\x02\xb0m\x13w\xa8o\xbd2L\xfe\xc2\xea\xd8\r\x9c\x15\xf05Xs\x8b\xbc\xab\xa0\x1c\xd8-\x1bO\x89\xd8\a\xd5Q`\xec\\\xb2\x1d\b\xfb\xb2\xa5\xfe\x9b\x0f\xe2\xbcE\xa69\x980W\x1ec}88:A9\x12\x01\xa3\xb3\x8c\x18\xd8+\xb3]\x1d\x8d\xf7\x9f3\x15͎:\xacz[\xeb\xc8|\xb9\xbd|\xfc\xf1\xfed\x1b\xc0y\xebȳ\x1aʓר\xfdF\xbb\x00-\x85\xc6+ǩ9>\n`\xb6\x82V\xfa\x8e\x02\xf0\x8e\x06N\xa9\xedc\x00\xbb\x01ީ\x00\x9e\x9c\xa7@&w\xe2\t0\x88\x11\x1a\xb0\xf5Wj\xb8\x84{\xf2\x02\x03ag\xa3n\xa5]\xf7\xe4\x19<5vk\xd4_/\xd8\x01\xd8&\xa7\x1a\x99\xfa\x1e9\xaeTC\x83\x1a\xf6\xa8#\xfd\x1fд\xd0\xe1\x01<\x89\x17\x88f\x84\x97LB\t\xd7\xd6\x13(\xb3\xb1\x15\xec\x98]\xa8\xd6\xeb\xad\xe2Av\x8d\xed\xbah\x14\x1f\xd6IA\xaa\x8el}X\xb7\xb4'\xbd\x0ej[\xa0ov\x8a\xa9\xe1\xe8i\x8dN\x15)t\x93\xa4Wv\xed\xff|/\xd4\xf0\xf1$\xd6Y-\xf3Jby\xa3\x02\xa2\x16P\x01\xb0\xbf\x9a\xb38\x12-[\xc2\xceݯ\xf7\x0f0\xb8NŘ\xb2\x9fx?^\f\xc7\x12\ba\xcal\xc8\xe7\"n\xbc\xed\x12&\x99\xd6Ye8}4Z\x91\x99\xd2\x1fb\xdd)\x96\xba\xff\x11)\xb0Ԫ\x84\x8b4\x8b\xa0&\x88N\xd4Жpi\xe0\x02;\xd2\x17\x18\xe8?/\x800\x1d\n!\xf6\xdbJ0\x1e\xa3S\xe3\xcc\xda\xe8`\x18\x81\xaf\xd4k:\xd6\xee\x1d5R>aP\xae\xaa\x8dj\x926`c=\xe0̾<\x81^\x96\xae\xac<\xfc\xee\xd9z\xdcҕ͘S\xa3\xc5\xd8&w\x86\xe0d\xb2d\x19Ӳ\xe1\f\x1b\x80w\xc8#\xfd2*\xf32\x06\x16\xf3y\xa3\b\xa9\x10(r6h\x1a\xfa-u\x94i\x0egr\xba^\xb8\")\xed\xec3\xd8\r\x93\x19\x83\xf6\xb1.dR\x13\xf8h\xde\x15\xec\xe90?\x13\xe6݉1(\xd3J\x1b\xf4\xd3T\x9c\f\xd4K]ɴ\xe0O\xff\x9b\xe3E&vsw\x05ʊ\xe7V\xd8N\xa0,\xc12y\x10\xec\x1f5lM\x8c\xc4A95\xe0\x05\xa0y\x980\xa0\x04\xe5\xe4Dy\x057H\x92\x82\x9e\x89\x02;\n\xa9D\v\x1e6\xd1\x1b\xf2\xabT@\x98\xd8\xcb7\xe4hL\xa9\u07fczu`&,\x9aL\x16E%\x989\xbfB\xf9g\xbb\xcaH\xa5_\xe5p\x02\xfeJ\xb3Ú\xaa\xec\xc8\fd\x96\x91\xafh\xc9ֈ\xba\xc0\x85\xb3)\xf2\x7f\t\x02\xa0_vp5g+\x8c\xda(&\x0e\xad\x1fP\xea'8`\x17\x80\x93/\xd7\xd5͢!\xb4}e\xa9\xf3\xf9\xfd\xfdC[\xf6\x98\xeeS\x1f\xe9\xde\x12Ȇ\x05\x96`L\xecA9&\xee\x95,\x10&\x88\xdcI\x1f\x8a.g \xfa\xe4\xd7ծ`\xc6\xf2\xfd\xef\x15h+\xe4rCnQ\x93\x90\x1d\x90\xaa̭dn\xc8V\x90[Z\x00\xbf\xa5\x1a\x9e\x9d\x01\x96\xd2zm\t\x9bƂ\xb6\x12\xec7vTk\xfd\x10t\xd9\b\xbf\x9cB\xb8/!\xeb,\x18ۋ\xedY\x86˂\xec\xa5j\xf4\x85SW\x9b\x0e\xc8\xf8\x92\xb5O\xa6ٽ\xa0\xa5>J\xf3\xc0\n\x90\x95\xe9\xb7\xe8!t{\xbf\xedu\b\xc8x\xd4P\xadT\x1ar\xbbΞ(3\x16\xbd\x01Lb\x01\x91/\xa8a\x02<\xd44\x95&\xa6R\x02W\xe9g\xa0\xf9\xf9A\xfe\xa6\x81\xe4\x15\nk\xa6\x00\xa7|Cv\xb0\x97\n\"p\x15\xd8\xfe\xb61(e\t\xa3\x11%Y\x99\ry8\x82%#\xad\xb8\xf1r\xcf4y\xfd\x13)\x98\xa8\fl\x06\xd0F\x18\x8cD\xa1\x86\x16\xf2\x04j\x86^﨡\xbf\xdav=2\xd9\xfe\x04\x01ؙ\xee<\xc9vg\xfbcdV\x9e\xabd\xbboAd\x9a\xacVD*\xb2r&pu\x83\xa0\xadQ5k&ZcD >1\xceø\xcbf\xee\b\xe8x\xa7\x1f\xe4\a\xed\x84t\x8e\x10#\xddZty:\x829\x82\"\xa5\f\xc6'\x82\xf7\x9eq \xfa\xac\r\x14\x9e*A\xe5\a\"\xe2r\xe0܃Ж\xa8\x1e\xe7\xe1\t\x927\x98\xc8\xdb\x1e\xb2\xed\xa1\xbdS\x9e:\r\xef\xfa\xd4\xf1\x8d\xdbF\xb8!\x94<\xc2\xd9y,T\x10\xcb\x1cj\a\x1a\x89t\x86\xc4\xc1\xfd\f\x14\xb2G8#\x18\xbfA1\xdb;U\x14\xdc\xf3\b\xe7\x94f=\x02Z\x9c\x98\xf6\x1b/\x96\x92\xf6\x05\x12\x02\xf3\xd9\xe9\xc4#\xb8\xd9\x14t\xd1\xfc\xe4H\xba\"\tO\xa0\xfd\x05Ӭ\xd9\xd6ڨCƾԎEv\x15\x1cY\x998Qk\xe60\x95 \xf7\xf5v\xd3\x17\xcaY^\x0f\xe4\xe4~+ƽ\xe1\xee\xf3Q\x9a\xad\xb8!\xef\xbf2\xedw\xfc\xdeI\xd0\x1f\xa5\xc17\xcfBN\x87\xf8\x05\xc4t\x1dqy\t\xa7\xb6-\x1d\xda\xfbV\t\xc2\ud7ad\x8b\xf0j\xf60M\xb6\xc2\xc6-\x9e\x1e\xb8\v醛\xb6\x0fݧ\xa84nL\t)\xd6.\xf5\x12\x1b\xc9\x11;\x11\xa4T\x1d\x8e\fQ\xab\a\x1d\xc9\xf5ğ\akI\\\x7f\xb7\xaf\xcai\x06y\xd8W\xc1\xdd@j\xe0\xc02R\x80:L\x19\x8e\xf6SZ\xfd\x9e\x86B\xa2\xd6u\xcfB\tK3\xed\xe1\xf1\xaa;\x9a\xfc\xee>k\xbbr\x13Z\x05f\xcf6\x1d\xd9\x04\x1co:?#4\xb1\xe8\x7f\xccR\x97\xe69\x96iP~\xb7@\xe3/\xe0\xc5\xd0\xf6;Ĝ\x85,(nN\xfc\xb75s(\xd0\xffCJ\xcaT\xc2\x1a~\x8b\xe5\x18\x1c:}}\x16\xab=\x8c\x1d\x81ib\xf9{\xa2|\xb8\xbd\x1c\x99\x9c\xb4\xba\x05\xb83\xe4r?\xf0Xn\xc8\xd3QjgSqSd\x16$\xd3d\xf5\bg\xbf\x19\xd7\xd6\x03\xab\xadX9\x03\xbfX\xdd\xd4ނ\x14\xfcLV\xd8w\xf5-NP\xa2$&6\xfb\xba~\xac\xcbO\xd6\x05-\xd7^z\x8d,X6\xda\x0f\xcbeR]l\x1b\x83\x06\x0f\xc2v\xackD\xac{<5\xdb$\xf9-\xa5\x8e\xec|\x8f\xa0r'\xb5q\x19Ɏ;\xbb$\xfbE\x9c\xec\xf9\xac\x17\xa1{W\xa5#U\xa8\xbf\xb0겗\xa8\xb5\xdc\xd6Ӛ\xd9\xed\x19\xf8L\x9a\x03j\x03\xb2U\xb3\xf2\x9d\x1e^\xb9=\v\x1c\x84f\xe8\x94\xcc\xc2-\x95\xcc@Gw\x8b\x9b'A\xcb\xcf$\x17\xeb\xc4\"u\x81\x8f+n\x98Nf\x86'ݑ\xb5DZ\x18\x02\xbc\xff\xda\xcazZ\xa5a\xff\x9e\x13\xbe\xa5x\x11\\\xebEA\xfbU\v\xd9f\xc6J\xc6bO\x87m\xa1v\xac]\xd4VЯ\xac\xa8\nB\vK\xfa\xd4pi\xef*\xce:\x1c\xaf\xeb\xce\x10.\x9a\x11#\xed\xa2*9\x98\xd4\x15\xe9*\xcc\xec2\xd1,\x87\xda0{)\x90\x82P\xb2\xa7\x8c\x8f\x94\xbb\f\x9fE\xb4]\x12\xa3xeq\xbd\xe0#m\xf05\x92\"!\x81\x9b\xe8dNk\xebR\xa5\xbb\x8aw\n\xd2ܳ\xb9dvp\xcfJ\xc5$\xd6\xe9]\xd9C\xf3\"F\xc5\xf9\x87\x8b6x~\xb8h3\xcf\x0f\x17m\xf4\xf9\xe1\xa2\xcd??\\4\xff\xfcp\xd1\xc2\xf3\xc3E\xfb\xe1\xa2M5\x9b\xd2\xd6s\x18\xb9\xd3q#?\xceb\x91\xb0\xad=\x85\xe2\x04|_\x85\xe1\xeb\xbcS+3\xb7\xf1^\x91:\xfe\xe4\xdap\xdd2%u\xa9\xa6] A\xbc\xdda\x9f\x99\xe2\xcdo\xa8\x97\x0f\x83^T/\xbf\x9d\xec|\xa5zy\x8fa\xdf\xeb\xbeR\xb5|\x98\xff\xb2j\xf9\x1b_\xaaQ\x00\r\xe9y\xb7\x17\x9f\x8f\r\xd9\x1bm\x00\xf8\x0f\xae\xbf\x1dԇ]\xc6\xf8g\xaf\xb6\x1fa~ba\xfc\xea/\xab\xef\x8fҋi;J\xcd\x01\x99\"\x93\n\xc7|m\\\xd9.\xee\xea\x16\xd2}\x9f¹T\x1aS+\xe6\xa7\xe85\xd42-\x82}\xaf\x8b\xd9@\xf1\xa9\xf4\xb6\"\xedD\xe76\xd2e\xeeLgd>\x98\x04\xd0g\x91\x1d\x95\x14\xb2\xd2>o`\xa1\xbf\xc5\xf4\x85\xdf\n\xc52\xb0D\x05\xfb\x9a\x1ce\x15\xa9؞\xa0\xddL\xfd\xdex՞ߣ\x06CO\xaf7\xdd_\x8c\xf45|䉙c\x04ϧ#\b\xdc]\x17\x87vA~Xp\xfe\xc0y_\x90\x88TD0>f\xb0\xeaS\xf9\x1d\xd3\xf4\xa9tI\xa2Ŗ\x7f:\xc1\x91V\xe5wqm_\xb7vo\xc4\t\\\xba\x99\x9d~\x84!\xbdzo\xba\xdcnI\xcd^\xbf\"o\x14\xe8|\xa5^Jnj\xa6*\xef\x82Z\xbc\xc4:\xeco\xdezO\xa9\xb6\xbb\xa8\xc6n\xb6T9\xb1\xb2\xae[37\rrA=]\x12q\xe6k\xe7\x16W\xcc\xf9\n\xb5\xc9y$\xd7\xc9E*\xe0&\x01\x8fV\xc7Mս\xcd佇5q\xe9\xd5n\x93\xa0\xb1\x12n\xbe\xc6\xedz\x95\xec\u05c8\xb2\xc7U\xcdl\x9d\xdal\x14>\x8d\xdfl%ڒ\xfa\xb3Y\x8a]XkVג\x8d\x8c\xbb\xb4¬[A6\x024\xa5\xael\xa4nl\x04\xe2d5Yj\xb5\xd8\b\xec\x19\xb3;)%\x93?.\xa9\x12\x8b_\xa2Bf\xad!\xff\xa3\xe4\xefR2H\xd5q.\xe7\x02\x9aO\xbd\xe6\x96\xf3\xc1ǚvVc~*3\xc7\xe5\xcejQq\xc3J\x8eۋ'\x96Gcvs\x84s}1\xc4\xef\x12\x8fk\xba\xcbLȧϵ0oz.7\xd5\xe4\t8'4&\x8a\x83\x99g\xee\x1e\xa0L\xae\xc1Z\b\xbb<\xfd\x95\x17\xfe\xba\xa0\x1b'\xefx\"5\xb6\x03c\x8ePX(\xe3מ\x8c\xaa\xf2iw\xd2y\xbe\xf8\xee\xef\x15\xa83\xc1\xebYj\xffb\xe6<\x94[\x96\xda\xc6BAQxm\xe3n\x9f\xea\xb9\xd9\xcd\xf2$o\x853xQ\xb0=\x1c\x11\x8e\xd5\x10\xbc\xe6\xb5U\x866j\x18i\x1aO\xc4ʺw\xe4\xf79O5\xf50\xd1\xf3\x06\x1a\xcbC\x8dY#\xff,\xe1\xc6\xe5\x01\xc7\x04\xc8\xd4\xc3Ai\x1bⳇ\x81\x9e+\xf0\x98\v=\x92}\xae\xb4\xc3>\xcfq\xc8g\xc1\xe1\x9e\x05!Ȳ $\x99L)\x87x\x9e%\x14y\xc6`\xe49\u0091\xcb\x02\x92\x19\x90\xbd\xc39)\xc7n\x92\x8a=\x92\xf7;S\x8a5\xe6\xb7$\xa7\x8f\xd3$\x1c\xa3Iج\x9c\xc34\xe1\xb8̲c2\t4|\xa6P噂\x95\xe7\bW\x9e7`\x99\rYf%g\xe6\xe7e\xc7[.N\xdeK\x95\x83\x9a\xdc\xebH\x15\xcdI\xa1\xec\xc5\x17\xdd1{\x99\xffp\xa7\x9cm\xd5qec\t\xeb\xfa\xd4{F~f\xc2\xef\xa3Z!l\xd9\xfd\xce\x06L\xe3\x88\xc4\xf3\xff\x8d\x97\xe7o\x1bu\xbb6\x1aJ\xaap\x87uwv\xa5\x15zC\xde\xd3\xec\u0603~\x8c\xc6\x15{\xa9\njȪ\xde\xf2z\xe5\x80ۿW\x1bB>\xc8zӾ}\x93\x8cfE\xc9\xcf6n\x88\xc0\\\xb5A\\&\x10Q\xe1\v\xe3\xdfIβ\x88\xa7\x15\xbd\\\xc85\x1e\\\t\x81W\x1ee\xed\xad\xef\xd26\x8c;Z\xe8\x94u\xafW\xdcK\xce\xe5\xd3\xc2p\x9c\x96\xec?\xf0\x96\xe6\xf9\x1c\xceۻ-6\r\x92\x82\xb7;\xd7\x15B5\xd2;\xb0\x16\xb3\x99\xce؊\xdf\xee;\x10#\x95v\xf5\x9f(\xad\xb5\xc5fc\xb7.\xb9\xaa?\xabi\xee\xb6\x0e\xbb\r\n\v\x15g\"\xb1\xd6\xc3\x1c\x99\xca\xd7%U\xe6\xec\xea\nnj\x1c\xc6\xf38\xc1nNe[F\xcd\xcb\xf0\xba\xdf(mí\xbf\xb8\x99w.\xbb[\xa1}\x8a^\x82\xc7\xf8Q\xbe\xd9C|W\xc4c\xdc\x05Y#\xa5\"\xaf\xa3EIW\xcbbi\x7f\xb5\xed\xaf\xf2\x04\xef\xa2٬\x0ey\xee{\xcd#\xe5D\x01\xa2\xbb\xdcu\xeavP\xbc\x9c\xf32]\x14\xaf\x0f\nC\xfb\xeb;\x13\xe7\xe2[G\xa6\x12n.\rpu\x01\xfe\x95wݭ\x1b\xc39\xf0\x0f\x8c\x83vh%(ѻa\xafZ\xa7V\xc5\xce9j{\xfbc=\xc0\x88\xf5q\xd3\u009co\t\xca:F.;\\\xe9 \x96\xe3\x13o8\u0084\x81C$\xdf<\xa1EO\x9d\x8b\xbf\x83Hϩ\x94/\xf1^\xad|`kQ9W1\xaaQ\xc6ാ}\x80\x99r<^~\xdd[2ǜ\xff\xb1\xdb\xe1\xf1F\xf4\xf9\xfb\xe1\xdd\xc5\xe9\xfek\x10^\x90+\x85W\\\xfaK\xd5\xf1Jȋ\xae\x88\xdf\xd5uP\xc6/\xa2\x83\xeb8B\x047\xb7Q\xbb\x97\xc4f_\x84\v\"\x0f\x8bw`\xba탧D\x96\xd1\xc1\xb3\xc0\xd7\xc6iC\x8b\xb9\x9b\xeeo\x87=\xf03$*oU\xd3\xd5\u05f5?Qݰ9\xa6f\x1bp\xae'\x86\f\x16\x1a\xe4\x04N \x88\xb5S\x8e\xbe\xe1S9\xfd>\x11\xa8m(\xfe\x18\x85\xd3\xf5A\xf3\x87\xc8\xdf\x7f^\xe5\x01\xdd-u\x02\xf5RO\xc0\xac/\xe0\x8f\x10a(\x99.\x14\x7fc}YXG\x81&\xb9jQ]\x9bi\xd6\xd5\xf3\xc9J\xeb\xf6~;\xd6sT\x82C\x83\x18\xff\x06\x1f\xba\xf8F%5\x9cY\xaa\x8a\x1a\xcelNAu\xd4Qdr\x8d\x82\xba\xfa4q\xad\xceސ\x8d\x8d\x9c\a\x80\xc7\xc1\xc2\xe7\x0f\xdcy\xb0\x02\xb4\xa6\x87p5\xf6\x93u\x98\x0f \x00\x93G\x91\xd9\xf8t|s~\xa8{1\xb4\xdb7\xa4\x99\xa9\xa8\x1f Tg\xb6Z\xbd\x8c)`.\x0f\xee\xc3\x15,|\xb0(D\x12\vi\xf2\xb5d*%\xf2x_7\xb4\xb4A\xa7\x0e\x19\xd1|`\n8;0\xeb\xb6[&\x1d\xa8\xda\xd1\x03\xac3\xc99\xa0\xae\x1d\xe2\xf5\x9c\x8b՟\xd2\xfa\fT\xcfN\xedC\xbb\xad\xdf_r\xdcv\xf7*RW\xa8\x8c\xdf\x1b2LA\xf3\x01\xaf\x01B\x12\a^\x14i8*D?u5Ĵ\xdd6,0\xafW}\x16\xd2\x7f\xf9\xea\xc6Ǯ\xf1\xe4JA\x7f\x97\xea\x86\x14L\xd8\x7f\xa8\xc8\xdd\x06P\xe8\xbc\b\x7f\xbc\xf1|\x06\xef;ۦ>,\xdb\xf2#!,\x88\xb1\xc8:~@rM>\xc20\x10tg\x1e!\xc7-\xcf\xd8\xf7\xbdl\x93\xad\xb8S\xf2\xa0@\x0fW՚\xfc\x8d2\xc3\xc4\xe1\x83Tw\xbc:0\xd1\xf8\x1b\x8b\x1a\xdfQe\x18\xe5\xfc\xec\xf0\x89!\xca\x04\xe5\xec\x1f1\xee\xb4\x7f\x9c\aT\xab\xdb\xc8o\th\x8c\xfd\xf0\x0e\xac\xa9\x1d\r7\xe2\x82\xe0\xe9:'\v\xbeY\xb3EÄ\x93]<_\xb9\x93\x95\xe9(\xbfFy\xc6\xe2]\x0flC>J\x03a\xe7\x9fuaZs\x01ڬa\xbf\x97ʸ\x1d\xa1\xf5\x9a\xb0\xbd\x0f_b)}\xca8F\xb1\xee\x03a\x84\x99\xa6ԳYo\x98IR\xa86\xf0Fゞ]\xbe\x97f\x99\r\xf8\xe1\x956\x94G4\xf27\xc5\xf8\x18'\xda\xf5\x02\xf9o)i\xf0m\xbb\xfd0\xaeGp\x8erx\x16\xdaY\xa3\xa8m&\x98\a\x04A\x9e\x143\xc6Z\x80vi\x171V\xe7sN\xb4Ղ\x17\x05\xf8\xc4y\v\xdb\xf1\xad\xe6n\x02\xa9n<\xe6l\xf8\xc9\xe1\xf7\xb0vH\x82\xd1ą\xdfR\xf7}-+\xb3#\x15\a+TJV\x87c\x90\xcb\x11[>\x027\xaf\x00\x93!\xa8!t(\xab1\x95\x12\xad\xad\xb7\xfa|_\x83.\xcd\x1eG1\xf5\xa5\x03\xe1#\x95\xaf\xfcE\xf7뽒\xc5\xda\xf3\x02\xaban\xfcv\x98b\xd2\x06\xec\xe6\x18%9q\xdf\xc0\xf27J\xa3\x18\x94%\bB\xb5\xc7'\xe1\"\x90\x8b\xf36\xdaPeR\xe3\xa0\xfbN\xe3\x99\x10\b!\xc7\xf1\xbd\xf7\xdb}\xeeB\x94[\xff\t\xb8\x1a\xf0\r\xd1L\x84\xefc\xba\xcdD'\n\xdaFF\n0\x19\x1a-t\x1a\xc44\x9d\b\xa6\x8b\xfe\x1f\x1b\xbc\x9cj\x9b\xf8>\xc5\v\xfe\xd2k\xde;\xf4\x86\x1fC\xab\x9bx\xcf5B\x8f?\xb1\xbd\xab\xbd\xca,\xd6\x7f\xfe??\xccvJ\xf2\xb2^N:X\xe8;՞\xd2̧\xcf\xee8X\xcfG\x03t}\xb7\x97\x8b\x9c\xf4\xd3ea\xe75c\xce\xf0\xe9\xd6\xebDb\xa7ˢ\xcdg\v5\xaf;\xbb'\x8a\x9f\x8b\x9c[c\x7f\xf3\xcd\"\xb1\xa6\x87\x10\x896#Ө\xe3\xcf\xd9h\xb3\x15l\x06\x1cG\xbe\xee\xd4\v@\xaf\x14nF\xed\xc0\xe0%*м\xb5\xb6\xfdH\xfe\xcd\xff\x06\x00\x00\xff\xffw\xe0\xabZYz\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec}O\x93\x1b+\x92\xf8ݟ\x82\xd0\xef\xe0\x99\x89\x96\xfc\xfc\xdbˆo\x9e\xb6\xbd\xabx\xef\xd9\x1d\xee~\x9e\xcb^PUJ\xe25\x055@\xa9\xad\xd9\xd8\xef\xbeA\x02\xf5\x97\xaa\xa2d\xf5\x1bφ\xb9\xd8]\x82$\xc9L\x92LH\x92\xf5z\xfd\x82\x96\xec\v(ͤxCh\xc9\xe0\xab\x01a\xffқ\xc7\x7f\xd7\x1b&_\x9d^\xbfxd\"\x7fCn+md\xf1\x19\xb4\xacT\x06\xef`\xcf\x043L\x8a\x17\x05\x18\x9aSC\u07fc \x84\n!\r\xb5\x9f\xb5\xfd\x93\x90L\n\xa3$\xe7\xa0\xd6\a\x10\x9b\xc7j\a\xbb\x8a\xf1\x1c\x14\x02\x0f]\x9f~ڼ\xfe\xff\x9b\x9f^\x10\"h\x01oȎf\x8fU\xa97'\xe0\xa0\xe4\x86\xc9\x17\xba\x84̂<(Y\x95oH\xf3\x83k\xe2\xbbs\xa8\xfe\x15[\xe3\aδ\xf9\xb9\xf5\xf1\x17\xa6\r\xfeP\xf2JQ^\xf7\x84\xdf4\x13\x87\x8aS\x15\xbe\xbe Dg\xb2\x847\xe4\xa3\xed\xa2\xa4\x19\xe4/\b\xf1Xc\x97k\x8f\xf0鵃\x90\x1d\xa1\xa0\x0e\x17Bd\t\xe2\xed\xdd\xf6˿\xddw>\x13\x92\x83\xce\x14+\r\x8e\xdd!F\x98&\x94|\xc1a\x11\xe5\xa9L̑\x1a\xa2\xa0T\xa0A\x18M\xcc\x11HFKS) rO~\xaev\xa0\x04\x18\xd05hB2^i\x03\x8ahC\r\x10j\b%\xa5d\xc2\x10&\x88a\x05\x90?\xbd\xbd\xdb\x12\xb9\xfb\x1d2\xa3\t\x159\xa1ZˌQ\x0399I^\x15\xe0\xda\xfeySC-\x95,A\x19\x16\xe8\xecJKxZ_{\xc3{i)\xe0j\x91\xdcJ\r\xb8ax*B\xee\x89f\xc7c\x8eL7\xc3E9\xea\x00&\xb6\x12\x15\x1e\xf9\r\xb9\ae\xc1\x10}\x94\x15ϭ\xb0\x9d@Y\x82e\xf2 \xd8?jؚ\x18\x89\x9drj\xc0\v@S\x980\xa0\x04\xe5\xe4Dy\x057H\x92\x82\x9e\x89\x02\xdb\v\xa9D\v\x1eV\xd1\x1b\xf2\xabT@\x98\xd8\xcb7\xe4hL\xa9\u07fczu`&L\x9aL\x16E%\x989\xbfB\xf9g\xbb\xcaH\xa5_\xe5p\x02\xfeJ\xb3Ú\xaa\xec\xc8\fd\x96\x91\xafh\xc9ֈ\xba\xc0\x89\xb3)\xf2\xff\x17\x04@\xbf\xec\xe0j\xceV\x18\xb5QL\x1cZ?\xa0\xd4Op\xc0N\x00'_\xae\xa9\x1bECh\xfb\xc9R\xe7\xf3\xfb\xfb\x87\xb6\xec1ݧ>ҽ%\x90\r\v,\xc1\x98\u0603rL\xdc+Y L\x10\xb9\x93>\x14]\xce@\xf4ɯ\xab]\xc1\x8c\xe5\xfb\xdf+\xd0V\xc8\xe5\x86ܢ&!; U\x99[\xc9ܐ\xad \xb7\xb4\x00~K5<;\x03,\xa5\xf5\xda\x126\x8d\x05m%د\xec\xa8\xd6\xfa!\xe8\xb2\x11~9\x85p_B֙0\xb6\x15۳\f\xa7\x05\xd9K\xd5\xe8\v\xa7\xae6\x1d\x90\xf1)k\x8b\xab}+Ş\x1d\xfa\xbfEQqU;\xc8d\xf8\xa9R\r2\xf6k\f\x8fi\\\xf0W\xaa(\xe7\xc0?0\x0e\xfa\xb7\x92K\x9aǪ\xf5P\xbb\x1b\xb6\xb22n\xd1\x10U\xb1\x03eu\xc5\xde\xfeXw\x10\x05JH\x85\x8dQG\x94\xa0\xf6R\x15\xe4\xe9\b\x82T:L\fW\x03\xd4pd\xa4\xe6\xb2\xd5\"\aP\x83\x1a#2`K\xa6ٽ\xa0\xa5>J\xf3\xc0\n\x90\x95\x99a\xc7\xed\xfd\xb6\xd7 H\x85g\v\xea\xf7JCn\a\xf3D\x99\xb1\xac\x89 }{\xbf%_P\xd5\ax\xa8\xf2+ML\xa5\x04\x92\xe23\xd0\xfc\xfc \x7f\xd3@\xf2\n\xb5F\xa6\x00\xd9}Cv\xb0\x97\n\"p\x15\xd8\xf6\xb62(e%T#J\xb22\x1b\xf2p\x04+B\xb4\xe2\xc6+ \xa6\xc9\xeb\x9fH\xc1De`Hڑ\x99\x86D\xa1\x86\x16\xf2\x04j\x86^﨡\xbf\xdaz=2\xd9\xf6\x04\x01ؑ\xee<\xc9vg\xfbcdT^\xac\xc9v߂\xc84Y\xad\x88Td\xe5l\x91Ս\x9b\x02\x15\xe3f\xcdD\xab\x8f\b\xc4'\xc6y\xe8w\xd9\xc8\x1d\x01\x1d\xef\xf4\x83\xfc\xa0\xdd\x14\x9d#\xc4H\xb3\x16]\x9e\x8e`\x8e\xa0H)\x83\x15\x10\xc1\xdb\xce'\xa2\xcf\xda@\xe1\xa9\x12\xd6\xde@D\xd4K\x9c{\x10\xda\x12\xd5\xe3<\x1c\xa7\xa88\xa7;\x0eo\x88Qհ;G\x86\x9d\x94\x1ch\xdf\x0e\xe8\xd3\xe13hò\x19*\xac\xfadp\xad\"DP\xfe\a\x1c[L\"\x9aYf\xe8#\x10\x1a\xa8a\xed\x13\xce[D\xecP\x80\xfc\x97 \xef\xec\xe2\x99\xd9%-\xa6\xe4\xdc\xe2ɀ\xa32\x13\x92p)\x0e\xa0\\o\xd60\t\x92\xa3\xc0\xcaVN욥\x80\xdbŗ\xec+\xbb\x9cŔ\x94\x9dţ2\xc0\x846@\xf3\xcd\xea\x9a\f\x82\xaf\x19\xafr\xc8o\x9d5zo\xed\xe8\x8aW\x16\xd7s>\xd2:_#)\x126p\x13\x8d\xccim]\xaatS\xf1NA\x9ay6\xb7\x99\x1d̳R1\x89qzW\xb6м\x88Qq\xfea\xa2\r\xca\x0f\x13m\xa6\xfc0\xd1F\xcb\x0f\x13m\xbe\xfc0\xd1|\xf9a\xa2\x85\xf2\xc3D\xfba\xa2MU\x9b\xd2\xd6s\x18\xb9k\x8a#?\xceb\x91p\xac=\x85\xe2\x04|\x1f\x85\xe1\xe3\xbcS#3\xb7\xf1V\x918\xfe\xe4\xd8p\xddZJ\xeaPM;A\x82x\xbb\x8bN3\xc1\x9b\xdf\x10/\x1f:\xbd(^~;\xd9\xf8J\xf1\xf2\x1eþ\xd5}\xa5h\xf90\xfee\xd1\xf27>T\xa3\x00\x1a\xb6\xe7\xddY|>\xd6e\xaf\xb7\x01\xe0?8\xfev\x10\x1fv\x19\xe3\x9f=\xda~\x84\xf9\x89\x81\U0006bfec\xbe?J/\xa6\xed(5\ad\x8a\f*ܷ\xb6~e;\xb8\xab\x1bH\xf7}\n\xe7RiL\x8d\x98\x9f\xa2\xd7P˴\b\xf6\xbdNf\x03ŧү\x15i7:\xb7\x91&sw:#\xe3\xc1M\x00}\x16\xd9QI!+\xed\xf7\r,\xf4\xb7\xb8}\xe1\x8fB1\f,Q\xc1\xbe&GYE\"\xb6'h7\x13\xbf7\x1e\xb5\xe7Ϩ\xc1\xd0\xd3\xebM\xf7\x17#}\f\x1fyb\xe6\x18\xc1\x13\xef\xea\xd2<\xb7\x06h+ ?L8\x7f\xf3\xbf/HD*\"\x18\x1f[\xb0\xea\xf4\b\x9d\xa5\xe9S\xe96\x89\x16\xaf\xfc\xd3\x1b\x1ciQ~\x17\xc7\xf6uc\xf7F\x8c\xc0\xa5\x87\xd9\xe9W\x18ң\xf7\xa6\xc3\xed\x96\xc4\xec\xf5#\xf2F\x81\xceG\xea\xa5\xecM\xcdD\xe5]\x10\x8b\x97\x18\x87\xfd\xcdG\xef)\xd1v\x17\xc5\xd8͆*'F\xd6uc\xe6\xa6A.\x88\xa7K\"\xce|\xec\xdc\xe2\x889\x1f\xa169\x8e\xe48\xb9H\x04\xdc$\xe0\xd1踩\xb8\xb7\x99}\xefaL\\z\xb4\xdb$h\x8c\x84\x9b\x8fq\xbb^$\xfb5\xbc\xecqU3\x1b\xa76\xeb\x85O\xe37\x1b\x89\xb6$\xfel\x96b\x17ƚձd#\xfd.\x8d0\xebF\x90\x8d\x00M\x89+\x1b\x89\x1b\x1b\x818\x19M\x96\x1a-6\x02{fٝ\x94\x92\xc9\x1f\x97D\x89ųِ\xd9Ր\xffQ\xf2w)\x19\xa4\xea\x18\x97s\x0eͧ^u\xcb\xf9`cM\x1b\xab1;\x95\x99\xe3rc\xb5\xa8\xb8a%\xc7\xe3\xc5\x13ˣ>\xbb9¹N\f\xf1\xbb\xc4\xeb\x9a.\x99\t\xf9\xf4\xb9\x16\xe6M\xcf䦚<\x01\xe7\x84\xc6Dq0\xf2\xcc%d\xca\xe4\x1a\xec\na\xa7\xa7Oy\xe1\xf36\xdd8y\xc7\x1b\xa9\xb1\x13\x18s\x84\xc2B\x19O{2\xaaʧ\xcdIg\xf9ⷿW\xa0\xce\x04ӳ\xd4\xf6\xc5\xcc}(7-\xb5\xf5\x85\x82\xa2\xf0\xdaƥ\x01\xeb\x99\xd9\xcd\xf4$o\x85[\xf0\xa2`{8\"\x1c\xab!x\xcdk\xab\f\xad\xd70R5\xbe\x11+\xeb֑\xdf\xe7,\xd5\xd4\xcbD\xcf\xebh,w5f\x17\xf9gq7.w8&@\xa6^\x0eJ;\x10\x9f\xbd\f\xf4\\\x8eǜ\xeb\x91ls\xa5]\xf6y\x8eK>\v.\xf7,pA\x969!\xc9dJ\xb9\xc4\xf3,\xae\xc83:#\xcf\xe1\x8e\\\xe6\x90̀\xec]\xceI\xb9v\x93\x14\xec\x91|ޙ\x12\xac1\x7f$9}\x9d&\xe1\x1aM\xc2a\xe5\x1c\xa6\t\xd7e\x96]\x93I\xa0\xe13\xb9*\xcf\xe4\xac<\x87\xbb\xf2\xbc\x0eˬ\xcb2+93?/\xbb\xder\xf1\xe6\xbdT9\xa8ɳ\x8eTќ\x14ʞ\x7f\xd1\xed\xb3\xb7\xf3\x1fr\xca\xd9Z\x1dS6\xb6a]\xdfz\xcf\xc8\xcfL\xf8sT+\x84\xadu\xbfs\x00\xd3\x18\"\xf1\xfd\xff\xc6\xca\xf3i_ݩ\x8d\x86\x92*\xb4og\x92Ѭ(\xf9\xd9\xfa\r\x11\x98\xab6\x88\xcb\x04\"*|\xa1\xff;\xc9Y\x16\xb1\xb4\xa2Ʌ\\\xe5AJ\bLy\x94\xb5\x8f\xbeK[1nh\xa1Q\xd6M\xaf\xb8\x97\x9c˧\x85\xee8-\xd9\x7f`\xba\xec\xf9=\x9c\xb7w[\xac\x1a$\x05\xd3l\xd7\x11B5\xd2;\xb0+f3\x9c\xb1\x19\xbf\xddw F\"\xed\xea?QZ\xeb\x15\x9b\x8de]rQ\x7fV\xd3\xdcm\x1dv\x1b\x14\x16*\xceDb\xac\x8792\x95\xafK\xaa\xcc\xd9\xc5\x15\xdc\xd48\x8c\xef\xe3\x84usj\xb7ety\x19\xe6]\x8e\xd26\xa4_\xc6üs\xd9=\n\xedS\xf4\x12<Ư\xf2\xcd^\xe2\xbb\"\x1e\xe3&\xc8\x1a)\x15\xf9\x1c\rJ\xba\xda.\x96\xf6\xa9m\x7f\x95'x\x17\xdd\xcd\xea\x90\xe7\xbeW=\x12N\x14 \xba\xe4\xaeS\xd9A19\xe7e\xba(\x1e\x1f\x14\xba\xf6\xe9;\x13\xc7\xe2kG\x86\x122\x97\x06\xb8:\xbekc\xa7\xd7\xdd\x17t\xadj\x15\xe6\x8d\x1d\xef<\x85\xad\xab~\x82\xbb\xbf^?FJ\x1b\xa9\xe8\x01~\x91.\a\xf6\x1c\r\xba\xb5;\tн\xc9\x13b\x16\xc3l\x88\xb9\x02>\x1bw\x0fX\x13\x8a3\x98\x87\x87_\xdc\x00\f+`\xf3\xce'\u07b6\xdaN\x83\xa5f\x18\x98k\xb4\xb3\xff=F\xd6\v\x82\xf9d[\xfci\xe1\xad\x00Ý1\xecm\x11\xf6\xa7N\"\xe9@\xa29\x11\xfd\x12o\xd5\xda_j1ə\x1eQ\t\x1d\x83\xd3z\xd4\x00w^\xf1\xba\xf2u\xb3.\x8e\x19\x93ci\xdf1\xc3\xf6|\xe2w\x97\x88\xdb?\xf3\xe0\x83\xe6+\x85)\x13}\x92nL1\xf8\r\xb9\xdf;Q'\xfa\xad1\xd6Q\x8ei\xee\bz#m\xeb\x05N\x1aʛ\f\xec1\x95R7\xc1\x88\x95\xc9P\x15g\x80L0n*\xf7zl\xac\xb7>\xc6\xf9\x92\xb1\xd6m\xd3Ǫ\xab,\x03\xad\xf7\x15\xe7\xe7:\xbez\xc9\xc0c\xab˕H\xf1\x812~\x11\x1d\\\xc3\x11\"\xb8\xb1\x8d\xea\xd1$6\xfb\xa0N\x10y\x98\xbc\x83\xa5\xc0\x16\xbcu\xb0\x8c\x0e\x9e\x05>\xd6J\x1bZ\xcceN\xbf\x1d\xb6\xc0\xf7ETފΪ\xd3\x7f?Qݰ9fS6\xe0\\K4A-4\xc8\t\x9c@\x10)0j\x1esq\xba7p\xfam\"P\xdbP|X\xbe{\xb2 ,p\xc1\x93\xf4\xef\xa6<\xe0\xf2\xadN\xa0^\xea\t\x98uB\xf7\b\x11\x86\x92\xe9\\\xbb7\xd66\x82u\x14h\xd2\xd2\x1fյ\x99f]=\x9f\xac\xb4n\xef\xb7c-G%8T\x88\xf1o\xf0p\xc27*\xa9\xe1\xc8RU\xd4pds\n\xaa\xa3\x8e\"\x83k\x14\xd4Շ\x89su6\xe32Vr\x16\x00^/\n\xe9\xf4\xdd\xfd\xa2\x02\xb4\xa6\x87\x90j\xf9\xc9\x1a`\a\x10\x80\x9b\x11\x91\xd1\xf8\xed\xdd\xe6>J7Ѱ;\x87\xa2\x99\xa9\xa8\xef D\xfb\xb5j\xbd\x8c)`.\x0f\xee!\x04\x16^\"\n\x96\xe9B\x9a|-\x99J\xb1d\xdf\xd7\x15-m\xf0\b\x19\x19Ѽ\x1c\x05\x9c\x1d\x985\x03-\x93\x0eT\xed\xe8\x01֙\xe4\x1cP\xd7\x0e\xf1z\xce\xc9\xeao\xfd|\x06\xaag\x87\xf6\xa1]ןW8n\xbb<}\xd4\x05\xbe\xe2CB\x86)h^\xe6\x1a $\xb1\xe3E\x96\xab\xa3B\xf4\r\xab!\xa6\xed\xbaa\x82y\xbd\xeaw\xb5\xfc\x93V7\xde\x17\x8a;\xeb\x05\xfd]\xaa\x1bR0a\xff\xa1\"w\a\n\xa1\xf1\"\xfc1\x83\xf6\f\xdew\xb6N}\xf9\xb2eGB\x98\x10c\x9eZ\xfc\xc2ݚ|\x84\xa1c\xe1\xee\xd0A\x8eGh\xb1\x87\xbbl\x95\xad\xb8S\xf2\xa0@\x0fg՚\xfc\x8d2\xc3\xc4\xe1\x83Tw\xbc:0\xd1\xd8\x1b\x8b*\xdfQe\x18\xe5\xfc\xec\xf0\x89!\xca\x04\xe5\xec\x1f1\xee\xb4\x7f\x9c\aT\xab\xdb\xc8o\th\x8c\xfd\xf0\x0e\xecR;\xean\xc4\x05\xc1\xd3uN\x16|\xb5f˟\t'\xbbx_o'+\xd3Q~\x8d\xf2\x8c\br\xe8sC>J\x03\xe1$\x99ua\xda\xe5\x02\xb4Y\xc3~/\x95q'\f\xeb5a{\xef\xbeĶ\x88)\xe3\x18\t\xe3^\xfe\"\xcc4\xa1\x83\xcd|Ý\t\x85j\x033\xe4\x16\xf4\xec\xf6\x0fi\x96Y\xef\x18^iCyD#\x7fS\xa46\xfa\x89v\xbe@\xfe[ʶ\xea\xb6]\x7f\xf8<\x15\x82s\x94û\xb5n5\x8a\xae\xcd\x04\xf7\x95@\x90'Ō\xb1+@;T\x88\x18\xab\xf39'\xdaj\xc1\x8bީ\"\xceZ؎\x1f]v7$\xea\xcacƆ\x1f\x1c\xbe\xaf\xb4C\x12\x8c\f\xcb]$b:\xb4\xb5\xac̎T\x1c\xacP)Y\x1d\x8eA.G\xd6\xf2\x11\xb8y\x05\xf8\xa6\x17j\b\x1d\xc24L\xa5D\xeb(\xa7\xbe/֠K\xb3\xc7QL\xfdQtx}\xf2\x95O\x9c\xbe\xde+Y\xac=/0\xba\xe2\xc6\x1f\xaf(&\xad\xc3n\x8eQ\x92\x13\xf7\xa6\x92\xcfP\x8cbP\x96 \b\xd5\x1e\x9f\x84\xc4\x12\x17??\xa6\rU&\xd5\x0f\xba\xefT\x9eq\x81\x10r\x1c\xdf{\x7f|\xe4\x12l\xdc\xfa'\xc5j\xc07D3\x11\x1e\xbet\x87SN\x14\xb4\xf5\x8c\x14\xe0\xe6Z4pf\xe0\xd3t<\x98.\xfa\x7f\xac\xf3r\xaa\xd7\xc4\xf7)V\xf0\x97^\xf5\xde%*|\\\xab\xae\xe2-\xd7\b=\xfe\xc4\xf6.\x96'\xb3X\xff\xf9\x9f~9\xea\x94de\xbd\x9c4\xb0\xd0v\xaa-\xa5\x99\xa7\xb4\xee8X\xcbG\x03tm\xb7\x97\x8b\x8c\xf4\xd3en\xe75}\xce\xf0&\xebu<\xb1\xd3e\xde泹\x9a\xd7\x1d\xdd\x13\xc5\xe7\a\xe7\xe6\xd8\xdf|\xb5\x88\xaf\xe9!D\xbc\xcd\xc80j\xffs\xd6\xdbl9\x9b\x01Ǒׂz\x0e\xe8\x95\xdc\xcd\xe8:0\xf8\x88\n4o\xcdmߓ\xff\xf2\xbf\x01\x00\x00\xff\xffԋ\n\xaf2z\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\xe3\xb8\x11\xbe\xebWt\xed\x1e|YQ3\x9bKJ\x97\x94FN\xaa\xa6\xe2\x89]#ǹ\xe4\xb0\x10Д\xb0\x06\x01\x06\x0fi\x94T\xfe{\xaa\x01\xf0!\x92\xb2\xe4\xa9$\x8b\x8bM\xb2\xd1\xe8\xfe\xfa\r\xcd\xe7\xf3\x19\xab\xe5\vZ'\x8d^\x02\xab%~\xf3\xa8\xe9\xc9\x15\xaf\xbfw\x854\x8b\xc3\xc7٫\xd4b\t\xeb༩\xbe\xa23\xc1r\xbc\xc7Rj\xe9\xa5ѳ\n=\x13̳\xe5\f\x80im<\xa3\u05ce\x1e\x01\xb8\xd1\xde\x1a\xa5\xd0\xcew\xa8\x8bװ\xc5m\x90J\xa0\x8d̛\xa3\x0f\x1f\x8a\x8f?\x17\x1ff\x00\x9aU\xb8\x84-㯡v\xdeX\xb6CexbY\x1cP\xa15\x8543W#\xa7\x13vքz\t݇\xc4!\x9f\x9e$\xff\x14\x99m\x12\xb3\x87\xcc,~W\xd2\xf9?_\xa6y\x90\xceG\xbaZ\x05\xcb\xd4%\xb1\"\x89\xdb\x1b\xeb\xff\xd2\x1d=\x87\xadS\xe9\x8bԻ\xa0\x98\xbd\xb0}\x06ษq\tqw\xcd8\x8a\x19@\x86&r\x9b\x03\x13\"\x82\xcdԓ\x95ڣ]\x1b\x15*ݞ%\xd0q+k\x1f\xc1L\xba@V\x06\x1am\xc0y\xe6\x83\x03\x17\xf8\x1e\x98\x83ՁIŶ\n\x17\x7fլ\xf9?\xf2\x03\xf8\xd5\x19\xfd\xc4\xfc~\tE\xdaU\xd4{暯\xc9FO\xbd7\xfeD\n8o\xa5\xdeM\x89\xf4\xc0\x9c\x7faJ\x8a(ɳ\xac\x10\xa4\x03\xbfGP\xccy\xf0\xf4\x82\x9e\x12B@\x10!4\b\xc1\x91\xb9|\x0e\xc0!q\x89\x18MK\xaaFg\x9d\x89M\xa2\xc0ˀK\x92\x9f\xded\xe9{l\x1b\xff.\xb8Ŗ\xa5\xf3\xac\xaa\xcf\xf8\xaevx\x89\xd9\x19\x14\xf7X\xb2\xa0|_U\xb2\x92\xea\xfb\xe5\xb9Z5\xf2B\xa4]g'ޟ\xbdK\xa7n\x8dQ\xc8\x12\x97Du\xf8\x98\xbc\x90\xef\xb1b\xcbLljԫ\xa7\xcf/\xbfۜ\xbd\x86)G\x1a\x04\x05\x19\x8e\xf5l\xb3G\x8b\xf0\x12\xe3/\xd9\xcde\xd5Z\x9e\x00f\xfb+r\xdf\x19\xb1\xb6\xa6F\xebe\x13,i\xf5rQ\xef\xed@\xa6;\x12;Q\x81\xa0$\x84ɏr\xbc\xa0Ț\x82)\xc1\xef\xa5\x03\x8b\xb5E\x87\xda\xf7\xe1m\x05+\x81\xe9,^\x01\x1b\xb4Ćb9(A\xb9\xeb\x80փEnvZ\xfe\xb3\xe5\xed\xc0\x9b\xec\xbc\x1e\x9d\x1f\xf0\x8c\xf1\xa9\x99\"W\r\xf8\x130-\xa0b'\xb0H\xa7@\xd0=~\x91\xc4\x15\xf0\x85\xfc]\xea\xd2,a\xef}햋\xc5N\xfa&\asSUAK\x7fZ\xc4t*\xb7\xc1\x1b\xeb\x16\x02\x0f\xa8\x16N\xee\xe6\xcc\xf2\xbd\xf4\xc8}\xb0\xb8`\xb5\x9cG\xd1uJ\x9a\x95\xf8\xd1\xe6\xac\xed\xee\xced\x1dEmZ1k\xbea\x01ʘ\xc9\v\xd2֤E\a4\xbd\"t\xbe\xfeq\xf3\f\xcd\xd1\xd1\x18C\xf4#\xee\xddFי\x80\x00\x93\xbaD\x9b\x8cXZSE\x9e\xa8Em\xa4\xf6\xf1\x81+\x89z\b\xbf\v\xdbJz\xb2\xfb?\x02:O\xb6*`\x1d\v\x13l\x11B\x1d㾀\xcf\x1a֬B\xb5f\x0e\xff\xe7\x06 \xa4ݜ\x80\xbd\xcd\x04\xfd\x9a:$N\xa8\xf5>4\xb5\xf0\x82\xbd&\xa3xS#?\x8b\x1f\x81NZ\xf2p\xcf<Ƹ\x18\xe0\x9aC\xfcr1m\xd6tp\xd3b\x9c\xa3s_\x8c\xc0ᗁȫ\x96\xf0L\xc6\x1am%],\x8bP\x1a;\xac\x18\xac\xcd\xc0\xfd\xd5d\xaab\xf4\ru\xa8Ƃ\xcc\xe1+2\xf1\xa8\xd5\xe9§\xbfY\xe9\xc7\a]0$\xad$\xe2\xe6\xa4\xf9\x13Zi\xc4\x15\xe5?\r\xc8[\b\xf6\xe6\betk\xedՉr\x90;i>ζ\xcdZ=}n2o\n\xa0\x1co\x19\xab\x02V9rM\t\x1f@HG\r\x80\x8bL\xc7`\xe9\xa0b\x83\xb0\x04oû\xd4\xe7F\x97r7V\xba\xdf\xd3\\\xf2\x98+\xac\aȭ\xe3I\x94\x9a\xc8;jk\x0eR\xa0\x9dS|\xc8R\xf2,I\xb0\xa9r\x95\x12\x95pcM/DYTŢ\xa0\xa8f\xea\x8a\r\xd7-a쀙\xd4Ƀ;\x061\xd9\xd8*\x97T\xedQ\x8b\xb6\x1b9\x93\xc6Ĭ\xe5P\xc0Q\xfa}J\x87j*\xee\xe0\xcdأ\xf5\x8a\xa7\xa9\xd7\x03ٟ\xf7H\x94\xa9\x80\"8\xe4\x16}\xf46T\xe4>\xe4J\x05\xc0\x97\xe0bB\x1d\xe6\x89f\xc5F\xad\xd9\xfd\x8a\xa71\xd0p\u0378\xb9\x85\xb9.\xf2\x1d\xb5\u038d\xc0\x16K\xb4\xa8\xfddR\xa7\x01\xc4j\xf4\x18\xf3\xba0\xdcQJ\xe7X{\xb70\a\xb4\a\x89\xc7\xc5\xd1\xd8W\xa9ws\x02|\x9e#h\x11NJŏ\xf1\xcf\x05\x95\x9f\x1f\xef\x1f\x97\xb0\x12\x02\x8cߣ%\xab\x95A5\x8e\xd6\xebo~\x8a5\xf6'\bR\xfc\xe1\xee{p1u\x8a\x9c\x1b\xb0\xd9D\xef?Q\xa3\x16\x85\"\x886\xc9*\xc6\x02UJ2v\x95\xad\x99r͔#Nu\x98\xfdE\x89\x89*\xc8TF}\xc5q2}#\xcc\x00\xbe\xcd;C\xcd+V\xcf\x135\xf3\xa6\x92|6\xd46\xb6\xc1W\"\xb2i\xbb\xa5\x16\x92S\xdbv\x1eI\xcd8\"κ\xf3\t\x18\x86\xfd\xfa\xa5\xfc1\rSR7W\xcf+\x12?\xf6i\xbb!.%\xb3\\\x11\x1dzj\xb7\x1ch\xa4\x8a\xc9\xec\x18\xe7\x98B\xb8њb\xd7\x1b`mb\xbcsÊ\xf0\xce|\xb2\r\xfc\x15'\x80\x1f\xa9\xf2)\x126\x18\xa7m$Kp\x18S\xf551\xe0zDp\xb6F{\x8b,\xeb\x15\x11\xb6E\x95\xc1z\x05۠\x85\xc2F\xa2\xe3\x1e5\xcd\x13\xb2\x14?\xfcf3\x93b\xce\xd3\b\x84\xe2+\x1e\xe4\xf8Nh\x8c\xee\xc3hG\x13\xf8m8\xd0\xc3/\xcdh\xbd\xb0\x99\xec\x97\t0J\xa9\xa8s\x9c\xc8\x13]\xc70\xbe\xbd\xfc\xb4y\xb8s\xb1\xe1G\xed\xa7\x9a\xc4#Z\x8c\xf3\x15\n\xea\xf9M\xbe\xc5\bΣ\x9dp\x80\xd6z\xd1栌\xde\r\x02'\xad|\xa7A\xfd\\r(cA\xa0\xa7Ҥw\xc0\xf7Lﰻ\xb3\xca\xf2\xbf-)\xb9\xcf\xc0g:\x0f\x91\xfa\x92{\xdcd\xd1g9\xd5ԏ\xee\x8b;\xe2\xe9\xbb\xe2F\xfaƲ\x17\x87\xa2+\xb8\x8f\xe8\x9b*M\xa0\xce}w\x7fܭ\xef\x1f\x86Ǘ\xd37 \xf1ޛ\xf37nA\xe0\xc8\\w\x87\xfe\xdb\xe1PQ\xb7z\xb5\x05\xfe\x92\xa8\xd2ec\xde\x02lk\x82\x7f+2\xef\xa6\x1c:\xff8\xf0\x1e\x19\xe3O\x1eך\f\xa2i,\u0083\xa5\xc1\xb3\xbbC\x8bIa\xaa\xb6\xdc~\x19\xb5\x1a\xfc2\xd3\xff6\xfe\xdd\xe6\x06\xbd&k\xed\xe8e\xaa\x97=\xbbf\x90\xfbo¶\xbdW^¿\xfe=\xfbO\x00\x00\x00\xff\xff\x80.\x12\xd3P\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96M\x93\xdb6\x0f\xc7\xef\xfe\x14\x98y\x0e\xb9<\x92\xb3\xed\xa5\xa3[\xb3\xc9a\xa7mƳ\x9bɝ&a\x8bY\x8ad\x01\xd0[\xb7\xd3\xef\xde!)\xf9E\xb67\xdbCy\x13\t\x02\x7f\xfe@\x80j\x9af\xa1\xa2\xfd\x8a\xc46\xf8\x0eT\xb4\xf8\x87\xa0\xcf_\xdc>\xffĭ\r\xcb\xdd\xdd\xe2\xd9z\xd3\xc1}b\t\xc3#rH\xa4\xf1#n\xac\xb7b\x83_\f(\xca(Q\xdd\x02@y\x1fD\xe5iΟ\x00:x\xa1\xe0\x1cR\xb3E\xdf>\xa75\xae\x93u\x06\xa98\x9fB\xef\u07b7w?\xb4\xef\x17\x00^\r\u0601A\x87\x82k\xa5\x9fS$\xfc=!\v\xb7;tH\xa1\xb5a\xc1\x11u\xf6\xbf\xa5\x90b\aDž\xba\x7f\x8c]u\x7f,\xae>\x14W\x8f\xd5UYu\x96\xe5\x97[\x16\xbf\xda\xd1*\xbaD\xca]\x17T\f\xd8\xfamr\x8a\xae\x9a,\x00X\x87\x88\x1d|β\xa2\xd2h\x16\x00㱋\xcc\x06\x941\x05\xa4r+\xb2^\x90\xee\x83K\xc3\x04\xb0\x01\x83\xac\xc9F)\xa0\xbe\xf4X\x8e\ba\x03\xd2#\xd4p \x01\xd68*0e\x1f\xc07\x0e~\xa5\xa4\xef\xa0ͼ\xdaj\x9a\x85\x8c\x06\x15\xf5\x87\xf9\xb4\xec\xb3`\x16\xb2~{K\x02\x8b\x92ē\x88\x12\xd7\x06\x0ft\xc2\xf7\\@\xb1oc\xaf\xf8<\xfaSY\xb8\x15\xb9\xda\xec\xee*i\xdd㠺\xd16D\xf4?\xaf\x1e\xbe\xfe\xf8t6\r\xe7Z\xaf\xa4\x16,\x83\x9a\x94fp\x95\x1a\x04\x8f\x10\b\x86@\x13Un\x0fN#\x85\x88$v\xbaZu\x9c\x14\xcf\xc9\xecL»\xac\xb2Z\x81\xc9U\x83\\\xa0\x8d\x97\x00\xcdx\xb0\n\xd32\x10FBF_\xeb\xe8\xcc1d#\xe5!\xac\xbf\xa1\x96\x16\x9e\x90\xb2\x1b\xe0>$gr\xb1\xed\x90\x04\bu\xd8z\xfb\xe7\xc17\xe7s\xe6\xa0N\xc91?\xd3(\x97\xce+\a;\xe5\x12\xfe\x1f\x9470\xa8=\x10\xe6(\x90\xfc\x89\xbfb\xc2-\xfc\x961Y\xbf\t\x1d\xf4\"\x91\xbb\xe5rkej\x1a:\fC\xf2V\xf6\xcbR\xffv\x9d$\x10/\r\xee\xd0-\xd9n\x1bE\xba\xb7\x82Z\x12\xe1RE\xdb\x14\xe9\xbe4\x8ev0\xff\xa3\xb1\xcd\xf0\xbb3\xad\x17\x17\xa4\x8eR\xe8\xafd \x97yM{\xddZOq\x04\x9d\xa72\x9d\xc7OO_`\n]\x921\xa7_\xb8\x1f7\xf21\x05\x19\x98\xf5\x1b\xa4\x9a\xc4\r\x85\xa1\xf8Dob\xb0^ʇv\x16\xfd\x1c?\xa7\xf5`\x85\xa7+\x99s\xd5\xc2}餹\xa8S4Jд\xf0\xe0\xe1^\r\xe8\xee\x15\xe3\x7f\x9e\x80L\x9a\x9b\f\xf6m)8}\x04\xe6ƕ\xda\xc9\xc2Ծo\xe4\xebJ\xd1>E\xd49\x83\x19b\xdem7V\x97\xf2\x80M x\xe9\xad\ue9e2\x9d\xd1=\x14x{\xb6p\xbd\xa0\xf38\xb6\xc9\xf9\xca\xcd\xc3Cɝ%\x9c\xdd\xc2\x06.z\xee\xeb\\J3\xfc\x97dj'\x1e\xd9\xe8D\x84^N\xfa\xb3\xba\xb6\xe9\xad,\x90(\xd0\xc5\xecLԧbT^ze=\x83\xf2\xfbq#H\xaf\x04^\x90r\x19\xe8\x90r\x9fA\x03&]\xf0\x1b\xb1\x9c\xbe%\x91\x82F\xe6\xf6\xc2\xce\n\x0eW4\xbd\x92\x9d<|rN\xad\x1dv \x94\xf0Ff\x15\x91\xda\xcf\xd6ʛ\xf5\x1d\x04\xabls-\a\x87w\xfa\xbbI(\xb8}\x1a.#5\xf0\x19_\xae\xcc>\xf8\x15\x85-!ϯ|^\\Uz\x87\x9f\x817P\xbaz)/&9\xf7;sB\x91%\x90ڞr\xe5\xb4>\xf4\xef\x0e\xfe\xfa{\xf1O\x00\x00\x00\xff\xff\x045\f\xc6i\n\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VM\x93\xdb6\f\xbd\xfbW`\xa6\x87\xb43\x91\x9c\xb4\x97\x8eo\xad\x93\xc3N\xd24\xb3N\xf7NS\xb0\xc4.E\xb2\x04\xe8\xcd\xf6\xd7w@J\xfe\x94\xbd\xdeCu\x13\t\x82\x8f\x0f\x0f\x8f\xac\xaaj\xa6\x82y\xc0Hƻ\x05\xa8`\xf0;\xa3\x93?\xaa\x1f\x7f\xa5\xda\xf8\xf9\xf6\xfd\xecѸf\x01\xcbD\xec\xfb{$\x9f\xa2\xc6\x0f\xb81ΰ\xf1n\xd6#\xabF\xb1Z\xcc\x00\x94s\x9e\x95\f\x93\xfc\x02h\xef8zk1V-\xba\xfa1\xadq\x9d\x8cm0\xe6\xe4\xe3\xd6\xdbw\xf5\xfb\x9f\xebw3\x00\xa7z\\@㟜\xf5\xaa\x89\xf8OBb\xaa\xb7h1\xfa\xda\xf8\x19\x05Ԓ\xbb\x8d>\x85\x05\xec'\xca\xdaa߂\xf9Ð澤\xc93\xd6\x10\x7f\x9a\x9a\xfdl\x86\x88`ST\xf6\x1cD\x9e$\xe3\xdadU<\x9b\x9e\x01\x90\xf6\x01\x17\xf0E`\x04\xa5\xb1\x99\x01\fG̰\xaa\xe1t\xdb\xf7%\x95\xee\xb0W\x05/\x80\x0f\xe8~\xfbz\xf7\xf0\xcb\xeah\x18\xa0A\xd2\xd1\x04\xceD\x9d`\x06C\xa0`@\x00\xecw\xa0@9P\x91\xcdFi\x86M\xf4=\xac\x95~La\x97\x15\xc0\xaf\xffF\xcd@\xec\xa3j\xf1-P\xd2\x1d(\xc9WB\xc1\xfa\x166\xc6b\xbd[\x14\xa2\x0f\x18ٌ,\x97\xef@C\a\xa3'\xc0\xdf\xc8\xd9J\x144\"\x1e$\xe0\x0eG~\xb0\x19\xe8\x00\xbf\x01\xee\fA\xc4\x10\x91\xd0\x159\x1d%\x06\tRn8A\r+\x8c\x92\x06\xa8\xf3\xc96\xa2\xb9-F\x86\x88ڷ\xce\xfc\xbb\xcbM\u0090lj\x15\x8fr\xd8\x7f\xc61F\xa7,l\x95M\xf8\x16\x94k\xa0W\xcf\x101\xf3\x94\xdcA\xbe\x1cB5\xfc\xe1#\x82q\x1b\xbf\x80\x8e9\xd0b>o\r\x8f\xbd\xa3}\xdf'g\xf8y\x9e\xdb\xc0\xac\x13\xfbH\xf3\x06\xb7h\xe7d\xdaJE\xdd\x19F\xcd)\xe2\\\x05Se\xe8.\xf7O\xdd7?ġ\xdb\xe8\xcd\x11V~\x16\x99\x11G\xe3ڃ\x89\xac\xf9+\x15\x10\xd5\x17\xc1\x94\xa5\xe5\x14{\xa2eHع\xff\xb8\xfa\x06\xe3ֹ\x18\xa7\xec\x17\xe5\xec\x16Ҿ\x04B\x98q\x1b\x8c\xa5\x88Yy\x92\x13]\x13\xbcq\x9c\x7f\xb45\xe8N駴\xee\r\xd3(f\xa9U\r\xcbl(\xb0FH\xa1Q\x8cM\rw\x0e\x96\xaaG\xbbT\x84\xff{\x01\x84i\xaa\x84\xd8\xdbJp腧\xc1\x85\xb5\x83\x89\xd1\xc9.\xd4\xeb\xa4\xd5W\x01\xb5TO\b\x94\x95fctn\r\xd8\xf8\bj\xdf\xf9\x03\x81\xf5Q\xe6\xe9\xce\xcd\xe0Tl\x91OGO\xb0|\xcbA\xb2\xfdS\xa7\x8e\x8d\xe6G\xac\xdbZ\xbc\x82\x06 \xc5=~\xaa\xcf2^\xc6\x00\x93\xea\x9dD2\x8aXh\x10^\xc5\nĤ\x0e1\x9do-\x1f\xba\xd4OoP\xc1\xef\x19\xf3g\xdf^\x9d_z\xc7\"\xf7\xabA\x0fަ\x1eWN\x05\xea\xfc\v\xb1w\x8c\xfd\x9f\x01c\xb91\xaf\x86\x8e\x17\xef\ue5ba\x12\x98\xec\xc5}\xefQ\xfc\x1e/\x9ft\b\xb8)\xcb\r\x98\x86ț\x0e\xba\\ݽ\x86\xc2\v\xe1\xaf(ҝ\xdb\xf8\xe9\xb8\v\xed=~\xf9\x1a\x7fY\xab\xf2\x10\x18\xb5*K\xca݆\xf0)\xad1:d\xa4\xbd\xcd>\x19\xee&3\x02}S\xe6\x84H\xf9\x01\xa4\xd5\xe9\xd3K\xbe5B\x83\x16\x19\x1bX?\x97\x1b\xe9\x99\x18\xfbs\xdc\x1b\x1f{\xc5\v\x90˻b3!#\x97\xacUk\x8b\v\xe0\x98.\xa9l\xf2\xe0\xa1S4цGg\xfe*1S\xc2\xd85\xe3Ue\xc0\xc5{\xa3\x82/\xf841\xfa5z\x8dDx\xdeF\x17O2\xd9\x04g\x83$/\xac急\xe1\xe1>\x8c\xfc\x17\x00\x00\xff\xff\t\x15i;\xcd\r\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\xdc6\xf2\xbf\xebSt9\a\xfdSerb\xff\xb7\xb6\xb6\xe6f\xcb\xeb-\xed&\xb2ʒ}I\xe5\x80!\x9a$\"\x12@\x00p\xa4\xd9T\xbe\xfbV\x03\x04\x87\x0f̌F\xb5\xce\xf2\"\r\x1e\xdd?4\xfa\x8d,\xcb.\x98\x16_\xd1X\xa1\xe4\x1a\x98\x16\xf8\xe4P\xd2/\x9b?\xfc\xcd\xe6B\xad\xb6o.\x1e\x84\xe4k\xb8\xea\xacS\xedg\xb4\xaa3\x05~\xc0RHᄒ\x17-:ƙc\xeb\v\x00&\xa5r\x8c\x86-\xfd\x04(\x94tF5\r\x9a\xacB\x99?t\x1b\xdct\xa2\xe1h<\xf1\xc8z\xfbC\xfe\xe6m\xfe\xc3\x05\x80d-\xaeA+\xbeUM\xd7\xe2\x86\x15\x0f\x9d\xb6\xf9\x16\x1b4*\x17\xea\xc2j,\x88veT\xa7װ\x9f\b{{\xbe\x01\xf3\xad\xe2_=\x99\xf7\x9e\x8c\x9fi\x84u\xffJ\xcd\xfe(\xac\xf3+t\xd3\x19\xd6,A\xf8I+d\xd55\xcc,\xa6/\x00l\xa14\xae\xe1\x86`hV \xbf\x00\xe8\x8f\xe8ae\xc08\xf7Bcͭ\x11ҡ\xb9\"\nQX\x19p\xb4\x85\x11\xday\xa1\xdc*\x0e\x01 \x04\x84`\x1ds\x9d\x05\xdb\x1550\v7\xf8\xb8\xba\x96\xb7FU\x06m\x80\a\xf0\xabU\xf2\x96\xb9z\ryX\x9e\xeb\x9aY\xecg\x83x\xef\xfcD?\xe4v\x04\xda:#d\x95\x82q/Z\x84\xc7\x1a%\xb8ZX\b\xa7\x85Gf\t\x8eq\xfe\x94i\xc6~\x9e\xb6[\xc7Z=Ape\x90\xed\xb7\x06\b\x9c9L\x01\x18\xe4\t\xaa\x04W#I\xde+\x16\x13R\xc8\xca\x0f\x85\x9b\x00\xa7`\x83\x1e\"r\xe8t\x02\x99\xc6\"\u05ca\xe72\x12\x9d\xc0\xba\x99\x8d\x9e\x92\r\xad\xffo\xa3\x9a\x00\xbaU\xfc\x05P\xce\xe2\x1b\x16O\xb8~\x1d\x0f\x9dԏ\x1a\xfd\x9aȼӍb\x1c\r\xb1\xaf\x99\xe4\r\xd2\xcd2p\x86I[\xa29\x00#n\xbb\xdf\xe9)\x98/\x91\xdeh\xe6\x1ca\xf4\xb6s\xe7\x94a\x15\u008f\xaa\xf0\x0e\x8aT\xda\xe0D\xa7m\xad\xba\x86\xc3&r\x01\xb0N\x99\xa4\x82\x13Ⱛ\xa7\x1b\xc9\xce\xecl\xca\xf30\xfa\x11\xed\xe8O\xf3\x82lD(\x99\xb6\xa0w\x15\xa6\xad'Lo\xdf\x04wU\xd4زu\xbfRi\x94\xefn\xaf\xbf\xfe\xff\xddd\x18@\x1b\xa5\xd18\x11\xddg\xf8F\xc1a4\nSQ_\x12\xc1\xb0\n8E\x05\xb4A\a\xc3\x18\xf2\x1eC\xb8\x0ea\xc1\xa06hQ\xba\xb1H\xe2\xa7J`\x12\xd4\xe6W,\\\x0ewh\x88L\xbc\x98B\xc9-\x1a\a\x06\vUI\xf1\uf076%]#\xa6\rs\xd8{\xf1\xfd\xe7\x1d\xadd\rlY\xd3\xe1k`\x92C\xcbv`\x90\xb8@'G\xf4\xfc\x12\x9b\xc3O\xca \bY\xaa5\xd4\xcei\xbb^\xad*\xe1bP,T\xdbvR\xb8\xdd\xca\xc77\xb1\xe9\x9c2v\xc5q\x8b\xcdʊ*c\xa6\xa8\x85\xc3\xc2u\x06WL\x8b\xccC\x97>0\xe6-\xff\xce\xf4a\xd4^N\xb0.\x14#|>\x98\x1d\xb9\x01\ng ,\xb0~k8\xc5^\xd0\xd1\x1d}\xfe\xfb\xdd=D\xd6\xfe2\xe6\xd2\xf7r\xdfo\xb4\xfb+ \x81\tY\x92Y\xd3%\x96F\xb5\x9e&J\xae\x95\x90\xce\xff(\x1a\x81r.~\xdbmZ\xe1\xe8\xde\x7f\xeb\xd0:\xba\xab\x1c\xae|\xa6@\xee\xa9Ӥ\xb9<\x87k\tW\xac\xc5\xe6\x8aY\xfc\xe6\x17@\x92\xb6\x19\t\xf6yW0Nr拃\xd4F\x131E9p_\xb3\xbc\xe3NcA\xb7G\x02\xa4\x9d\xa2\x14\xbd\x87*\x95\x016_\x9eO\b\xa7\r\x97\xbe\xa4w\x9a/\x9a!{\x9f\xda\x13\xb1ɑO\x8d\x0e3\xac\\\x10\x05h\xe6^v\xd8cP++\x9c2;\"\x1c\x1cl\xbe\xa0p\xe0\x1a蓊\xe3\x89s\xdc(\x8e)ش\x15\\͂\xb6R~E\xfe\xa8\x93rɅ>%\xcf\x02\xa6\x15?\x81\xab\xe7\xc8\xc0`\x89\x06e\x81\xd1q\x1dK\x1e\x12\xc8\xc6a}\x89\xf1\xb0R\xc0\x11\xaf\x9eD\xfc\xee\xf6:z\xf2(\xc4\x1e\xbb[\xf2=!\x1f\xfaJ\x81\r\xf7\x81\xee4\xef\xcb\xeb20\xf3>\xcd)`\xa0\x05\x864p\b\x12 \xa4u\xc88\xa82I\x91j\x12 \xc37\xd8\xefx\x1d\xad\x032R\xa8\xca{b\x8a\x9a\xa5\xa0l\x86Ҙ\x10\x8b\xbd6.\x82y\xfcl\x17\xd4\xc7)(j&+\f\xe7E(;\x8a\x8e\xf9\xe5K\xecx\x99\x92\xc4/\x91\x9a\xcc\x1d\xc7\xff,\xb8?\xf3p>\x83~\xc6\xe1\xc6U\xc6\xd1\xc3=t\x1b4\x12\x1d\xfa\xf3qUX:Z\x81\xdaٕڢ\xd9\n|\\=*\xf3 d\x95\x91jfA\a\xecʗ\xa9\xab\xef\xfc\x9f\x17\x9f\xc5W\xb4\xcf=Ф\xd2\xfe\x96\xa7\">v\xf5\xa2C\xc5\x1c\xf6\xf9q\xec\xf2\xaeϬ\xe6{\xc9,\x1ekQԱ8\xe9}\xec\x01c\x12\x94\t\xf3\xe0\x9a\x99\xdc}sU&\x81v\x86\x10\xed\xb2\xbe\x97\x961\xc9\xe9\x7f+\xac\xa3\xf1\x17I\xb0\x13\xcf2\xdf/\xd7\x1f\xfe\x1c\x05\xefċl\xf5@\x02\x1e\xbe\xa7l\x0f+k\x99\xce\xc2j\xe6T+\x8a\xd9j\xcaJ\xaf9\t\xbe\x14hN\xa4q\x9f'\x8bc\xa2\x99\xc8o\x875g呎U\x89\xc4m\xdc:<\x96\xde\x1d\x95״q\xc3*\v\xcc 0h\x99\xa6{~\xc0]\x16\x12\x02\xcd\x04Es\n\xd8CW\x04\x98֍H\x06\xee>\xec\xf7)k/\t*\xcbYe\x0f\x9d=yk\xb1\vt\xa5d)\xaa\x13\xf7\xf0e\xb2x\xc8\tlL;JQuf_J\x8d\x9bS\xe7&͚\x19\xd64\xd8|\x14\r\xda\xc0\xf7\x19\xf6s\xbb\xdc5\x94%]\xbbACB/ir`p\xc0\xef\x04\xdc>\xbf\xd2hJe\xdaВ\xedl,\x17\x0e\x9fl/r!\x1dVC\a\xee\xac\v\xb9\xa7%ϻ\x0eZ\x1a\x8fy\xa21\xe8ꔣ\x9d\xb4\v\x97\xe7AٵK(\x19<(-Xbܠu\v\x83\xa7\x89W\xcbD\xf1\x88\xf5\x04\xe5>!\x83\xbe_\x9f\xa8={\xdb\b\x89\xfaP\x7f\xfa\x16m\xaa\xc0;\\\xdd\x1d\x84h\xf0\xb7\x8eʎ)\xc4,\xdd\t\x98\xad\xa1\xcax6\xa4\x15\xbf\x98\vr\xec\xf7f\x93\x936\xf2\x18\xe9\xb2=\xe2_\a\xceh\x90\x84W\x8f^\xa6!\x1c\xba\xf8\x16B\xb5\xe0K[$\x85\xa22k\xd2b=q\xbdW\xcb\x1d\xbe\x1bix\xaf\xee\xa2%w:z#\xe9y\xa4z\x1c0\"\x17v\xfa\x04\x84\xa8!\xf75\x10\x95h%\x13\rr\x88\x0fa\xf3=\t\xaac*\x1b,)Z\aӋ\xae\xa2\x877\xd4\x195\x82\xf5m\xbeK{\x84fg\x91{?\x9a\x10²\xf6 \x17\xc5\\hKgI\xa2\xb2k\x1a\xb6ip\r\xcet\xcb\xe9#\x96آ\xb5\xac:e\x8a?\x85U\xa1\xe1\xd2o\x01\xb6Q\x9d\x1b:.\x93xui{\x9d:\xaf\xe9\x93\xece̢\x00՚\xb6\xaf\xb9\x9a\xc6\xef\x19;\x82\xfd#\xa9G\xb5\xc1t\xce\xf6\x12\x9f\x00\xe0_\xffN!\xa45)\x03\x1b\xbc\xd7Q\v\x83#N\xf9\x06\x1f\x13\xa3\x8bW\xcb\xf1\xe4U4\x99\xc4\xdcGo\rg\x9d\xbfgtJ\x04\xfd2\xa8U\x13\x8dY9\u058c\x02\xf5f\xe7\xd0N\xddy\xaa\xbd\xe6\xcb\xf2\xbd\x18G\xfb\xe3\xfd\x05J}\xa7\xa1`ҷ\xc1ɺ\x9c\x02.\xacn\xd8.\x95\x82D\x84\x94z\x93q\x91\v\xd8\xebs4j\x8d!\xe597\xc3\xf1\x98>(y\xa0P\x8c\xf6,\xa4\xfb\xeb_^\x90b@\x10\xe7{\xe2\xf2m8\x1cIb\xacd\xda\xd6\xca]\x7f8\xa1\x05w\xc3\xc2h\r\xfb\x1c~\xc8!#\xb5^\x15RW5\xf8\x96\xb3Lu\xfa^~\n\xead\xf1\x89(Կԧb\xd0\x1dR\xea\xe90<\xe9\\\xcd\xdf\x1c_\x83\x15\xbeOM\xa5@\xa8\rB\x17\xc9Rp\xa2\xd4J\x19L\xb8LX\x86\x95I\x10\x99\xc2\xff3\xe3GRO\x16\x83\x1e9\x1f\xd1\xee\xdf:\xc6#\xddfx\xc7[\xc3\xef\x7f\\\xfc'\x00\x00\xff\xffo\x05\xb2\xa4Q#\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y_\x93۶\x11\x7fק\xd8q\x1e\xae\x991\xa9\xd8\xedt:z\xb3\xef\x9aε\xc9Yc\x9d\xfd\x92\xc9\x03D\xacHD$\x80\x02\xa0tj&߽\xb3\x00A\xf1\x9f\xa4\xd3M.\xe1\x8b}\xc0b\xf1\xc3\x0f\xfb\x0f\xab$IfL\x8b\xafh\xacPr\x01L\v|r(\xe9/\x9bn\xffaS\xa1\xe6\xbbw\xb3\xad\x90|\x01\xb7\xb5u\xaa\xfa\x8cV\xd5&\xc3;\xdc\b)\x9cPrV\xa1c\x9c9\xb6\x98\x010)\x95c4l\xe9O\x80LIgTY\xa2Ir\x94\xe9\xb6^\xe3\xba\x16%G\xe3\x95ǭwߥ\xefާ\xdf\xcd\x00$\xabp\x01Z\xf1\x9d*\xeb\n\rZ\xa7\f\xdat\x87%\x1a\x95\n5\xb3\x1a3R\x9e\x1bU\xeb\x05\x1c'\xc2\xe2f\xe3\x00z\xa9\xf8W\xaf\xe7s\xd0\xe3\xa7Ja\xdd\x7f&\xa7\x7f\x10\xd6y\x11]ֆ\x95\x138\xfc\xac\x152\xafKf\xc6\xf33\x00\x9b)\x8d\vx (\x9ae\xc8g\x00\xcd9=\xb4\x04\x18\xe7\x9e9V.\x8d\x90\x0e\xcd-\xa9\x88\x8c%\xc0\xd1fFh\xe7\x99i\xf5\x80ڀ+\x90\xb6\xf4\xac2!\x85\xcc\xfdP\x80\x00N\xc1\x1a\xa1A½2\x80_\xac\x92K\xe6\x8a\x05\xa4D\\\xaa\x15Oe\xd4\xd9\xc8\x04\xce\x1f\x06\xa3\xee@\xe7\xb0\xce\b\x99\x9fB\xf6;\x83\xea\xe1Y*\xfeL$\x8f\x05z\x99\x88\xa6֥b\x1c\rm^0\xc9K\x042Pp\x86I\xbbAs\x02E\\\xf6x\xd0}$_\xa2\xbe\xce\xcc5\xec\\CE\x90\xedm\xff\xb5;tiߥ\xe2\xcd\x02h\x8c\x1a\xacc\xae\xb6`\xeb\xac\x00f\xe1\x01\xf7\xf3{\xb94*7h\xed\x04\f/\x9e\xea\x82\xd9>\x8e\x95\x9fx]\x1c\x1be*\xe6\x16 \xa4\xfb\xfb\xdfNck\x16\xa5N9V~<8\xb4=\xa4\x8f\xc3ဖ\x9c-o\xae\xffO\x81\xbb&HwJ\xf6y\xfd8\x18\x9d\x02\xdbQ\x1a\xe3m\x9a\x19\xf4\xa1\xf6QTh\x1d\xabtO뇼\xaf\x8f3\x17\x06\xc2\xf4\xee]\beY\x81\x15[4\x92J\xa3\xfc\xb0\xbc\xff\xfa\xd7Uo\x18@\x1b\xa5\xd18\x11\xa3k\xf8:ɣ3\n}foHa\x90\x02NY\x03mp\x8a0\x86\xbc\xc1\x10\x9cEX0\xa8\rZ\x94!\x8f\xf4\x14\x03\t1\tj\xfd\vf.\x85\x15\x1aR\x03\xb6Pu\xe9#\xd0\x0e\x8d\x03\x83\x99ʥ\xf8_\xabے\xefѦ%s\u0604\xf8\xe3\xe7c\xb0d%\xecXY\xe3[`\x92C\xc5\x0e`\x90v\x81Zv\xf4y\x11\x9b\u008fd!Bn\xd4\x02\n\xe7\xb4]\xcc\xe7\xb9p1if\xaa\xaaj)\xdca\xee\xf3\x9fX\xd7N\x19;\xe7\xb8\xc3rnE\x9e0\x93\x15\xc2a\xe6j\x83s\xa6E\xe2\xa1K\x9f8ӊ\x7fc\x9a4kozXGN\x17>\x9f\xeb\xce\xdc\x00%;\x10\x16X\xb34\x9c\xe2Ht\fٟ\xff\xb9z\x84\xb8\xb5\xbf\x8c!\xfb\x9e\xf7\xe3B{\xbc\x02\"L\xc8\r\x05]\xbačQ\x95\u05c9\x92k%\xa4\xf3\x7fd\xa5@9\xa4\xdf\xd6\xebJ8\xba\xf7\xff\xd6h\x1d\xddU\n\xb7\xbe\x92\xa0xYk\xb2\\\x9e½\x84[Vay\xcb,\xbe\xfa\x05\x10\xd36!b\x9fw\x05\xdd\"h(\x1cX\xebL\xc4\n\xe6\xc4}\r\xab\x92\x95ƌ\xae\x8f\x18\xa4\xa5b#2\xef\x1b\x14~\x80\x8d\xe4Ӟ\xeaiץoͲm\xadWN\x19\x96\xe3\x0f*\xe8\x1c\n\r\xb0}\x9cZ\x13\xc1\xc9N\xce\v\xca\xc1\x06ɑR\x802.\xde\x17h\xb0\xbbƠVV8e\x0e\xa48d\xcbt\xa4\xe1\xc4E\xf8#+~\xe1\x18\x14\xee\xbdC\x18ܠA\x99a\x8c\x10\xe7*\x99\x89St\x12\xfa\x18\xe2i\xea\xe1L\xf4\x9c\x04\xfcay\x1f#fd\xb8\x81\xee\xc6\xfb^\xa0\x87\xbe\x8d\xc0\x92\xfb\x84ry\xef\x9b\xfbM\xd8\xcc\xc7\x0e\xa7\x80\x81\x16\x18*\xd26\x18\x83\x90\xd6!\xe3\xa06\x93\x1a\xe9m\x00\xe4`\x06\x9b\x15oC\xa4hB\xd21\x84\x13\xf5\xc0(F\t\x0e\xff^}z\x98\xffk\x8a\xf9\xf6\x14\xc0\xb2\f\xad\xf5\xf9\x1a+\x94\xeem\x9b\xb39Za\x90S\xe1\x82iŤؠui\xb3\a\x1a\xfb\xd3\xfb\x9f\xa7\xd9\x03\xf8^\x19\xc0'V\xe9\x12߂\b\x8c\xb7\xe1/ڌ\xb0\x81\x8eV#\xec\x85+\xc40i\xb5\f\x90u5\xc7\xde\xfb\xe3:\xb6EP\xcdqk\x84Rlq\x01o|%x\x84\xf9+9\xd6ooNh\xfdKp\xa07$\xf4&\x80k\xf3]\xd7#\x8f ]\xc1\x1c8#\xf2\x1c\x8f\x85\xe8\xf0\xf3\xc1\x9bBⷠ\f1 UG\x85WL\xb7\x17\xe2\x11\xf2\x11\xe8\x9f\xde\xff|\x12q\x9f/\x10\x92\xe3\x13\xbc\a!\x037Z\xf1oSx\xf4\xd6q\x90\x8e=\xd1NY\xa1,\x9ebV\xc9\xf2\x10\xaa\xfd\x1d\x82U\x15\xc2\x1e\xcb2\t\xf5\x06\x87=;\x10\v\xf1\xe2\xc8\xde\x18hf\xdcYk\x8dU\xc6㧻O\x8b\x80\x8c\f*\xf7\xf1\x8e\xb2\xd3FP\xd5@\xe5B\xc8y\xde\x1aGI3~\xb6\x0e\xe6\xe3\x14d\x05\x939\x86\xf3\"lj\xcaB\xe9\xcdK\xfcx\x9c\xfa\xe37Q\x02\f\x03ǟ\x96D\x9fy8_\xa9>\xe3pݷ\xd6\xd9\xc3m\xeb5\x1a\x89\x0e\xfd\xf9\xb8\xca,\x1d-C\xed\xec\\\xed\xd0\xec\x04\xee\xe7{e\xb6B\xe6\t\x99f\x12l\xc0\xce\xfd\x93y\xfe\x8d\xff\xe7\xc5g\xf1\xaf\xeb\xe7\x1e\xa8\xf7\xe8\x7f\xcdS\xd1>v\xfe\xa2C\xc5Z\xf1\xf9y\xecf\xd5\x140õ\xe4\x16\xfbBdE|\x0441\xf6\x843\t\xaa8y\b\xcdL\x1e^ݔ\x89\xd0\xda\x10\xa2C\xd2\xf4\xb4\x12&9\xfd\xdf\n\xebh\xfcE\f\xd6\xe2Y\xee\xfb\xe5\xfe\xee\x8f1\xf0Z\xbc\xc8WO\x14\xba\xe1{J\x8e\xb0\x92\x8a\xe9$H3\xa7*\x91\r\xa4\xa9\xf6\xbb\xe7D\xfcF\xa0\xb9P\xc5}\xee\t\xc7*t\xa2\x8ale\xae*#\xadd\xda\x16\xca\xdd\xdf]\xc0\xb1j\x05#\x86\xe3u5\xc5c\xd45h\x02]\x87\xc7\xfb\xcb\xc3\xe9@\xd2\a\u0557\x8eȔ\x11\xb9O[\xad\xef\xfbW\x84d\x15\xeb6\xff\xba_Ŵ\x162\xbf\nk\xb7\x97v\x01藎hDy\xa1\x9b\xe7\x8a)\x9c\xbd\x1e\xdf\x18-ʺ\x1aCI`\xab\xb4`\x13\xe3tG#\xfb\xa4\x897\xe3\xba\xe6\f\x13\xc1\x00.pд\x9e&\xdeQ\x8d\xfd\x84\xbaҏ\xd0\xdb\xc5[\xd1t@\xbe֮\xe8\xd9MEr\x1fa2\xfd:\x1c\xc8h\xc5gCҺ.9\x98<:\xd4p\xa2o\xab\x83\xd9^K\xb4{\x9a\xf1\xc3\xda\xf7ۮyZ\x87\x1e_\xc3{\x88\xf0.v\xfe\xe8y\xf3\xe2\xc7u\xa6\xe8\xe9\xd0k\xcf]\xb0\x81\xdb\xf1\n\xdf\xc92\xbc\xf1\tQ\xa1\x7f\xb1\x86\xf6\xe4\x9eٸ\xc9\xd4}CG_X\xea\xb3*\xa9C\xee\v{zwl\x98(\x91C\xfb+\x8bo\xa5[\xdfҹ\x99\xaac\xa3\xa2\xda\"\xf7qc\x02\xf4x]\xec\x92r\xe60!\x15#\tY\x97%[\x97\xb8\x00g\xea\xf1\xf4\x19\xf7\xaa\xd0Z\x96_\xf2\xaf\x1f\x83Tx\xf37K\x80\xadU\xed\xdaG\x7f\xe3h\r\x157\xb6\xb1\x82\xeb\x1a\x0f\x05\xb3\x97\xa0,If\xca\xe2Z\x97?orp&\x94=\xe0~btԵ\xeeN\xdeF\x13\x9a\x98\xfb\xde[\xc7U\x044\x1b]\xe2\xa0\x11\x83B\x95Ѻ\x95\xa3\xa4TWk4D\x84o\x95GFb\xe0\x98\xea\xa2\xf8\xd7בɣ\x86\x18\v\x83\xaa\xe6=\x991雊d\xbfN\x01\x17V\x97\xec0\xa17\x9e\xc4\x17Xd\xbe\xe4GG\x8b\x89^H\xee\xef\xe7\xae\xed\xfe\xb4?\x05L\x97\x7fS?,L\xddB\xf7W\x82\xc1|\xfb\x1b\xc8\xeb\xecp\xa6䳎\x19\xf7ܰ\xb7\xea\t_\x8ax^\xf5t\xbc놮q\xa0\xeao\xf3GƨI\xa2F\x83\x1e9\xef\xe8n:\xa7ݑz\xdd\xfe.\xb0\x80_\x7f\x9b\xfd?\x00\x00\xff\xffg\b\x17r\xc1\x1f\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc]O\x93\xdb:r\xbfϧ@9\a'U#y\x9d\\Rs\x9b\xf8\xd9Ye\xf7\xd9Sc\x97\xdf\x19\"[\x12vH\x80\x0f\x005VR\xf9\xee\xa9n\x80\xe0\x1f\x81$\xa8\x19\xbd\xf5\x06\xb7\xa1\x80\x06\xd0\xddht7~\xc0\xacV\xab\x1b^\x89\uf80dP\xf2\x8e\xf1J\xc0\x0f\v\x12\xff2\xeb\xa7\x7f7k\xa1\xde\x1d\xdf\xdf<\t\x99߱\x0f\xb5\xb1\xaa|\x04\xa3j\x9d\xc1/\xb0\x13RX\xa1\xe4M\t\x96\xe7\xdc\xf2\xbb\x1bƸ\x94\xcar\xfcl\xf0O\xc62%\xadVE\x01z\xb5\a\xb9~\xaa\xb7\xb0\xadE\x91\x83&\xe2M\xd7\xc7?\xad\xdf\xff\xeb\xfaO7\x8cI^\xc2\x1d\xd3`\xac\xd2`\xd6G(@\xab\xb5P7\xa6\x82\fi\ued6a\xab;\xd6\xfe\xe0\xda\xf8\xfe\xdcX\x1f]s\xfaR\bc\xff\xd2\xfd\xfaWa,\xfdR\x15\xb5\xe6E\xdb\x19}4B\xee\xeb\x82\xeb\xf0\xf9\x861\x93\xa9\n\xee\xd8g\xec\xa6\xe2\x19\xe47\x8c\xf9\xa1S\xb7+?\xea\xe3{G\";@\xc9\xddx\x18S\x15\xc8\xfb\x87\xcd\xf7\x7f\xfb\xda\xfb\xccX\x0e&Ӣ\xb2\xc4\x00?6&\f\xe3\xec;\xcd\r\a@\xbcf\xf6\xc0-\xd3Pi0 \xada\xf6\x00\x8cWU!2bu\xa0ȘڅV\x86\xed\xb4*[j[\x9e=\xd5\x15\xb3\x8aqf\xb9ރe\x7f\xa9\xb7\xa0%X0,+jcA\xaf\x03\xadJ\xab\n\xb4\x15\rc]\xe9\xa8K\xe7\xeb`.oq\xba\xae\x16\xcbQO\xc0\rٳ\fr\xcf!\x1c\xad=\b\xd3Nm8\x1d?%.\x99\xda\xfe\r2\xbbf_A#\x19f\x0e\xaa.rT\xaf#hdN\xa6\xf6R\xfcw\xa0mp\xa2\xd8i\xc1-xy\xb7EH\vZ\xf2\x82\x1dyQ\xc3-\xe32g%?1\r\xd8\v\xabe\x87\x1eU1k\xf6+\x89G\xee\xd4\x1d;X[\x99\xbbw\xef\xf6\xc26\xcb$SeYKaO\xefH\xe3Ŷ\xb6J\x9bw9\x1c\xa1xg\xc4~\xc5uv\x10\x162[kx\xc7+\xb1\xa2\xa1KZ*\xeb2\xff\xa7 \xb6\xb7\xbd\xb1\xda\x13j\x9e\xb1Z\xc8}\xe7\aR\xf3\t\t\xa0\xc2;]rM\xdd,ZF\xe3'\xe4\xce\xe3ǯߺz&̐\xfb\xc4\xf7\x8e\xf2\xb5\"@\x86\t\xb9\x03\xed\x84Hچ4A\xe6\x95\x12\xd2\xd2\x1fY!@\x0e\xd9o\xeam),\xca\xfd\xf7\x1a\f*\xb4Z\xb3\x0fd;\xd8\x16X]\xe5\xdcB\xbef\x1b\xc9>\xf0\x12\x8a\x0f\xdc\xc0\xd5\x05\x80\x9c6+dl\x9a\b\xbafoX\xd9q\xad\xf3Cc\xbcF\xe4\xe5W\xff\xd7\n\xb2ފ\xc1fb\xe7\x979\xdb)\xdd3\x0e\xd8d\xdd#\x1a_\xb4X\xdc\xeaG\v6\xfce0\x94\xff\b\x15Q\x7fp\x10\xb5\x14\xbf\xd7@&έX83)g$Y3>R\x8b\xf5\xd9\xef#<\xc5\x02?\xb2\xa2\xce!\x0f\xd6\xf6l.\x83\x11\x7f\xe7d\n?]I\xb1\x95#|\x8cX;\xfa\xb7\x13\x9b \xc9P͟\x0f\";8'\x00u\x93\xe8\xb0\\\x81!Á\x8e\xeail\x92lN\xf6\xbe\x93)\xd3і\x9955\xa4\x173'mI0\xb7m\x991\xbcg\x86\xc5\x7f\x8f\xee\x9cm\xf9\xff\xc9\xd8f'\xb9@i7gM_Wi)\xa8Bg\x7f\xb3cPV\xf6t˄m\xbe\xceQ\xe4E\xd1\xe9\xff\x1fX0\xcb5~3l\xf9\xaa\x1a?)\x959\x8a(\x95\xd0\xfd?\xa0Ph\xb3\xf8\xea\xf7\x8ad\x81\xfc\xb5\xdbꖉ]\x10H~\xcbv\xa2\xb0\xa0\a\x92y\xd1zy\rf\xa4\xecwXJn\xb3\xc3\xc7\x1f\xe8٘6ϔȗac\xe7\x1271B\x7fc\x9e\xa1\xcb(|\x15\x1aJ\x17\x16\x7f#n\xb6_(\x9e\xb8\xff\xfc\v\xe4S\xecai\x9aw6\x91\xfb\xc1`\xbb]{??u\x1a\xde\xf5\t1\x93Kx\xdc2Ξ\xe0\xe4<\x16.\x19\n\x87[\xf2w\xa3\xd1\xd39s(\xf3BJ\xf6\x04'\"\xe3S)\xb3\xadSU\xc1\x95'\x88\xb8\xfb\xb1\xd2c \x8e\xc9\a\xb8\x8e\x93\xf8\x81\x18A\x81w:\xf3\x18\xa5\xc5\x1a[4?9\x96nH\x9a\xd2\xf0\xfe\x82i\x06\xb1u҇$طƉ\bW\xc1AT\x89\x13\xa5\xec\xa1\x01Z-Mb\xec;/D\x1e:rz\xbf\x91\xe3\xdep\xbf|Vv#o]DfHK~Q`>+K_\xae\xc2N7\xf0\v\x98\xe9\x1a\xd2\xf2\x92\xcel#\x1f\xba\x19\xb6\x04\xe5ve\xe3\x12)A<°\x8d\xc4\xc0\xc5\xf3\x83\U000a5bbb\xe9\xfd\xa1_\xca\xdaP\nM*\xb9\xa2\xadr\x1d\xeb\xc91;\x91\xa4\xd2=\x89\x9c\x0f-t\xea:L$\xfb\rw\x12\xd7\xdee\x80\v\x9eA\xdeD\x9b\x94\xb7\xe4\x16\xf6\"c%\xe8\xfd\xd4\xc6\xd1-\x15\xda\xf7\xb4!$Z]W\x16jX\xda\xd6\xde\x14o\xba\xf3\xf9\xc1\xacp\xe5&\xd4j\x84=[u$]9^u~F\xb4Œ\xff1\xcb]\x9e\xe7t\x84ċ\x87\x05\x16\x7f\x81,\xce\xf7~70\xb7C\x96\xbc\xc2\xf5\xfb?\xb8͑B\xff/\xab\xb8\xd0\tk\xf8\x9e\x8e\x89\n\xe8\xb5\xf5\x89\xb1n7\u06030\f\xe5{\xe4\xc5y\"<29\x85\xb6\x05\n\xb7\x91\xabݙ\xc7r˞\x0fʸ=u'\xa0\x88\xa5l\xfaE\x18\xf6\xe6\tNon\xcf\xec\xc0\x9b\x8d|\xe36\xf8\xc5\xe6&x\vJ\x16'\xf6\x86ھy\x89\x13\x94\xa8\x89\x89\xd5~\xac\x9eBJnU\xf2j\xe5\xb5תRd\xa3\xedd4=ޖ\x9e:uS\xe4mnܻ\xc7S\xb3M\xd2\xdfJ\x19\xfb\xe7x\xa2od<\x0fM\x8b\xbeO\x1bɗ\xcd\xfa\xfa>\xf7\x15\x8c1z\x80;\v\xda'\xff\x9c\x81n\"\x87\x17\xc6Tsɽ\x90\xd8\xe3!!\x8b\f\x9e\xd1&wT\x922\xc4%\xde&\xf2e\xa1\x9f\xfe\xf1G'7\x89+\x1b\xff\xeeN䵽\xe1L\x95%\x1f\x1e\x0e&\r\xf5\x83k\xd9\xe8\xb4'䤯\xf75\xad\xe7t7\xb1\xd1!:\x16|\x16\xf6 $\xe3\x8d\xd9\x00\xed\x15\x8a\xb3J\xcd[0W\x0eܰ-\x80\f9\xf5\x9fa\x9f/\x85\xdcP\a\xec\xfd\xab\xfb\x05\xace\xd7E\xe2lX\x1d\x04\x1a>\xd0N\x95\xeaR\xa9\x9c=\x1f@CO+\xce\x13\xe5\xe8i&\x92\x94\xcav\xf3\x11H\xb7R\xf9[\xc3vB\x1b\xdb\x1dh\xaa\xc2\xd5&U\x1d\x16J\x18g\xf7M\x94\xa0j{\x81\f>\xb6\xad{\xe7\xba%\xff!ʺd\xbcTu\x82S\xe0\n\xee/\xa2\f\x87\xaf^\x02\xcf\\\xd8p\x0eE\x99\x19\xabPJU\x016U\xc4[ء9ʔ4\"\a݀\x03\x9cd\x85\u0085\xbb㢨c\xc7>\xb1\xb24\xbc\x95\x1f\xb5\xbe(\xba\xfd\xe2Zv\xb2\x8d\a\xf5\xdcgP2\v\x0e\xfc\bL옰\fd\x86r\x01\xedL6u\xe1\x99A\xacIV\xcb4\x03\x8f\x05d]\xa61`E+[\xc8\xc9dZ\xb7\xfa'.\x8ak\x88\r5\xef\x93ҏ\xc0\xf3K\x120\xbfu\x9a3\x90\xa6\xd6tp\xef\xcc˳(\xd2ƌ\x92c\x05\xafev\x00\xb2S\xb2g>\x98#/\xa4\xb1\xc0Su\x01\xbd\xa6ZJ!\xf7i\xb2KNq\xb6űz\xabT\x01|\bx\x8a\x15\xe4\xf5\xe5f跶\xf5\x1fb\x86\x82\x04\xd2݅-xQy[ĭ\x85\xb2r\xebM1]\xcb\xee\xees\x05+\xb4$\x06\xf7\xa3x\xcd\xe0ZH\x91 \xd8\xc1\x99\x8b\xb0]\xcf\x12I\\ճ\xc4\x0e\x82SqI\xfal\xd3#\x80\xab\xb3\tRh\xecAk\x16x\x99[`<\xcf!w\x89ItU|\xcc\xe2\xe0e#P\x85\xe8얻\x89I\x92mJ/\"\xa5T\xac>ª\x96OR=\xcb\x15E\xf2f\xb1\x01IO\r\xbej\xf7\xf6bK\xf4GZ\xa1\xbe\xbe\xa6\xebT\xe3<]\xc1\xca$\xeb͢lȔ\x16\xcc\xd95\a]\x1e\xf9qv\x14S\xfdO4\xf6\a\xcd\x1f\x1c\xe68\x15϶\x89\xb7\xea8\x7f\xcf\a\xb0\a\xd0\r\x98yE\xb8혝nϣ\xdb8&\x00\xdcP\x7f\x1aW\xd8\x01/\a\x90\xb7x\xa0\x83^\xc0-*6\xaf\v\xeb\xe0Ǻ\x8e(Q\x12\xf0+\xee\x19\xa4 '\xe6\xf0\x12}\f`\xc0+4 @\xd5t\x12\x99\xa1\x93\xa5C\xfav\x0f\xe3\xfb\xc0\aJ\xf95#\xfd\xbb\xc3\x03\x130\r3H\x86i\xd0\xe4\x14\xbf\xceզ˱V\a}=\x8f\xa6\xfd\xb9\xd8g\xa1\xfcR\xf9u0\xea\x80\xf69\x18i2\x80\x83\x90\xe5Ɛ\x9d\x80\x05\\Č\v\xaeB\x9f\x0eD\x8a\xf7\x19\xadD\xd5\x106\x94j\xf6\xabͣۅa\xef\xd9A\xd5\x11H\xdd\x04wf\x00\x16\xe3\xb0\n\x7f\x88\x00\x96\x1f߯\xfb\xbfX\xe5A\x16\x94\xf9\x8a̎\x02\x956\x9b*d.\x8e\"\xafy\xd1[d\x1d\xb5h\xb5\x87)ͤ(b竨VM\xfb\x9e\x1a\xb1/\x95;gYl\x8e\xa6]\xc44,\xc6\xc5\b\x8c>\xc2bd\x93Zz\xe4\x90\x0e5M\xc7XL\x83\"\x96 +\x86\xb8\x89Q\xa2\xf3x\x8a\x14\xef~\x06;q\x01b\"\x11-\xf7\xe2\x03\x92\x14L\xc4EH\x88Y@Y\"\xfe\xa1\x8fl\x98&\xb9\x00\xf5\x90Ĝy\x84\xc3b\\\x83\xc7\x11L\xce#\x19\xcd\x10\xc1)L\x12\x1e\xc50L\xa1\x13\xa6Y\x1eA.\xa4c\x12&I\x13^a\x1e\x89\xf0zx\xc3\u05c8\x02\xc6M\xcd,\x9a\xe0EQB\x02^`\tJ`\x96c\x17\"\x02\u0089\xffH\xbfKq\x00\xfds\xfe\x11\xa2)\xa7\xff#\xa7\xfb#\x14'\xcf\xfcS\xcf\xf4Gh\xcfl\xbb\x93Z2\xf9㒳\xfc\x10\x86\xfcʫJ\xc8\xfd\xb9\x9e\xa4jӤ&\x9d\x01\x01\xba}\xf6T\xa9\x1b-\xf4\xe2\xacX\x97\xeeVn$&k\xd2zBZ\xb5f\xf7\xf2tF\x97\xae\x04Dc\x90\xfe\xb5-\x1cֳ(\x8a\xee\xdd$\"\xdb%\xe5o\xf9\x99xf\x00+\x8ey\xd8Q\x11*\xdd\xf3\x8e\xe7B\xb0/\x83\xea\xddDᴷ\x1ds\xb4\x85=\\\xe8m\x97uaE\x15]\xf2\x95VGAi\xc7\x03\x9c\x02?\xff\xa6\xe8V\xd0\xf6D\x94\xbe<\x86ո\x1e\x04\x0e<\xb6\x86\x9e\xa1(\x187\xe7\xd3\xcf\xdc\xc5\xd8L\xad\xe8\xae\x1bJ\xb2\xd1\a\x7f\x81\xf6\x96Vl,b\x97͕\xcd\x12\xc9\xd0\xe5Z\x13Ɉ\x8c\xeeE\xd3\xfe\xb0s\xdd\xe9\xdb\xef5\xe8\x13SG:\xd3\xf7\x0e\xd2\f\xec\xde\xd9\x15\x83\xf1[c鼹tױ\aqBk_ؽt;v\x94\xec`\x8cD\aM\\\x1b\x1b\xa15ǰg\xa4j\x94\xaaT\xa1u\\\x1f&7\xa6T\xcc\xfau#\xa5\xe5\xb1Ҭ\x97r\x95x\xe9\xf2\x88i\x82d*\x06=\xedLd\x16s~\xad\xc8i.vJv\x1a\xd30\xe5\xd7\xc0\x92/\xc0\x90/\x88\xa1\x96EQ\xc9lJ\xc1\x8a_%\x96\xbab4u\x8dx겈j\x86\xe4\x00\x03\x9e\x82\xeeN:\xc6K>\xb3I9e\x9b?9\x9eFm'\xa0\xb5\x13N\x83\xe6F\x9a\x80\xca^\x86\xc6N\xe0\xe1\x95b\xad+E[\u05c8\xb7\xae\x1bq\xcd\xc6\\\xb3\x9a3\xf3\xf32\x14\xf5Ň\f\xcdq\xf4g\x95Ã\xd2v.@x\x18֏\x1c\x01v\x82&U\xe4L6Uc'\r\xe8\xfb{\xbf\xff\xb2I\xc5O\xeb\x1a\xf7\xf7W\x95\xe3\xd8\xe6\xce\x16\x1e\a\xd5Ϯ\xd0\xee@\x83t\x0fK\xfc\xd7\xd7/\x9f\x03\xfd\x98?\xea\x9d\xde\xc1\x9b\x06\xce\xc1\xc8=s\xfc\xe9\x93\a\xdc8n\xd1\x1e\xfeʇ\x04\xbc\x12\xffIov\xcd'd\xee\x1f6T\xb5\xf1\x96譯p\xa0\x1f\xce\u07b6\x80\xbbG\xe0Ȩ\xf6ov=\x8a\x11\xd8i\xf8\x93ыI\xcd\xee%\xc60Y\x0e\x84\x84\xab\xeea\xe3F\xb7f\x9f\xd0u\x93'\xa6\x9c\xe2\x1d\x84\xceW\x15\xd7\xf6D\xdaan\xc3\x18Ɠ2\xcd\x1e2\x95:\x195\xb5\xe7oAEy\xdb<\tE\ap\xa7\xaa\x7f\x9a9\xe4\xe8%\xe3\x18\xbf=1{o\xe2\x15\xc71\xbe\x1d\xaf\x88S\x91\xcfQ\x04ī\xa5\xa4\xbc\x19z\xf8>g\xd6\x1eC\xc5i{\x86\x91l\x93։\xf0\aۓI3\x92W\xe6\x10\xc9\n\xbd̦\xd1CU\x96\xdb:q>\xaenoJ\";t\f\xd034&Jw\x9e\xed\x1b\x8c\tת#D\xdb0\xb9\xd0R\x14\xb7\x9d\xc0\xfc\x8f9\xf2L|\x16\xe4\xe2\aA\x1c{FL\x05e\x9aЌ5\xba\xd0\xf2\xe5\x82\xc3\xceY\x17.\x01\xd8:\xedw&\xbe(q\xf1[\x12\xf3̊0j\xec\x19\x89\x94\xa7\"\xfe\xae\xfc\x9c0I&;@^\x17\x90\xf0\xc0\xdb\xd7N\xd5\xf9'\xde\x1a±5\xa9\xfa\x8f\xbc!_;\xdb+:\xbc\xfd\xc7\xe4<\xd3=\xe5\x11\x88w\x97\xa4\xf3\xecݫS\x19\xfa\xf1\xa6\xce20fW\x17\rZ&\xd3\xc0-\xe4M\xf5(2\xbf\x99\xc3\x02XH|\x17Yu\x9eѻI\x90\x8c\x89\x98\xc9\t\x13\x99\xf1\xca\x12\n\x9e\xbc\x8cZk\x9a\xb2\xfbM\xedΞ\xfe\xeb\x91\x1d7Z\x1e\xce\xe8\xc18\xc6\xf22\xe2\x89\r/\x82\r[\xd0\x03\x9b:\xef\xc0w\xba/\xa4\x05TN,\xad\xcdM@T\xe6\xeb\x0emG\x86\x9c\x1f$\r9\x83#H\xa6$\xdd5\x81|\n\xbe\xfb\x8d\xd2f\xfa\b\xfa\xad\tt\bP\x84\xbe\xe2W˵\rC?\u05c8\x9d\xd2%\xb7w,\xe7\x16V\xd8\xfa\xb2\x1d2\xfez\xa1\xd6\xf3'\x1ctiŇ\xc1tӄ\xc4[\x14\xfe\xaaI\t\xc6\xf0}\xe3\xbe?\x83\x06\xb6\a\x89,\x9ez\xa0\xad\xbd\xad\xe3Wp\xc0\x9d!\xb7xfk\xee;p;e8\xfb\x89\x9d\x1b\xb8W?)\"؏\xae\x1b!-\xec\xcfN]\xfcM\xa1G\xe0f\xf8J\xec\x19#>u\xeb\xfa\x9c\x99\xe3\x81{\x92\x84;\x90\x18=*jE\x88RF\xac\x11\xf6\xbc\b\xfaU\x1d\xb8\x993\x97\x0fX'\\\xa1\xeb,\xca`)\x1fG\xc6\x14\xbfҳb\x9f\xe19\xf2\xf5\x13)=\xe5A\xe3Ki\xc56\xf2A\xab\xbd\x06s\xae\xd2+\xba\xe3!\xe4\xfe\x93\xd2\x0fE\xbd\x172@\xf0\x96U~\xe0\xda\n^\x14'7\x9eH\xdb\x0f\xcdb\x8e\xfc6\xdfz\xe4\x87)!\xf99\xcf&\x05\\\xb56\xa7\"\xa4[\xe8t\x81m\xabj\xdb]\x15oM\xbb`b\x01\xb4\xa7\xb6f\x9f\x95\x85&W/\xfaD\x05\x06\xcfƮ`\xb7Sں\x1c\xcej\xc5\xc4\xce\x1b\xeaX\xae\x81\x8b\x82|\r\xf7\xc6-: \x01]\x12v>\x1fOjZ\x15䤔\xfc\xe4\xc2R\x9ee5ځw\xc6\xf2؆\xf6\"ז\x9c\x1b\xaf\xcd)\x11\xe5\xa6[?\x84tu\xb9\x05M\x97:\xf0g\xc7:\xba[\xe7LP\xf4\x9c\x92\xd1=\xae\xce\xd5^fp9\xc7\xd3jSƇ~W\x96\x17\x9bqG\xad\x7f!!T\x0e\xb11~9\x9fF\xef5\xcfq,\xa20MS\x94Yv\xe0r\x8f\xea\xa3U\xbd?4*8f\xa9\xc7Ҩ5\xe5|*Z\xa9\xa69\U000f2d56\x9d\x94\xad?\x05\xcb\xdb\xe1N\x11\x9dfᄟ\xa9[Dnk3\xee\xdd]\xad\x98\xceļ\x9d\x91\xc6#\xfc\x8f%\x94B\x13nN2\x9b\x86\t\xbb䑘\xb8\r4Ō\xe8|\x83\x05\xbcd\xbe\xa1q\xfa|[\xaf\xb78\xb5\xbeԒɏ\xfbٯ\xc0\x0eg\xd2/\xe1\x85k9\xb6\xf0h~\x91\x91/\x12\xb7\xcf6\x80D\a\x93\xc0 \xd1\xfb\x96\xe4t,\xe3\x85\xe9y\x99sAW\xaf\xf2˼i\xea\x18}\xe9\x9f\xd7\v>\x067\xe6c\x8a?\xfc}P}p\xe7\x02=㖢\xf7a#\xcc\xf9g\xb1k\xfe-¶\x80\x7f9\xab\xf1\aߝx\xe6Z\n\xb9\x9f\x9b\xfco\xbeZ$\x1c\xf0\x14\"\x01Ad\x12!DX\x14\x104\x83\x1cy\xf9;\x04\t/\b\t\xa2\xdb\xc9\xd9GR\xe4\xbc\xc3dߓ\xff\xf2\x7f\x01\x00\x00\xff\xffT\xf5\x7f\x80\xacd\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Mw$)rw\xfd\n\x9e|\x18\xdbOU=m_\xfctk\xab{\xecz;ӭ\xd7\xd2\xf6\xc9\x17*3J\xc5(\x13r\x81,uy\xdf\xfew\xbf\b \xbf*\xc9$K\x92gfW\\\xba\x95\x05A\x10\x11\xc4\a\x04\xb0Z\xad.x%\xbe\x816B\xc9k\xc6+\x01\xdf-H\xfcˬ\x1f\xffì\x85zwx\x7f\xf1(d~\xcdnjcU\xf9\x15\x8c\xaau\x06\x1fa'\xa4\xb0Bɋ\x12,Ϲ\xe5\xd7\x17\x8cq)\x95\xe5\xf8\xd9\xe0\x9f\x8ceJZ\xad\x8a\x02\xf4\xea\x01\xe4\xfa\xb1\xde¶\x16E\x0e\x9a\x80\x87\xae\x0f?\xae\xdf\xff\xdb\xfa\xc7\v\xc6$/ᚙl\x0fy]\x80Y\x1f\xa0\x00\xad\xd6B]\x98\n2\x04\xfa\xa0U]]\xb3\xf6\a\xd7\xc8w落\xf3\xed\xe9S!\x8c\xfdS\xef\xf3\xcf\xc2X\xfa\xa9*j͋N\x7f\xf4\xd5\b\xf9P\x17\\\xb7\xdf/\x183\x99\xaa\xe0\x9a}Ʈ*\x9eA~\xc1\x98ǟ\xba^1\x9e\xe7D\x11^\xdcj!-\xe8\x1bU\xd4e\xa0Ċ\xe5`2-*K#\xbe\xb3\xdcֆ\xa9\x1d\xb3{\xe8\xf6\x83\xe5W\xa3\xe4-\xb7\xfbk\xb66To]\xed\xb9\t\xbf:\x129\x00\xfe\x93=\"n\xc6j!\x1f\xc6z\xfb\xc0n\xb4\x92\f\xbeW\x1a\f\xa2\xccrb\xa0|`O{\x90\xcc*\xa6kI\xa8\xfc'\xcf\x1e\xebj\x04\x91\n\xb2\xf5\x00O\x8fI\xff\xe3\x1c.\xf7{`\x057\x96YQ\x02\xe3\xbeC\xf6\xc4\r\xe1\xb0S\x9aٽ0\xf34A =l\x1d:?\x0f?;\x84rn\xc1\xa3\xd3\x01\x15\x84w\x9di \xb9\xbd\x17%\x18\xcb\xcb>\xcc\x0f\x0f\x90\x00\x8cHT\xf1ڐp\xb4\xado\xbb\x9f\x1c\x80\xadR\x05py\xd1V:\xbcw\xb2\x97\xed\xa1\xe4\u05fe\xb2\xaa@~\xb8\xdd|\xfb\xf7\xbb\xdeg6\x90%O)&\f\xe3\xec\x1bM\f\xa6\xfdLev\xcf-Ӏ\x9c\ai\xb1F\xa5a\x15\xa8\x9b7 \x19S\x9aU\xa0\x85\xcaE\x16\xb8B\x8d\xcd^\xd5Eζ\x80\fZ7\r*\xad*\xd0V\x84\xa9\xe7JG\xa3t\xbe\x0e0\xfe\x01\a\xe5j9I\x04C\xc2\xe7'\x14\xe4\x9e\x0en~\b\xd3\xe2OL\xea\x01fX\x89K\xa6\xb6\xbfBf\xd7\xec\x0e4\x82\tXgJ\x1e@#\x052\xf5 \xc5\xff6\xb0\rJ\xbd%a\xb4\xe0\xf5A[h\x02K^\xb0\x03/j\xb8b\\\xe6\xac\xe4G\xa6\x01{a\xb5\xec\xc0\xa3*f\xcd~Q\x1a\x98\x90;u\xcd\xf6\xd6V\xe6\xfaݻ\aa\x83&\xcdTY\xd6R\xd8\xe3;R\x8ab[[\xa5ͻ\x1c\x0eP\xbc3\xe2a\xc5u\xb6\x17\x162[kx\xc7+\xb1\"\xd4%i\xd3u\x99\xffS\xe0\xa8\xf9\xa1\x87\xeb\xc9|s\x85\x14\xe1\x04\aP#:\x81qM\xdd(ZB\xe3'\xa4\xce\xd7Ow\xf7]a\x12fH}\xa2{G\xc2Z\x16 \xc1\x84܁\x9f\xd1;\xadJ\x82\t2\xaf\x94\x90\x96\xfe\xc8\n\x01rH~SoKa\x91\xef\x7f\xa9\xc1X\xe4՚ݐyA9\xac+\x9c\x81\xf9\x9am$\xbb\xe1%\x147\xdc\xc0\xab3\x00)mVH\xd84\x16t-㰲\xa3Z\xe7\x87`\xde\"\xfc\ns\xfc\xae\x82\xac7e\xb0\x9d؉\x8c&\x06i\xcfF\x05\f4\xa8+㳖~!55\xfc:\xc0\xc3\xe9\xb2\xd0+\x18\xb4\x1fvO\x1cn\xcd\x18ʕ\x83\x86:E\xaa!wǴ`\x87\x12\x1e\xca\f&}\xad\x97j\xdfN`2\xaf\xea\xd6\x11\x1cO\xb8J?AY\xa1ژA\xf1\xdeWC\x14\x91>y\xe35\x05\xc3\x1fԬ\xf2ڕ\x9d(7\xean\x0fȷ\x83Ƚ\xf6:\xe1*\x9b\xe4,\x96̈;\xc9+\xb3W\x16m\x9c\xaa\xedX\xad\xc1\x00n\xee6\x83F\x1d\xce#VdÉ\xd1V\xb1'.N9\xed\n\xca\xe5\xcd݆}C\x97\b\x02L\xe6,9\xb3\xb5\x96\xa4\x8e\xbf\x02Ϗ\xf7\xea\xcf\x06X^\x93V\nv\xf9*\x02x\v;\x9c\xf4\x1a\x10\x066\x00\xadq\x0e\x18BM\xd5vM\x0eG\x0e;^\x17\xd6+9a\xd8\xfb\x1fY)dm\xe1\x94\xefl\x9a\xf7D$ny\xa9\x0e\xa0\x13h\xf8\x91[\xfe\v\xd6\x1d\x90\x0ea0\x02\xe2\xd9Od\xdc\x1e#\x03\xc5&['\xa9l\xb3\xeb@\x15\x86]^\xe2<\xbbt.\xf1啫[\x8b®\x84\xa4~\"0]\xefO\xa2(B\xff\xe7Q\xc3\x11\xd7\xf1\xd6ܫ\x9f\x8c\x13\xeb\x14\xe2D\x9a\x8e(\x98J\xe5\xec@\xf5b2&\n`\xe6h,\x94\x9eR\x1dυ\x88Kڱ(<\x18öǀ\xfb\xf8\xb8e]\x14|[\xc05\xb3\xba\x1e\xefvJ\x91\x8d\xd1\xe6+\x18+\xb2\x04\xca\\\x0eI\xe3Z\x8e\x10F\xd3\x0f\x11\xa2\f(\x80.\x0f\x7fD\xb7\xdbS\b}\xa7\xa2\xe8\x10w\x9e*\x8c\xfd\x8fd\x1f\xd1\xdcgh\x84\xaf\xbdq\x17P\x90C!\x15+\x94|\x00\xedzD\xc7)H\x98\x06\x94\xb8<\x02\x15-\xad\x86\x02]\x06\xb6\xab\xd1\b\xaf\x19j\x82\xa8\x8c\bi,\xf0|}\xf9Z̃\xefYQ\xe7\x90\xdf\x14\xb5\xb1\xa0\xef0\x04\xccC\b<\xaae\aL\xfc4\t\xc0\xbb_\x85\xc8\x00\xf9\x90\xb9J+\x8a4cDj=\xb1c\x05.\xf0E\xa6zL[\x17\xab\xa3*\fX\xacr\xf9\xaf\x971%\x8a\x12\xd0\xef\xbdߏa\\CC\x8d\x9eF\x8d@l\xf4,\x94\x95=\x8eˑ\xb0PF\x888\xabr\x16\xb0\x97k\xcdǔj\x18N\x13џ\xcf\xde\x18\x88\x01\x83e\xa8\xf6\x1b\xb1x\xd8\xff?\"\x93\xcfb\xab\xa1u,.$\xb2\xb3\x10\xc6\xf6\xb89\f\x88\x1a\xcc0vF\x9ab\xd0\"\xa4\x83\x89ʭü\xdf3\xcdΙ\t1\xd1o$͋\xf3\x9eDŽ\xea\x0fH\xb0\xbdR\x8f)D\xfao\xac\xd7\x06\xca,\xa3%U\xb6\x85=?\b\xa5\xcdp\xb5\x05\xbeCVۨ\x9e\xe0\x96\xe5b\xb7\x03\x8d\xb0h\x81\xb0YO\x9c\"\xd6t\x98\xc0:\n(Za0\xae\x96\xe9\xc8<\xa2Fl(\x14\x8eE\xa12B\x1c\xbdx\xb2\xee\xb98\x88\xbc\xe6\x05\x19z.37>\xde\xe0\x17sOf\x04\xe2\x04\x7f\xe7N\x84Q \x97zQ\xb6\x92\x80\xeeu\xa9t\xcc\xf3t\xe5\x14L\x9c\f[N\xc1q,$m\x8b\xae\v0\x1e\x15\xe7\xc0\xb6z\xe7\xaa\xe5\x94[\xa0*\xf8\x16\nf\xa0\x80\xcc*\x1d'O\x8a\x10\xb8\x92\xaa?#\x94\x1dѤ\xfd hV\x89\xb6\x05\x03̽\xc8\xf6\xce\xddD)#X,W`Hc\xf0\xaa*\"V\xa8-\xb3\x92\xe1;\x9bS\x1amIP\x1fC\xb81EҖD\x1dܖ\x19mܧz#6oD\xef\xa1)\x9f%웓\xe6//\xecHn\x01\x86\x9c>\U000bab98\xb0\xe1k\nԞ\x1fh\xfe\xce\x18w\xdel\xd9\f[\xbf\xf8ly\x11\xae5h\xfc\x9d0\x8d\x8c՝\xb7U\x8b\x18\xf6s\xb7\xe5\x15\x13\xbb\x86a\xf9\x15ۉ\xc2\x02\xf9Rs\x88v\x1c\x9dYν$\x81Rm/\x96\x92\xdbl\xff\xa9Y\xd6Nh1\xa0\xd5\x10\x80\xf3\xcbC\fC\f<\x9d.\n4\xc0$\x90\x9dA\x91\x9b\xd6\xc4xn?\xef\x8aq\xf6\bG\xe7Y\x8d.\x0f\x8d\x15d-o@j\xa0\xcdER#\x8fp$P~\xb70\t\xde\x12Qq\xe5\x11\x8e\xa9U\aDE\xfc\xfc>\x85\xa3.~\xa0Q\xa4L\xa5\xb64D\xf5s\x87Y\x956X\xb6L)\x85\x12(~\xe6\xb0\x1b\x86\xf5\xb6\xc8\x1f\xe1\xf8\x83q\xec\xc3Y\xb3\x17\xd5\x02\n\xa0¦%\x19\xb5k\xf6\x86\xbf\xf1B\xe4Mg4O\x16@\xdc\xc8+\xf6YY\xfc\xe7\xd3wa\x10E\x99\xb3\x8f\n\xccge\xe9˫\x92\xd8\r\xe2L\x02\xbb\xc64-\xa53\vH\x97E\xfd\xb78\x90\tE\x11m\xd8&\f\xdbH\x8c\xcf\x1c}\x96\xb0i\x0f\x019\x87VY\x1b\xda]\x96J\xaeܒ\x96\xefm\x01\xd0.^\x9eUJ\xf78u\xb5\x10\xe2(\x8a\x1e\xbd{\xb4V\ue5d3}\xf9\xa9\xa2\xa1*x\x06y\xd8e\xa3$\x00n\xe1Ad\xac\x04\xfd\x00\xacB\xbb\x91.T\v4\xb9+gHa\xbak\x11\x8a7\v#{\xdace\x85\xb3>\xb1f`sR\xf5Ȏ\xfft\xf5\xb4Q\x92y'\x7f(\x89\xfa\xdd\x14\xb5e\x96e!\xbfN}\x10\x87\xa4s?JN\x1bO\x7fE\xf3J\xe2\xfd\xb74kȅ6k\xf6\x81\x12\xf4\n\xe8\xb6\x0f\xab\x84\x9d\xae\x92@\"&\xc20\x94\x93\x03/\xd0}@\xe5-\x19\x14ΙP\xbb\x13\x0f*M\xc5<\xed\x95q6\xbf\xd9\x18\xbb|\x84\xa3ߜ\xedj\x89ˍ\x8c\xae\xda\xf7\v\xea\xfc\x13\xa5\xd5x-J\x16GvI\xbf]\x92c\xb6d\x8a\x9c\xe1\xbc-\x90\xea\x05U\xbf\xaf\x1e\xeb-h\t\x16̪\xe4\xd5\xca\xcf\x06\xab\xca\xe8\x1e\xa7+\x94F\xb7$\x8c\xc08=x<ظI6C\xf7\x7f\x8e\x02\xc9\xf3\xa1R&\x92i\x11A\xebV\x19\xeb\x16\x0f{\xae\xfa\xc8\xeabJ\xe4\xe8W\x1c\x19\xdfY\xd0\xccX\xa5Cb\x17\xaa\xec\xc1\xe2:J\x8d\x99\x97\x1b\xb7O\xe4W2\x1d`\fP/[\xed\xe2\xec\xc1\xa5۫\xc2\xff\xcf\xc3\xcc\xc8\xd1\"ؕV\x19\x98h6B[\x12\xad\xce\xccbo\xb3\xd0\xcb]\xe0\xb7KR\xeb)\xcbС,s㑴g\x04E\x9f\xbew֬Q\x85\xe1\xdf)\xa2|\x0e\x8e\x8cr\xbb˒\x0f\x93\f\x93ѽq\xad\xc3\x04\xf4\xc0\\\xb0\xa5\x1fjRH\xcb|n/\x92\xbf7\xa7\xa5\x14rC\x1d\xb1\xf7\xaf\xe6\xe8\xb0`\x06b\x19Ice\xc0\x0e߾eH\xf3!5\xf6e!UM\xd1>\x8f\x86\x1egOwA\xd29\xc5\xd0\x11\x97\xcav\x17z|O?\x18\xb6\x13\xda\xd8\x16\xe1\x05P\x85\x99\xc8z\x1a\x1d\xde\x19\xf1\xa9\xfc\xa4\xf5\xd9\xe1\xe9\x17\u05fa\xb3$\xb9WO>\xc1sIP\x1e\x88\xbf\xe7\a`bDŽe 3UKZ,Cu\x81\xdd,\x80\xe8\x98\xe8\x8cI\xa2\xcd\xec4\x96u\x99N\x90\x15I\xa7\x90\xb3+k\xdd&?q\x91\xb6\xb2\xc5\xcec\xab\x9dJ\xa2\x1c+\xfd\xccP\x9fM\xd9\xcd\xe4-\xf9wQ\xd6%\xe3%\xb2eI̹sy\x98!\xed\xd7\xf1\xfa\x89\v\xebOS\xb8M\xd9e\xda4SeU\x80\x85\x90a\x99)iD\x0e\x8d\xfb\xe0\xf9?\x9a\xaf\x1a+\x9c\xed\xb8(j\xbd@G/\xe6\xccҘϫ\xa7\x97\x0f\xe4\xd2\x11Y\x111\x13\x17\xec\x178\xdc\xf3\xf6\xa3\xd2\xcb\\\xe6[\r/\xef\x9aVZ(ʁ\x9d\xf1Nga\x92\xf7\xda\xf7N\xbd\xf0ry\x8c\xb9\xa7\xb3P\t\x937\xf7\xb4)o\xee\xe9\x9b{\xfa\xe6\x9e\x0eʛ{\xfa枾\xb9\xa7\xe3\xe5\xcd=\xed\x947\xf74\xd9~\xa4`\xb8\xa2\x95ۉ\nIX%\xa6o̡=ӗ\xcfR\xf2gA\x96dWo\xc6[\x8e\x9c\x05Zt\x86\xc4t\x8c^\x93n\x8dS2L&w\xa64\xc1\v\x7f\x81\xb36\x01\x81\xb3\xcf\xdal&\x01\xbc\xe0Y\x1b\x8f\xe9p\xed\xfc\x05O\xda\x04Z,?\x84q\xe5ӘJ\xe0aK\xc8\xe5\xa0\xe4\xb1nc^l\x0f\x8f\xd1:\xbfq\xd6\xfdI\xb6\xe6\xf9\"\xf3\xffr~'\"6'\xa7S#P\x85A\xb9\xfacp\xe2,\xdaG\xa9\xed\xfe\x17\x1b]KX\xa7x\xdd5\x03\xddT\xcb~\xca\xeb\x1fG\xb0ϑ\xe4\xd4\xf371\xe7<\xae\xdb:\xc4Խ\xf3\x1e\xbfoZZ(\xbfTޒ\xa5\x9f{ߌ4{\xc6\xc9wn\x8e2\xdbk%Um\xfc\n\x0f\xf6\xf0!sW\x01\x84\x8e\xcc\x12e\xf0\x9e\xedU\x1d9\xe31Cׄ\xcc\xdbx\xbe\xad\xcf\xe0\x00\xcb\x0f\xef\xd7\xfd_\xac\xf2ٷ\x11\xac\x9f\x84ݻ\xfb\x18x\x9e\xa3\xa3\xde9\xe2\x13&\xaf\xbf\x93e(x\x11\x88J3)\n'\x95\x01B߀~\xa9ܒ\xdf\xd9~\xcb\xfc\xc2Sz\x8e\xee\xd2\xcc\xdc&\x97r\xdeK~F>\xee\xb2\xc3R\xb3\xb9\xb7)H\xb3\x94\x8c\xdb\xf1\\\xda\x19\xa8K\xf2lS\xd7\x14\x13rj\xd33i\xd3\xc8\xc3\xe8&\xa5\xd4\xfc\xd9\xe4(45W\xf6u2d\x13\xf3b;ٮ\xb3 \xcf̆M&XZ\xe6kr\xbek'\x8bu\x9eZ\x13Y\xae㹫\xb3 \xc7r[S2V\x93pM\xceSm\xb2O\xe7wF\x9e\x95\x9d\xfa\xf2\xe7`^r\xddb:\xd74)\xc34imc\x1e\xe7\xa4\x1cҥ\x99\xa3IT]\x9a%\xdad\x80Nt\x9c\x94\x1bz\x9a\xf795\x94ٌ\xd0x\xb6\xe7\x14ر<Є\x1c\xcf\t\x90\xdd\xec\xcf\xc5n\xc0\xac4\xcdVX\x9a\xbb9~?Z(\xf3ֹ\xf8-d\xf6\xb9dR\xba\xe74\xa7\x04w_\x06MPZ\x82\x9f8\xe6\x88\xc7Ce瞟\xe1\x88G@nv\xac\xac\v+\xaa\xa2sA\x99\xddñ\xb9\xf2\xe7WE\a\u05f7G\x82\xf6\xe5k#\xf21\x90\xfd\x90\x82\x1b\xf6\x04E\x81\xff\x9eP!s\xd7\x01fj\x05h\xa5\xe2\x1b\x81\xfe\xaa#\x7f\x97\xe0\x95[\x16\xa3S\xfdd\x01K\x844}\x01֤)\x99v\x8f\x9dWO\xdf\xfeR\x83>2\xbas+\xf8AQ1kO{\xfa\xc9l0&\f\xca\xc7k1w)e_\x19\xc5gC\xa3\x02\xd8\a\xe9\f\xf3\x10W\x82\x85Z\xa7\r\xa7\xa6\x94-FO1\x10R5\x10\"\xedS\xbc\xef%\xc7\x1f_#\xb8z\x89\xf0*\xc9\x11y\x8d\x10뵂\xac\xa5a֒䍤㋯\x11l-\t\xb7\x16\xf9\x8c\xe9\xc7\x13_\xebX\xe2+\x84]g\a^\x8bH\x97z\xecpq\xf8\x950\xbe\x99c\x86'>Z\x02\xc8\xe8\xf1\xc2\xf1\x10,\x01\xe2ɱ\xc2\xd9 ,e\x1e\fôg\x1f\x12LNdZ\xb4\x9b\x9e\x9a\x84\x94\xb6\xd1=\x7f\xf8/\xf1\xd0_\xe26x\n\xf6\x89\x87\xfb\x96\x1f\xeaK\xa4\xf3\x99\xe1\xd9d\u05c9\x87\xf7\x16\x05hg\x86h\x93\x10\xa7\x0e\xebM\ai\xd3\vp\xc3Czg\xb8\x13\t\x12\x96Pe\xf9A\xbbgo\xc6(\x9d\x83\x9e\xdd\xd7Z\"γ\x82<\x88\xa3\xfa\xfd\x0fvt\u008d\xa8X\xab\xbbg\x16\xe3\xa8j\xee\x1d\xc9؟\x84\xf4\xbb\xf5(\xb8\x1d\x9f\xa4\xb7\xf1\xd6:L\xf1}\x9d\xd6K\xf5\x17\xab\xbb\x1d;\x03\x15״\x8f\xbf=\xba\xa4 \xb3f\x9fx\xb6oz\x88\x80\xa4~\xf7ܰ\x9d\xd2%\xb7\xec\xb2\xd9\n}\xe7:\xc0\xbf/\u05cc\xfd\xa4\x9a\xf4\x91νb\x11\xa8F\x94UqĈ\x89]v\xc1ti^\xd6ɂ\x88\xc5(\xd8\\\xf8\v\v\a\x97\n\xefTQ\xa8\xa73\x97.x%\xfe\x8b\xde1I[\x1b\xfbp\xbb\xa1\xeaA\xaa\xe8\r\x94&{\xae\x91\xb1-L+\xf4v\xe0\xe4zt\xa1\x8ed\xaf6\x7fN@\xa4W\x04\x82\x9f\xe1\xd5x\xa6P\x8b\xddn\x1c\x96k\x12,.\x8fL\xf9{\xe2\x85\xceW\x15\xd7\xd1M=\xe6\xe5\xc1\\\xf50\fv|n\x05kҬ\x9d\xbe\x8a\xd0-=\x9a\x87\a\x12h\xb3\xf7X\xf5\xb7щ\xd2\x1dz>\a\xa7\xe9\x83˳G\x96_\x01\xa7i\x97iET\x8c\xfc\x14M\xc7{\xf1\xd5C\xe3/\x91\xffE\x1d\xe0ct\x15\xb1\xffd\xc0\xa0\xc9H\x02]\x80:umz\x9b5\x17\xbf\xce\xfa\x052\xe2\x02*\xfe\xe2\xeb\x05\xe3\xf3-\xc6^i\xf0\xf7\x7f\a\xd8\x13\xb6\r\xa7\xec\xed7\n7\x1bu\xe9g\xb8\x0f&\xc3R\xe1\xe0\xaa\xd6\b\xc8\xd83\v/E-\xab4\x7f\x80\x9f\x95{\t#\x85Z\xfd\x16\xbd\xc7P\xbc3\x17\xb2\x89\xfd\\\x8b)s?\xb6!\xc0\xf6\x90\xc1\xc9u\xf4\x88홷\xed[[$\f\xee\xfe\xfeg7 +JX\x7f\xac]\x86\t\xea]\x03H\xe90P\xd7h\x1b\xd7N{\xf5D\xf7\xb9w\x9f\xab\xe8<\b\x04t\xa8\x81\xd2F\xcf\x1aM]\x15\x8a\xe7\xa0o\x94܉\x87\x84\x81\xfd\xb9\xd7``\xd93\xfa\xe8\a\x1b\xeccd`\xa1\xe7W\xcc\fA\x97\xad(\xa0\xf8I\x14`\x1c≊\xfe\xf6\xb4e\xa3\xf7\xebr\xeb\x1c\xd4\x1d\xfe\xd8t2aM\xddPiM\xbf\x02\x8d\x8e\xa0[\xfd\xafM\x10\xf0ib\xb0\x86\x8fBZx\x88$\xf8\xcch\xf8C\xef\xe9\x8f0IR\xd4ڷ\xf1\x96\x1do\xb93]\xa7\x92\x05\xd5.\n\x8b\x1b\xa32A\x0e6\xed\x8eЩ\x91\u05fbsz*P\x9a\xa0cm\xe0˓\x04\xfd5\xa8d\xb3\x91\xb1\xb76\xfa\xb3\xe6\xa4a\xf4\x9d\r\xabȭ\x1fT\x1f\v\xe7\xa4'\x90q\xaf\xb4\x84m\x1ea\x9a\xb7yNI73\xaf\xe2Z~\xdc'Y\x8d?\x87\xb3j^\xe8\xb9H\xa0\xac{\x85\xa6\x0fx\xfc\xf1%\xf7\\M\xc6+[\xeb\xa0rjM\x17p#\x10p\xf7S\x9f\xf7\xfcR\xfb\x9c\xdd\f/\xdb\a\xeeڅ\xef\xd9\xe7\xf4F\xf8\xd7<\xa0\x14}Qȅ\x8c\uee7b\x15\xc2?\x8f\x9d\xa3\xf3\x80.,\x9f\x19\xe9-\xd6iN\xfcyBS\xc3p\xd1\xf9]\f\xf5\xf1#\\+\xf6\x19NC\xb6\x15\xfb$q\x10\xa7\x9e\x9c;\xa7\x059-\x97\x8f==79\xc4Cӊ\x0eɍh\x8b\xbe\x9a\x1bT\x1f\xa4\xf0\xd2\xc35M\x15w n\x8c\xad\xff,vn/#\xc31\xfd\xcbI\x8d\xa8\xe2\x9aTZ1\x855:\xa5N>\x1a\xd0\az)&\b\x89\xf7ֺ_\xeam{\xef<\xfb\xeb\xdf.\xfe/\x00\x00\xff\xff\x89~\x02\xaf\x9ft\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b7\xf2\xbf\xebSt9\a\xfdSe\x0ec\xff\xb7\xb6\xb6x\xb3\xe5͖v\x13Yeʾ\xa4r\x00\a=3\x88f\x00\x04\xc0P\xe2\xa6\xf2ݷ\x1a\x0fr\x1e )\xa9\xd6\u07b9H\x04\x1a\x8d\x1f\x1a\xfd\xc6b\xb1\xb8`Z|Ac\x85\x92+`Z\xe0\xa3CI\xbflq\xff7[\b\xb5ܾ\xb9\xb8\x17\x92\xafષNu\x9fЪޔ\xf8\x01+!\x85\x13J^t\xe8\x18g\x8e\xad.\x00\x98\x94\xca1\x1a\xb6\xf4\x13\xa0T\xd2\x19նh\x165\xca\xe2\xbe\xdf\xe0\xa6\x17-G㙧\xad\xb7?\x14o\xde\x16?\\\x00H\xd6\xe1\n\xb4\xe2[\xd5\xf6\x1dnXy\xdfk[l\xb1E\xa3\n\xa1.\xacƒx\xd7F\xf5z\x05\x87\x89\xb06\xee\x1b0\xdf*\xfeųy\xef\xd9\xf8\x99VX\xf7\xaf\xdc\xecO\xc2:O\xa1\xdbްv\x0e\xc2OZ!\xeb\xbeef6}\x01`K\xa5q\x057\x04C\xb3\x12\xf9\x05@<\xa2\x87\xb5\x00ƹ\x17\x1ako\x8d\x90\x0e\xcd\x15qH\xc2Z\x00G[\x1a\xa1\x9d\x17ʭ\xe2\x10\x00B@\b\xd61\xd7[\xb0}\xd9\x00\xb3p\x83\x0f\xcbkykTm\xd0\x06x\x00\xbfY%o\x99kVP\x04\xf2B7\xccb\x9c\r\xe2]\xfb\x898\xe4v\x04\xda:#d\x9d\x83q':\x84\x87\x06%\xb8FX\b\xa7\x85\af\t\x8eq\xfe\x94\xf9\x8d\xfd<-\xb7\x8euz\x84\xe0\xca ;,\r\x108s\x98\x03\xb0\x97'\xa8\n\\\x83$y\xafXLH!k?\x14n\x02\x9c\x82\rz\x88ȡ\xd7\x19d\x1a\xcbB+^\xc8\xc4t\x04\xebf2zN6D\xff\xdfF5\x02t\xab\xf8\v\xa0\x98\x9d\xb8\x01\ng ,\xb0\xb84\x9c\xe2 \xe8\xe4\x8e>\xfd}}\aik\x7f\x19S\xe9{\xb9\x1f\x16\xda\xc3\x15\x90\xc0\x84\xacȬ\xe9\x12+\xa3:\xcf\x13%\xd7JH\xe7\x7f\x94\xad@9\x15\xbf\xed7\x9dpt\xef\xbf\xf7h\x1d\xddU\x01W>S \xf7\xd4k\xd2\\^\xc0\xb5\x84+\xd6a{\xc5,~\xf5\v I\xdb\x05\t\xf6iW0Lr\xa6\xc4Aj\x83\x89\x94\xa2\x1c\xb9\xafIޱ\xd6X\xd2\xed\x91\x00i\xa5\xa8D\xf4P\x952\xc0\xa6\xe4ňq\xdep\xe9\xcbz\xa7)\xd1\x04\xd9\xfbܚ\x84M\x0e|jr\x98\x81r\xc6\x14\xa0\x9dz\xd9\xfd\x1a\x83ZY\xe1\x94\xd9\x11\xe3\xe0`\x8b\x19\x87#\xd7@\x9fT\x1cϜ\xe3Fq\xcc\xc1\xa6\xa5\xe0\x1a\x16\xb4\x95\xf2+\xf2G\xbd\x94\xf3]\xe8S\xf2Y\xc0\xb4\xe2gp\xc5\x1d\x19\x18\xacР,19\xaeS\xc9C\x06\xd90\xac\xcf1\x1eW\n8\xe1ճ\x88\xdf\xdd^'O\x9e\x84\x18\xb1\xbb\xf9\xbeg\xe4C_%\xb0\xe5>Н\xdf\xfb\xf2\xba\n\x9by\x9f\xe6\x140\xd0\x02C\x1a\xb8\x0f\x12 \xa4u\xc88\xa8*ˑj\x12 \xc37\x18W\xbc\x0e\x1e,\xba\xcaCh!\xd9\x03#\xdf)8\xfcs\xfd\xf1f\xf9\x8f\x9c\xe8\xf7\xa7\x00V\x96h}\x16\xec\xb0C\xe9^\xef\x13s\x8eV\x18\xe4\x94fc\xd11)*\xb4\xae\x88{\xa0\xb1\xbf\xbc\xfd5/=\x80\x1f\x95\x01|d\x9dn\xf15\x88 \xf1\xbd[NJ#l\x10Ǟ#<\b\u05c8i0\xddK\x80\xd4+\x1e\xfb\xc1\x1fױ{\x04\x15\x8f\xdb#\xb4\xe2\x1eW\xf0ʧ5\a\x98\x7f\x90\xed\xfc\xf9\xea\b\xd7\xff\v\xa6\xfd\x8a\x88^\x05p\xfb8<4\xba\x03\xc8`yF\xd45\x1e\xb2\xaa\xe9\xe7\x83\n\xb9\xea\xefA\x19\x92\x80T\x03\x16\x9e1\xdd^p\x94\xc8g\xa0\x7fy\xfb\xebQ\xc4cy\x81\x90\x1c\x1f\xe1-\x88X\xdahſ/\xe0\xcek\xc7N:\xf6H;\x95\x8d\xb2xL\xb2J\xb6\xbb\x90\xe7n\x11\xac\xa2B\t\xdbv\x11\xf2 \x0e\x0flGRH\x17G\xfa\xc6@3\xe3Njk\xca~\xee>~\xf8\xb8\n\xc8H\xa1j\xef\x89)jV\x82\xb2\x19JcB,\xf6\xda8\v\xe6\xe9\xb3}P\x1f\xa7\xa0l\x98\xac1\x9c\x17\xa1\xea):\x16\x97/\xb1\xe3yJ\x92\xbeLj2u\x1c\xff\xb3\xe0\xfe\xc4\xc3\xf9\f\xfa\t\x87\x1bV\x19'\x0fw\xdfo\xd0Ht\xe8\xcf\xc7Ui\xe9h%jg\x97j\x8bf+\xf0a\xf9\xa0̽\x90\xf5\x82Ts\x11t\xc0.}\x99\xba\xfc\xce\xffy\xf1Y|E\xfb\xd4\x03\x8d*\xed\xafy*\xda\xc7._t\xa8\x94\xc3>=\x8e]\xaecf5]Kf\xf1Ј\xb2I\xc5I\xf4\xb1G\x8cIP&̃kfr\xf7\xd5U\x99\x04\xda\x1bB\xb4[\xc4^ڂIN\xff[a\x1d\x8d\xbfH\x82\xbdx\x92\xf9~\xbe\xfe\xf0m\x14\xbc\x17/\xb2\xd5#\tx\xf8\x1e\x17\aX\x8b\x8e\xe9E\xa0fNu\xa2\x9cPSVz\xcdI\xf0\x95@s&\x8d\xfb4\"N\x89f&\xbf\xdd\xd3<+\x8ft\xac\xce$n\xc3\xd6\xe1\xa9\xf4\ue93cƍ\x1bV[`\x06\x81A\xc74\xdd\xf3=\xee\x16!!\xd0LP4\xa7\x80\xbd\xef\x8a\x00Ӻ\x15\xd9\xc0\x1d\xc3~LY\xa3$\xa8,g\xb5=v\xf6쭥.\xd0\x1a\x1d\x95\xad\xdfD\x0e\x9f'{>Y&\xf9\xc4a\x89\x1dZ\xcb\xeas\xa6\xf8s\xa0\nM\x8f\xb8\x04\xd8F\xf5n\xdf\xf5\x18ŌK\x1bu\xeay\x8d\x97l?a\xacΌ\xea=\x1b랶\xf5k\x86\x8e\xe0\xf0P\xe9Qm0\x9f7\xbd\xc4'\x00\xf8\x17\xb8s\b\x89&g`{\xefu\xd2\xc2\xe0\x84S\xbe\xc1\x87\xcc\xe8\xec\xe5p8y\x95L&3\xf7\xa3\xb7\x86g\x9d?ntN\x04\x91\f\x1a\xd5&cV\x8e\xb5 \xfbn\x83\x86\xe4\xb0\xd99\xb4cw\x9ekq\xf9\xd2\xf8 \xc6\xc1\xfat\x7f\x81S\xac\xf6K&}+چ\xb0ͅ\xd5-\xdbe\x18\xa7\x83\xf8\xf4\x97\x8c\x8b\\\xc0A\x9f\x93Qk<\x16\xe5O\xb7\xe6<\xa6\x0fJ\x1e)֒=\v\xe9\xfe\xfa\x97\x13ɲ\x90\x0e\xebIp\x88\xf3$\xce\xf7\xb4\xcb\xd7\xd9\xe1D\x12c%ӶQ\xee\xfa\xc3\x19-X\xef\t\x935\x1c\xf2h\xef\xfb|\xa3<\x12EU\xc8]\xd5\u07b7<\xcbT\xc7o\xd6砎\x88\xcfD\xa1\xf8Z\x9e\x8bAk\xd4̐\xa5\xfbg\x95\xab\xe9\xbb\xdfk\xb0\xc2\xf7\x8a)\xf5\f\xb9h\xe8\xe4X\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xdf2~d\xf5d6\xe8\x91\xf3\x01\xef\xf8\xde0\x1c\xe97\xfb\xb7\xb4\x15\xfc\xf1\xe7\xc5\x7f\x02\x00\x00\xff\xff\xb6]>s\xd5\"\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs#\xb7\x11\xbe\xf3Wt\xad\x0f\x8a\xabv\x86\xdeM*\x95\xe2mW\x8aSJl\xadj\xa9\u074b\xcb\apМ\x819\x03\xc0x\x90b\\\xfe\xef\xa9\x06\x06\xe4\xbcHJ\xaaȞˮ\x80F\xe3Ç~\xa1\x99eٌi\xf1\x15\x8d\x15J.\x80i\x81\x8f\x0e%\xfde\xf3\xcd?l.\xd4|\xfbn\xb6\x11\x92/\xe0\xda[\xa7\x9a\xcfh\x957\x05\xde\xe0ZHᄒ\xb3\x06\x1d\xe3̱\xc5\f\x80I\xa9\x1c\xa3aK\x7f\x02\x14J:\xa3\xea\x1aMV\xa2\xcc7~\x85+/j\x8e&(O[o\xbf\xcb߽Ͽ\x9b\x01H\xd6\xe0\x02\xb4\xe2[U\xfb\x06\rZ\xa7\f\xda|\x8b5\x1a\x95\v5\xb3\x1a\vR^\x1a\xe5\xf5\x02\x8e\x13qq\xbbq\x04}\xaf\xf8נ\xe7s\xd4\x13\xa6ja\xdd\x7f&\xa7\x7f\x10\xd6\x05\x11]{\xc3\xea\t\x1ca\xd6\nY\xfa\x9a\x99\xf1\xfc\f\xc0\x16J\xe3\x02\xee\b\x8af\x05\xf2\x19@{\xce\x00-\x03\xc6y`\x8e\xd5\xf7FH\x87\xe6\x9aT$\xc62\xe0h\v#\xb4\v\xcc\x1c\xf4\x80Z\x83\xab\x90\xb6\f\xac2!\x85,\xc3P\x84\x00N\xc1\n\xa1E\u00832\x80_\xac\x92\xf7\xccU\vȉ\xb8\\+\x9eˤ\xb3\x95\x89\x9c\xdf\rFݞ\xcea\x9d\x11\xb2<\x85\xec\xff\f\xaa\x87\xe7^\xf1'\"y\xa80\xc8$4^\u05caq4\xb4y\xc5$\xaf\x11\xc8@\xc1\x19&\xed\x1a\xcd\t\x14i\xd9\xc3^\xf7\x91|I\xfa:3\xcfa\xe79TD\xd9\xde\xf6_\xbbC\x97\xf6\xbdW\xbc]\x00\xadQ\x83u\xccy\v\xd6\x17\x150\vw\xb8\x9b\xdf\xca{\xa3J\x83\xd6N\xc0\b\u2e6e\x98\xed\xe3X\x86\x89\xd7űV\xa6an\x01B\xba\xbf\xff\xed4\xb6vQ\xee\x94c\xf5ǽC\xdbC\xfa0\x1c\x8eh\xc9\xd9\xca\xf6\xfa\xff\x14\xb8+\x82t\xa3d\x9f\u05cf\x83\xd1)\xb0\x1d\xa5)\xde\xe6\x85\xc1\x10j\x1fD\x83ֱF\xf7\xb4~(\xfb\xfa8sq No\xdf\xc5PVTذE+\xa94\xca\x0f\xf7\xb7_\xff\xba\xec\r\x03h\xa34\x1a'Rt\x8d_'ytF\xa1\xcf\xec\x15)\x8cR\xc0)k\xa0\x8dN\x11ǐ\xb7\x18\xa2\xb3\b\v\x06\xb5A\x8b2摞b !&A\xad~\xc1\xc2\xe5\xb0DCj\xc0V\xca\xd7!\x02m\xd180X\xa8R\x8a\xff\x1et[\xf2=ڴf\x0e\xdb\x10\x7f\xfcB\f\x96\xac\x86-\xab=\xbe\x05&94l\x0f\x06i\x17\xf0\xb2\xa3/\x88\xd8\x1c~$\v\x11r\xad\x16P9\xa7\xedb>/\x85KI\xb3PM\xe3\xa5p\xfby\xc8\x7fb\xe5\x9d2v\xceq\x8b\xf5܊2c\xa6\xa8\x84\xc3\xc2y\x83s\xa6E\x16\xa0ː8\xf3\x86\x7fc\xda4k\xafzXGN\x17\xbf\x90\xeb\xce\xdc\x00%;\x10\x16X\xbb4\x9e\xe2Ht\nٟ\xff\xb9|\x80\xb4u\xb8\x8c!\xfb\x81\xf7\xe3B{\xbc\x02\"L\xc85\x05]\xbaĵQMЉ\x92k%\xa4\v\x7f\x14\xb5@9\xa4\xdf\xfaU#\x1c\xdd\xfb\xaf\x1e\xad\xa3\xbb\xca\xe1:T\x12\x14/\xbd&\xcb\xe59\xdcJ\xb8f\r\xd6\xd7\xcc\xe2\xab_\x001m3\"\xf6iW\xd0-\x82\x86\u0091\xb5\xceD\xaa`N\xdcװ*Yj,\xe8\xfa\x88AZ*֢\b\xbeA\xe1\a\xd8H>艹v]\xfaV\xac\xd8x\xbdtʰ\x12\x7fPQ\xe7Ph\x80\xed\xe3Ԛ\x04Nvr^T\x0e6J\x8e\x94\x02\xd4i\xf1\xaeB\x83\xdd5\x06\xb5\xb2\xc2)\xb3'\xc51[\xe6#\r'.\"\x1cY\xf1\vǠp\x1f\x1c\xc2\xe0\x1a\r\xca\x02S\x848W\xc9L\x9c\xa2\x93\xd0\xc7\x10OS\x0fg\xa2\xe7$\xe0\x0f\xf7\xb7)b&\x86[\xe8n\xbc\xef\x05z\xe8[\v\xacyH(\x97\xf7\xbe\xba]\xc7\xcdB\xecp\n\x18h\x81\xb1\"=\x04c\x10\xd2:d\x1c\xd4zR#\xbd\r\x80\x1c\xcc`\xbb\xe2m\x8c\x14mH:\x86p\xa2\x1e\x18\xc5(\xc1\xe1\xdf\xcbOw\xf3\x7fM1\x7f8\x05\xb0\xa2@kC\xbe\xc6\x06\xa5{{\xc8\xd9\x1c\xad0ȩp\xc1\xbcaR\xacѺ\xbc\xdd\x03\x8d\xfd\xe9\xfd\xcf\xd3\xec\x01|\xaf\f\xe0#kt\x8doAD\xc6\x0f\xe1/ٌ\xb0\x91\x8e\x83F\xd8\tW\x89a\xd2:0@\xd6\xd5\x1e{\x17\x8e\xeb\xd8\x06A\xb5\xc7\xf5\b\xb5\xd8\xe0\x02ބJ\xf0\b\xf37r\xac\xdfߜ\xd0\xfa\x97\xe8@oH\xe8M\x04w\xc8w]\x8f<\x82t\x15s\xe0\x8c(K<\x16\xa2\xc3/\x04o\n\x89߂2ĀT\x1d\x15A1\xdd^\x8cG\xc8G\xa0\x7fz\xff\xf3I\xc4}\xbe@H\x8e\x8f\xf0\x1e\x84\x8c\xdchſ\xcd\xe1!X\xc7^:\xf6H;\x15\x95\xb2x\x8aY%\xeb}\xac\xf6\xb7\bV5\b;\xac\xeb,\xd6\x1b\x1cvlO,\xa4\x8b#{c\xa0\x99qg\xad5U\x19\x0f\x9fn>-\"22\xa82\xc4;\xcaNkAU\x03\x95\v1\xe7\x05k\x1c%\xcd\xf4Y\x1f\xcd\xc7)(*&K\x8c\xe7EX{\xcaB\xf9\xd5K\xfcx\x9c\xfa\xd37Q\x02\f\x03ǟ\x96D\x9fx\xb8P\xa9>\xe1pݷ\xd6\xd9\xc3m\xfc\n\x8dD\x87\xe1|\\\x15\x96\x8eV\xa0vv\xae\xb6h\xb6\x02w\xf3\x9d2\x1b!ˌL3\x8b6`\xe7\xe1\xc9<\xff&\xfc\xf3Ⳅ\xd7\xf5S\x0f\xd4{\xf4\xbf\xe6\xa9h\x1f;\x7fѡR\xad\xf8\xf4\x8bE\x96f\x82*$*\x82b\xd59]\xd73\xadk*l\xdaR\xea\xd0T,\x94\\\x8bқ\xf0z9Eʤ+t\x1b\x8c\x17n\xefKG4]݅\x16\xa7\xab\xa6.\xaf\xd7\xf8\x1c\xa3E\xe9\x9b1\x94\f6J\v61N\x86;rZ\x9ax3.\xf6\xce\xdc\\\xf4\x8a\v\x1c\xb4\xfd\xb8\x89\xc7e\xebT\xb1\xd8\x0e#\xf4\xa0\v\xae5\x9d\xa5\x9e\xebl\x06\x7f\xf5\xf4r\xe8#̦\x9f\xcc\x03\x19\xad\xf8lHZ7N\r&\x8fQf8\xd1w\xe0\xc1l\xafO\xdc=\u0378\xdb\x10\x9a\x90\xcf\xe97\xc4\xc6g\xcb{L{.\xb5C\xe9\xcd\xf7\xe2\x8eC\xa1\xe8=\xd5\xebY^\xb0\x81\xeb\xf1\x8a\xd0\xde3\xbc\xf5\t\xd1`x\xc6Ǟ\xed\x8eٴ\xc9\xd4}CG_\\\x1aJ\rR\x87<\xbcv\xe81\xb6f\xa2F\x0e\x87\x9f\x9e\xc2\xef\v6\xf4\xb9\xae\xa6\x8a\xfb\xa4\xc8[\xe4!\x98N\x80\x1e\xafK\xadc\xce\x1cf\xa4b$!}]\xb3U\x8d\vpƏ\xa7ϸW\x83ֲ\xf2\x92\x7f\xfd\x18\xa5b#\xa4]\x02l\xa5\xbc;tBZGk\xa9\xb8\xb2\xad\x15<\xaf\x1bS1{\t\xca=\xc9LY\xdc\xc1\xe5ϛ\x1c\x9c\tew\xb8\x9b\x18\x1d\xb5\xf2\xbb\x93\xd7Ʉ&\xe6\xbe\x0f\xd6\xf1,\x02ڍ.qЊA\xa5\xead\xdd\xcaQ\xa6\xf6\xcd\n\r\x11\x11~?H\x8c\xa4\xc01\xd5Z\nO\xd2#\x93G\r)\x16FU\xed#\xbb`2tZm\xccv\\X]\xb3\xfd\x84\xdet\x92Pu\x92\xf9\x92\x1f\x1d-&y!\xb9\xff\x89\xecx\xbe%v\xf8}d\xba&\x9e\xfa\xb5e\xea\x16\xba?\x9d\f\xe6\x0f?\f\xbd\xce\x0eg\x92\xbfu̸\xa7\x86\xbdeO\xf8R\xc4\v\xaa\xa7\xe3]7t\x8d\x03U\x7f\x9b?2FM\x125\x1a\f\xc8yGw\xdbN\xee\x8e\xf8\xd5\xe1ǒ\x05\xfc\xf6\xfb\xec\x7f\x01\x00\x00\xff\xff\xb1\x168S\xd6 \x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc=Ms\x1b;rw\xfd\n\x94spR%\xd2\xeb\xe4\x92\xd2M\xf1\xb3\xb3\xcc\xee\x93U\x92\xcb\xef\f\xce4I\xac0\xc0<\x00C\x9aI忧\xd0\xc0`\xbe03\x18J|\xeb\rn\x1a\x02\r\xa0\xbb\xd1߀V\xab\xd5\r-\xd9wP\x9aIqGh\xc9\xe0\x87\x01a\xff\xd2\xeb\x97\x7f\xd7k&?\x1c?\u07bc0\x91ߑO\x956\xb2x\x02-+\x95\xc1/\xb0c\x82\x19&\xc5M\x01\x86\xe6\xd4л\x1bB\xa8\x10\xd2P\xfbY\xdb?\tɤ0Jr\x0ej\xb5\a\xb1~\xa9\xb6\xb0\xad\x18\xcfA!\xf0z\xea\xe3\x9f\xd6\x1f\xffu\xfd\xa7\x1bB\x04-\xe0\x8e(\xd0F*\xd0\xeb#pPr\xcd\xe4\x8d.!\xb30\xf7JV\xe5\x1di~pc\xfc|n\xadOn8~\xe1L\x9b\xbf\xb4\xbf\xfe\x95i\x83\xbf\x94\xbcR\x947\x93\xe1G\xcdľ\xe2T\x85\xcf7\x84\xe8L\x96pG\x1e\xec4%\xcd \xbf!\xc4/\x1d\xa7]\xf9U\x1f?:\x10\xd9\x01\n\xea\xd6C\x88,A\xdc?n\xbe\xff\xdbs\xe73!9\xe8L\xb1\xd2 \x02\xfc\xda\bӄ\x92\xef\xb87\xbb\x00\xc451\aj\x88\x82R\x81\x06a41\a \xb4,9\xcb\x10\xd5\x01\"!r\x17Fi\xb2S\xb2h\xa0mi\xf6R\x95\xc4HB\x89\xa1j\x0f\x86\xfc\xa5ڂ\x12`@\x93\x8cWڀZ\aX\xa5\x92%(\xc3jĺ\xd6b\x97\xd6\xd7\xde^\xde\xdb\xed\xba^$\xb7|\x02n\xc9\x1ee\x90{\f\xd9՚\x03\xd3\xcd\xd6\xfa\xdb\xf1[\xa2\x82\xc8\xed\xdf 3k\xf2\fʂ!\xfa +\x9e[\xf6:\x82\xb2\xc8\xc9\xe4^\xb0\xff\x0e\xb0\xb5ݨ\x9d\x94S\x03\x9e\xdeMc\u0080\x12\x94\x93#\xe5\x15\xdc\x12*rR\xd03Q`g!\x95h\xc1\xc3.zM~E\U0008877c#\acJ}\xf7\xe1Þ\x99\xfa\x98d\xb2(*\xc1\xcc\xf9\x03r<\xdbVF*\xfd!\x87#\xf0\x0f\x9a\xedWTe\af 3\x95\x82\x0f\xb4d+\\\xba\xc0\xa3\xb2.\xf2\x7f\nd{\xdfY\xab9[\xce\xd3F1\xb1o\xfd\x80l>A\x01\xcb\xf0\x8e\x97\xdcP\xb7\x8b\x06\xd1\xf6\x93\xc5\xce\xd3\xe7\xe7om>c\xba\x8f}\xc4{\x8b\xf9\x1a\x12X\x841\xb1\x03刈\xdcfa\x82\xc8KɄ\xc1?2\xce@\xf4ѯ\xabm\xc1\x8c\xa5\xfb\xef\x15h\xcb\xd0rM>\xa1\xec [ U\x99S\x03\xf9\x9al\x04\xf9D\v\xe0\x9f\xa8\x86\xab\x13\xc0bZ\xaf,b\xd3H\xd0\x16{\xfd\xce\x0ek\xad\x1fj\xe15B/\x7f\xfa\x9fK\xc8:'\xc6\x0ec;\x7f\xcc\xc9N\xaa\x8ep\xb0C\xd6\x1d\xa0\xf1Ck\x9b;\xfdV\x82\xf5\x7f\xe9-\xe5?BG\xcb?v\x11\x95`\xbfW\x80\"ΝX\x18\x88\x94\x01HR\xaf\x0f\xd9b=\xf8}\x04\xa7\xb6\xc1\x8f\x8cW9\xe4A\xda\x0e\xf6\xd2[\xf1\xe7\xc1\x00\xd4:\x94\t\xcb\xffV\xfc\xdbe\x8b\xe6W+N#+\xa6\n\x88\xe5@&\x1c<\xc2\x04n6\x8aiۘ\x81\"\xb2\xb8\xc9\xdd\x11\"*\xce\xe9\x96\xc3\x1d1\xaa\x82\x11\xccP\xa5\xe8y\x041\xb5\nN\xc5K\xe8\xef\x05\x02g\x19\xb4\x15\x85C\x8dS2T\rWD~r\xac0m\xc5Y\xbd\xcbG\xc9Yv\x9eEMlP}\xdc\xfc\xe1\xab9x\v\azdRE\xb6dO\xa4\xed\xdaR\xa4\x8d0\x95V\x96y \xf9e\x1b\x8e\"\xeb \xe5\xcb\x1c\xed\xffl\xfb4R\x9bdh\xbc\x85\xadxj{%\xba\x05\x02? \xabLd\x99\x84\xe4\x15*\x10\xa9H)\xb5\x19\xa7\xfb\xb8\xec!N\x1c\x8c1-\x99b\x9a\xc1μ\xa8\xac)g7\xda\x11\x9bR\x80]ka)\xd7\xf4U\xb2r}\xfb\xfa\xad\x85\xf18FȖjȉ\xf4\\_q\xd0~\xae\x1c\xc9\xdfȕ\xdbQ\xd0a\xf3\xce\xd2\xe0t\v\x9ch\xe0\x90\x19\xa9\x86\x98L\xc1\xa7k)\xb2r\x04\x8f\x11\xa9\xd9e\xfffc\x13 \x89e\xf3Ӂe\ag\x04X\xdeD8$\x97\xa0QpXC\xf5<\xb6I2G{?ɔ\xe8h\xda̙\xeaË\x89\x93\xa6%\x88ۦ\xcd\bށ`\xf1ߣ\x9a\xb3i\xff?\x11[k\x92\v\x98v3\x18\xfa\xb6L\x8bN\x955\xf67;\x02Eiη\x84\x99\xfa\xeb\x1cD\xcayk\xfe\x7f`\xc2,\xe7\xf8M\x7f\xe4\x9br\xfc$U\xe6 Z\xaa\x84\xe9\xff\x01\x89\x82\xca\xe2\xd9\xeb\x8ad\x82\xfc\xb5=ꖰ] H~Kv\x8c\x1bP=ʼ꼼\x052R\xf4\x9dm\x055\xd9\xe1\xf3\x0fk\xd9\xe8&Δ\x88\x97\xfe`g\x12\xd7>BW1\xcf\xc0%\xe8\xbe2\x05\x85s\x8b\xbf!6\x9b/\xe8O\xdc?\xfc\x02\xf9\x14zH\x1a\xe7\r6r\xdf[l{jo\xe7\xa7nÛ>\xc1gr\x01\x8f[B\xc9\v\x9c\x9d\xc5B\x05\xb1ġ\x06\xedݨ\xf74D\x0eF^\x90\xc9^\xe0\x8c`|(evt*+\xb8\xf6\x02\x11s?\xd6:\b\xb4k\xf2\x0e\xaeä\xfd\x80\x88@\xc7;\x1dy\x04\xc3b\xb5,\x9a\xdf\x1cI\x17$u\xabq\x7f\xc16\x03\xd9Z\xe1C$\xec{\xedHdO\xc1\x81\x95\x89\x1b\xc5\xe8\xa1\x06<-u`\xec;\xe5,\x0f\x139\xbe߈qk\xb8\xdb\x1e\xa4و[\xe7\x91i\xe4\x92_$\xe8\ai\xf0\xcbU\xd0\xe9\x16~\x012\xdd@<^\u0089m\x8b\x87v\x84-\x81\xb9]۸@J \x0f\xd3d#\xac\xe3\xe2\xf1\x81\xf1R7ݴ~趢\xd2\x18B\x13R\xacPU\xaec39d'\x82\x94\xaaC\x91\xe1\xd2¤n\xc2D\xb0߬&q\xe3]\x04\x98\xd3\f\xf2\xda\xdbĸ%5\xb0g\x19)@\xed\xa7\x14G\xbb\x95V\xbe\xa7-!Q꺶\x90\xc3\xd2T{ݼ\xe8\xce\xe7\x17\xb3\xb2'7\xa1WM\xecٮ#\xe1\xca\xf1\xae\xf3;B\x15\x8b\xf6\xc7,vi\x9ec\n\x89\xf2\xc7\x05\x12\x7f\x01-\x86\xba\xdf-\xcciȂ\x96\xf6\xfc\xfe\x8fUs\xc8\xd0\xffKJ\xcaT\xc2\x19\xbe\xc74\x11\x87\xceX\x1f\x18kOcg`\x9aX\xfa\x1e)\x1f\x06\xc2#\x9b\x93V\xb6\x00w\x8a\\\xee\x06\x16\xcb-9\x1d\xa4v:uǀ\xc7B6\xdd\xc64y\xf7\x02\xe7w\xb7\x039\xf0n#\xde9\x05\xbfX\xdc\x04kA\n~&\xefp\xec\xbb\xd7\x18A\x89\x9c\x98\xd8\xed\xc7\xea%\x84\xe4V\x05-W\x9e{\x8d,X6:ND\xc3\xe3M\xeb\xb0S;D\xde\xc4ƽy<\xb5\xdb$\xfe-\xa56\x7f\x8e\a\xfaF\xd6\xf3X\x8f\xe8ڴ\x91x٬\xad\xefc_A\x18[\vpg@\xf9\xe0\x9f\x13е\xe7\xf0J\x9fj.\xb8\x17\x02{4\x04d-\x82g\xb8ɥJR\x96\xb8\xc4ڴxYh\xa7\x7f\xfeъMړm\xffno䭭\xe1L\x16\x05\xed'\a\x93\x96\xfaɍ\xacy\xda\x03r\xd4W\xfb\n\xcfs\xba\x99X\xf3\x10\xa6\x05O\xcc\x1c\x98 \xb4\x16\x1b\xa0\xbe\xb9]@\x1at]D\xce\x1aՁ\xa0\xe1\x03j\xaaT\x93J\xe6\xe4t\x00\x05\x1d\xae\x18\x06ʭ\xa5\x99\bRHӎGX\xb8\xa5\xcc\xdfk\xb2cJ\x9b\xf6BS\x19\xaeҩ찐\xc2vw\xdfX\x01\xb22\x17\xd0\xe0s3\xba\x93\xd7-\xe8\x0fVT\x05\xa1\x85\xac\x12\x8c\x02\u05ec~aEH\xbez\n\x9c(3!\x0f\x85\x91\x19#-\x95J\x0e&\x95\xc4[\xd8Yq\x94I\xa1Y\x0e\xaa.\x0ep\x94e\xd2\x1e\xdc\x1de\xbc\x8a\xa5}bm\xa9{+>+u\x91w\xfbՍlE\x1b\x0f\xf2\xd4EP2\n\x0e\xf4\b\x84\xed\b3\x04Df\xe9\x02ʉl\x9c\xc2#\x03Q\x93̖i\x02\xde6\x10U\x91\x86\x80\x15\x9el&&\x83i\xed\xee_(\xe3\xd7 \x9b\xe5\xbc/R=\x01\xcd/\t\xc0\xfc\xd6\x1aN@\xe8Ja\xe2މ\x97\x13\xe3ik\xb6\x94#\x9cV\";\x00\xca)\xd1\x11\x1fāgB\x1b\xa0\xa9\xbc`\xad\xa6J\b&\xf6i\xb4K\x0eq6͡z+%\a\xda/x\x8a5\x8b\xeb\xcb\xc5\xd0o\xcd\xe8?D\f\x05\n\xa4\x9b\v[\xf0\xa4\xf2\xb2\x88\x1a\x03E\xe9Λ$\xaa\x12m\xeds\x05)\xb4\xc4\a\xf7\xabxK\xe7\x9a\t\x96@\xd8^΅\x99\xb6eiA\\ղ\xb4\x13\x04\xa3\xe2\x92\xf0٦\x03\xc0\x9e\xce\xdaI\xc1\xb5\a\xaeY`en\x81\xd0<\x87\xdc\x05&\xad\xa9\xe2}\x16W^6R\xaa\x10\xdd\xddr31\x89\xb2u\xebx\xa4\x18\x8aUGXU\xe2EȓX\xa1'\xaf\x17\v\x90\xf4\xd0\xe0\x9bNo.\x96D\x7f\xa4\x14\xea\xf2k:O\xd5\xc6\xd3\x15\xa4L2\xdf,\x8a\x86Lq\xc1\x9c\\s\xa5\xcb#?ήbj\xfe\x89\xc1>\xd1\xfc\xc9\xd5\x1c\xa7ֳm\xe2\xa3Z\xc6\xdf\xe9\x00\xe6\x00\xaa.f^a\xddvLN7\xf9\xe8Ə\t\x05n\x96\x7fjS\xd8\x15^\xf6J\xde⎎\xb5\x02n-cӊ\x1bW~\xac\xaa\b\x13%\x15~\xc5-\x83\x94ʉ\xb9z\x89n\r`\xa8W\xa8\x8b\x00e=Id\x87\x8e\x96\xaeҷ\x9d\x8c\xef\x16>`ȯ^\xe9߽<0\xa1\xa6a\xa6\x92a\xbahr\n_C\xb6ic\xac\xe1A\xdf\xcfW\xd3\xfe\\\xe83P|-\xfd9\x185@\xbb\x18\x8c\f镃\xa0\xe4\xb6.;\x16\x16P\x16\x13.\xf6\x14\xfap\xa0\x85x\x9f\xe1I\x945`\x8d\xa1f\x7f\xda|u;\xd3\xe4#9\xc8*RR7\x81\x9d\x99\x02\x8b\xf1\xb2\n\x9fD\x00C\x8f\x1f\xd7\xdd_\x8c\xf4E\x16\x18\xf9\x8a\xec\x0e\x1d\x95&\x9a\xcaDΎ,\xaf(\xef\x1c\xb2\x16[4\xdcC\xa4\"\x82\xf1X~ղU=\xbe\xc3F\xe4k\xe9\xf2,\x8b\xc5Ѵ\x89\x98V\x8bqq\x05F\xb7\xc2bDI-M9\xa4\x97\x9a\xa6\xd7XL\x17E,\xa9\xac\xe8\xd7M\x8c\x02\x9d\xaf\xa7H\xb1\xeegj'.\xa8\x98H\xac\x96{u\x82$\xa5&\xe2\xa2J\x88ق\xb2\xc4\xfa\x87ne\xc34\xc8\x05U\x0fIș\xafpX\\\xd7\xe0\xeb\b&\xf7\x91\\\xcd\x10\xa9S\x98\x04\xd42/*\x19M)\xb5\xe2W\xf1\xa5\xae\xe8M]ß\xbạ\x9a\x01٫\x01O\xa9\xeeNJ\xe3%\xe7lR\xb2l\xf3\x99\xe3\xe9\xaa\xed\x84j\xed\x84l\xd0\xdcJ\x13\xaa\xb2\x97Uc'\xe0\xf0J\xbe֕\xbc\xadk\xf8[\xd7\xf5\xb8f}\xaeYΙ\xf9yY\x15\xf5\xc5I\x86:\x1d\xfd sx\x94\xca\xcc9\b\x8f\xfd\xfe\x91\x14`\xcbi\x92<'\xa2\xee\x1a\xcb4X\xdb\xdf\xdb\xfd\x97m*\x9e\xad\xab\xcd\xdf_en\xd76\x97[x\xeau\x1f\\\xa1݁\x02\xe1\x1e\x96\xf8\xaf\xe7\xaf\x0f\x01~\xcc\x1e\xf5Fo\xefM\x03g`\xe4\x1e9>\xfb\xe4\vn\x1c\xb6P\x87\xbfq\x92\x80\x96\xec?\xf1ͮ\xf9\x80\xcc\xfd\xe3\x06\xbb\xd6\xd6\x12\xbe\xf5\x15\x12\xfa!\xf7\xb6\x05\xab=\x02FF\xb9\x7f\xb3\xeb@\x8c\x94\x9d\x86?\t\xbe\x98Tk/6V\x93劐\xec\xa9{ܸխ\xc9\x17k\xba\x893\x91\x8e\xf1\x0eL嫒*sF\xeeзa\r\xe3A\x99Z\x87L\x85NFE\xed\xf0-\xa8(n\xeb'\xa10\x01w.\xbb\xd9\xcc>F/Y\xc7\xf8\xed\x89\xd9{\x13o\xb8\x8equ\xbcBLE>G+ \xde,$\xe5\xc5\xd0')v,\x12\x8f\x8a\xdd\x7fp}{\x02 Ï\x95jނ\x9a|\x86g\xfaL\x9e\x143\xf0\\R\xa5\xe1\v\xe3cz\xbe[D\xd8\x1b\xe2\xb4\xf8\x8eӽ˂\xe7,\xa3\x06\x82\x00\xc6\x19F8~\x87\xe35\xc2\xe2.\xa1)G\xcc\xf5\xe9B\xc9\t\"y\xc4<~\x9f\xd3&O\xa1\xe3\xb4\x1a\xa1\x9c\x87hZd\xa9v\xc1&.aM!#\xba\xdf \x01/\xd9o\x18\x9c\xbe\xdf\xc6\xea\xe5\xe7ƖZ\xb2\xf9q;\xfb\r\xd0\xe1D\xfa%\xb8p#\xc7\x0e\x1e\xee/\xb2\xf2E\xe4\xf6\xd1\x06\x10\xd6\xc0\xc4\x1a\x9c\xe85W4:\x96\xe1Bw\xac\xcc9\xa7\xab\xd3\xf9u\xd64Nlm\xe9\x9f\xd7\n>\x063\xe6s\x8a=\xfc\xbd\u05fdw\xd5\xc5Z\xc6\rDo\xc3F\x90\xf3\xcflW\xff7\x8a-\x87\x7f\x19\xf4\xf8\x83\xaf\xac\x9c\xa8\x12L\xec\xe76\xff\x9b\xef\x16q\a<\x84\x88C\x10\xd9Dp\x11\x169\x04\xf5\"G\x1e\\\x0fN\xc2+\\\x82\xa8:\x19|DF\xce[H\xf63\xf9/\xff\x17\x00\x00\xff\xff\xb2%\xffT#f\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Ms\x1c\xb9nw\xfd\n\x96r\xd8$\xa5\x19\xaf\x93KJ7=ٛ\xa8\x9e\xd7VYZ\x9fr\xe1tc4\\u\x93\xfdH\xf6ȓW\uffe7\x00\x92\xfd5\xcdn\xf6H\xca~D\xbc\xd8\xea!A\x10\x00A\x00\x04\xc9\xd5ju\xc6+\xf1\r\xb4\x11J^2^\t\xf8nA\xe2_f\xfd\xf8\x1ff-Ի\xfd\xfb\xb3G!\xf3Kv]\x1b\xabʯ`T\xad3\xf8\x00[!\x85\x15J\x9e\x95`y\xce-\xbf\x80v=\xa2\x85\x1b$L\x03J\\\x1e\x81\x8a&\x91\x86\x02m;\xb6\xad\xd1ZZ3\xd4\x04Q\x19\x11\xd2X\xe0\xf9\xfa\xfc\xb5\x98\a߳\xa2\xce!\xbf.jcAߡ\xaf\x9e\x87XEd\xd9\xe91\xf1\xe3$\x00o'\x17\"\x03\xe4C\xe6*\xad($\x10#Rk2\x1f*p\x11\nd\xaaǴ\xb5\x85;\xaa\u0080\xc5*\xe7\xffz\x1eS\xa2(\x01\xfd\xde\xfb\xfd\x18\xc654\xd4\xe8i\xd4\b\xc4F\xcfBY\xd9ø\x1c\t\vet\xed\x9eQ9\v\xd8˵\xe6cJ5\f\xa7\t\xbd\x9c\xce\xde\x18\x88\x01\x83e\xa8\xf6\x1b\xb1x\xd8\xff\xffG&\x9f\xc4VC\x01G.$\xb2\xb3\x10\xc6\xf6\xb89\xf4\\\x1b\xccv\xdc\x12Mѻ\x14\xd2\xc1D\xe56g\xd9\xfeNhv\xcaL\x88\x89~#i^\x9cw<&T\x7f@\x82\xed\x94zL!\xd2\x7fa\xbd6\xa2\xc12\x8a}\xb3\r\xec\xf8^(m\x86a1\xf8\x0eYm\xa3z\x82[\x96\x8b\xed\x164¢Hn\x13\xf8}\x9eߤ\xa7\x19\x7f4\xae\x96\xe9\xc8<\xa2Fl(\xe4\xf8L\xb8J\x888Z\xf1\xb4\xba\xe7b/\xf2\x9a\x17\xb4\xd0s\x99\xb9\xf1\xf1\x06\xbf\xb8\xb74)\x10G\xf8;s\"\x8c\x02\xb9\xd4\v\x87(\th^\x97J\xc7,OW\x8e\xc1\xc4ɰ\xe1\x14ň\xc5\x0eڢkt2\x1d*\u0380m\xf5\xceE\xcb)\x17I,\xf8\x06\nf\xa0\x80̪\tg2E\b\\I՟\x11ʎhҾ\x134\xabDۂ\x0e\xe6Nd;gn\xa2\x94\x11,\x96+0\xa41xU\x15\x91U\xa8-\xb3\x92\xe1;\x9bS\x1amIP\x1fC\xb81EҖD\x1dܖ\x19mܧz#6oD\xef\xa1)\x9f%\xec7G\xcd_^ؑ\xdc\x02\f\x19}du]0a\xc3\xd7\x14\xa8=;\xd0\xfc\xc9\x18w\xdal\xb9\x19\xb6~\xf1\xd9\xf2\"\\k\xd0\xf8\x930\x8d\x16\xab;\xbfV-bاn\xcb\v&\xb6\r\xc3\xf2\v\xb6\x15\x85\x05\xb2\xa5\xe6\x10\xed\x18:\xb3\x9c{I\x02\xa5\xae\xbdXJn\xb3\xdd\xc7f\xff!\xa1ŀVC\x00\xce.\x0f>\f\xf1 \x01$k\x8c\n\xda:\x13\x1aJ\xb7%wO\xf3\xa3\xfdB\x16\xe0\xd5\xe7\x0f\xb1Hb\xbf$J\xeaѠ\xae\x06\x96N\x17\x05\x1a`\x12\xc8Π\xc8Lk|<\xb7\xf1z\xc18{\x84\x83\xb3\xacF\xc3Cc\x05Y\xcb\x1b\x90\x1ah\x17\x98\xd4\xc8#\x1c\b\x94\xdf\xd6M\x82\xb7DT\\y\x84Cj\xd5\x01Q\x11?\xbf\xc7ᨋ\x1fh\x14)S\xa9-\rQ\xfd\xdcaV\xa5\r\x96-SJ\xa1\x04\x8a\x9f8\xec\x86a\xbd\\\x86G8\xfc`\x1c\xfbp\xd6\xecD\xb5\x80\x02\xa8\xb0)$\xa3\xb6\xcd&\xfe7^\x88\xbc\xe9\x8c\xe6\xc9\x02\x887\xf2\x82}V\x16\xff\xf9\xf8]\x18DQ\xe6\xec\x83\x02\xf3YY\xfa\xf2\xaa$v\x838\x91\xc0\xae1MK\xe9\x96\x05\xa4ˢ\xfe[\x1ch\tE\x11m\xd8&\f\xbb\x91\xe8\x9f9\xfa,a\xd3\x0e\x02r\x0e\xad\xb26\x94\x06 \x95\\\xb9\x90\x96\xefm\x01\xd0.^\x9eUJ\xf78u\xb1\x10\xe2(\x8a\x1e\xbd{\\\xad\xdc/G\t\x14SECU\xf0\f\xf2\xb0\xcbF\xd9\x1a\xdc\u0083\xc8X\t\xfa\x01X\x85\xebF\xbaP-\xd0䮜 \x85\xe9\xa6E(~Y\x98\xd8\xf5\xed\x96\x15\xce\xfaĚ\x81\xcdI\xd5gvfǫ\xa7\x8d\x92\x96w\xb2\x87\x92\xa8\xdf\xcd%\\\xb6\xb2,\xe4ױ\r\xe2\x90t\xe6G\xc9i\xe3\xe9︼\x92x\xff#m5\xe4B\x9b5\xbb\xa2L\xca\x02\xba\xedC\x94\xb0\xd3U\x12H\xc4D\x18\x86r\xb2\xe7\x05\x9a\x0f\xa8\xbc%\x83\xc2\x19\x13j{dA\xa5\xa9\x98\xa7\x9d2n\xcdo6\xc6\xce\x1f\xe1\xe07g\xbbZ\xe2\xfcFF\xa3\xf6\xfd\x82:\xffHi5V\x8b\x92Ł\x9d\xd3o\xe7d\x98-\x99\"'\x18o\v\xa4zA\xd5\xef\xab\xc7z\x03Z\x82\x05\xb3*y\xb5\xf2\xb3\xc1\xaa2\xba\xc7\xe9\n\xe5;.q#\xd0Oo\xb2:\xf0\xff!+\x10\xcd\xff9\n$χJ\x99H\xa6E\x04\xad[e\xac\v\x1e\xf6L\xf5\x91\xe8b\x8a\xe7\xe8#\x8e\x8co-hf\xac\xd2!\xe7\x04U\xf6 \xb8\x8eRc\xe6\xe5\xc6\xed\x13\xf9H\xa6\x03\x8c\x0e\xeay\xab]\xdczp\xee\xf6\xaa\xf0\xff\xf3032\xb4\bv\xa5U\x06&\x9a\x8dЖ\xc4Ug&\xd8\xdb\x04z\xb9s\xfc\xb6Ij=%\f\x1d\xca23\x1eI{\x82S\xf4\xf1{'f\x8d*\f\xffN\x11\xe5Spd\x94\x84_\x96|\x98\r\x9a\x8c\xee\xb5k\x1d&\xa0\a\xe6\x9c-\xfdP\x93BZfs{\x91\xfc\xbd\x19-\xa5\x907\xd4\x11{\xffj\x86\x0e\v\xcb@,#i\xac\f\xd8\xe1۷\fi>\xa4\xfa\xbe,\xe4\x14*\xda\xe7\xd1\xd0\xe3\xec\xf1.H:\xa7\x18\x1a\xe2R\xd9n\xa0\xc7\xf7\xf4\x83a[\xa1\x8dm\x11^\x00U\x98\x89\xac\xa7\xd1\xe1\x9d\xe0\x9fʏZ\x9f\xec\x9e~q\xad;!ɝz\xf2\x99\xb8K\x9c\xf2@\xfc\x1d\xdf\x03\x13[&,\x03\x99\xa9ZR\xb0\f\xd5\x05v\xb3\x00\xa2c\xa2[L\x12\xd7\xccNcY\x97\xe9\x04Y\x91t\n9\x1bY\xeb6\xf9\x89\x8b\xb4\xc8\x16;\x8d\xadv*\x89r\xac\xf4Sx}6e7\x05\xb6\xe4\xdfEY\x97\x8c\x97Ȗ%>\xe7\xd6\xe5a\x86\xfcl\xc7\xeb'.l\x93H\x8b\xecY\xa6M3UV\x05X\b\x19\x96\x99\x92F\xe4И\x0f\x9e\xff\xa3\x89ű\xc2ٖ\x8b\xa2\xd6\vt\xf4b\xce,\xf5\xf9\xbczzyG.\x1d\x91\x15\x1131`\xbf\xc0\xe0\x9e_?*\xbd\xccd\xbe\xd5\xf0\xf2\xa6i\xa5\x85\xa2\x1c\xd8\x19\xebt\x16&Y\xaf}\xeb\xd4\v/\x97\x87\x98y:\v\x950y3O\x9b\xf2f\x9e\xbe\x99\xa7o\xe6頼\x99\xa7o\xe6\xe9\x9by:^\xde\xcc\xd3Ny3O\x93\u05cf\x14\fW\x14\xb9\x9d\xa8\x90\x84Ub\xfa\xc6\x1c\xda3}\xf9,%\x7f\x16dIv\xf5\xcdxˑ\xb3@\x8bΐ\x98\u03a2פ[\xe3\x94\f\x93\xc9\x1d\xc6L\xb0\xc2_\xe0\xacM@\xe0\xe4\xb367\x93\x00^\xf0\xac\x8d\xc7t\x18;\x7f\xc1\x936\x81\x16\xcb\x0fa\\\xf84\xa6\x12x\xd8\x12r9(y\xacۘ\x15\xdb\xc3c\xb4\xceo\x9cu\x7f\x94\xady\xba\xc8\xfc\x9f\x9c߉\x88\xcd\xd1\xe9\xd4\bTaP\xae\xfe\x18\x9c8\x89\xf6Qj\xbb\xff\xc5F\xd7\x12\xd6)^w\x1fD7ղ\x9f\xf2\xfa\xc7\x11\xecS$9\xf5\xfcM\xcc8\x8f\xeb\xb6\x0e1u\xef\xbc\xc7\uf6d6\x16\xca/\x95_\xc9\xd2Ͻߌ4{\xc6\xc9wn\x0e2\xdbi%Um|\x84\a{\xb8\xcaܝ\r\xa1#\xb3D\x19\xbcg;UG\xcex\xcc\xd05!\xf36\x9eo\xeb38\xc0\xf2\xfd\xfbu\xff\x17\xab|\xf6m\x04\xeb'aw\xee\xe6\x03\x9e\xe7h\xa8w\x8e\xf8\x84\xc9\xeb/\xcf\x19\n^\x04\xa2\xd2L\x8a\xc2Ie\x80\xd0_@\xbfT.\xe4w\xb2\xdd2\x1fxJ\xcf\xd1]\x9a\x99\xdb\xe4R\xce[\xc9\xcf\xc8\xc7]vXj6\xf76\x05i\x96\x92q;\x9eK;\x03uI\x9emjL1!\xa76=\x936\x8d<\x8c\xae\xbcJ͟M\xf6BSse_'C61/\xb6\x93\xed:\v\xf2\xc4l\xd8d\x82\xa5e\xbe&\xe7\xbbv\xb2X\xe7\xa95\x91\xe5:\x9e\xbb:\vr,\xb75%c5\t\xd7\xe4<\xd5&\xfbt~g\xe4Y٩/\x7f\x0e\xe6%\xe3\x16ӹ\xa6I\x19\xa6I\xb1\x8dy\x9c\x93rH\x97f\x8e&Qui\x96h\x93\x01:\xd1qRn\xe8q\xde\xe7\xd4Pf3B\xe3ٞS`\xc7\xf2@\x13r<'@v\xb3?\x17\x9b\x01\xb3\xd24[ai\xee\xe6\xf8Ev\xa1̯\xce\xc5o!\xb3\xcf%\x93\xd2=\xa39Ź\xfb2h\x82\xd2\x12\xec\xc41C<\xee*;\xf3\xfc\x04C<\x02\xf2f\xcbʺ\xb0\xa2*:7\xc9\xd9\x1d\x1c\x9a+\x7f~Utp}s h_\xbe6\"\x1f\x03\xd9w)\xb8aOP\x14\xf8\xef\x11\x152woc\xa6V\x80\xabT|#\xd0_u\xe4/}\xbcpa1:\xd5O+`\x89\x90\xa6/\xc0\x9a\\J\xa6\xcdcg\xd5ӷ\xbfՠ\x0f\x8c\xee\xdc\nvPT\xcc\xdaӞ~2\x1b\xf4\t\x83\xf2\xf1Z\xcc\xdd\x1e\xdaWF\xf1\xd9Ш\x00v%\xdd\xc2<ĕ`\xa1\xd6iݩ)e\x8b\xdeS\f\x84T\r\x84H\xfb\x14\xeb{\xc9\xf1\xc7\xd7p\xae^½J2D^\xc3\xc5z-'k\xa9\x9b\xb5$y#\xe9\xf8\xe2k8[KܭE6c\xfa\xf1\xc4\xd7:\x96\xf8\nn\xd7Ɏ\xd7\"ҥ\x1e;\\\xec~%\x8co\xe6\x98ᑍ\x96\x002z\xbcp\xdc\x05K\x80xt\xacp\xd6\tK\x99\aC7\xedه\x04\x93\x13\x99\x16\xed\xa6\xa7&!\xa5mt\xcf\x1f\xfeK<\xf4\x97\xb8\r\x9e\x82}\xe2\xe1\xbe\xe5\x87\xfa\x12\xe9|\xa2{6\xd9u\xe2\xe1\xbdE\x0eډ.\xda$ĩ\xc3z\xd3N\xdat\x00nxH\xef\x04s\"A\xc2\x12\xaa,?h\xf7\xec\xcd\x18\xa5sг\xfbZK\xc4yV\x90\a~T\xbf\xff\xc1\x8eN\xb8\x11\x15ku\xf7\xccb\x1cUͽ#\x19\xfb\xab\x90~\xb7\x1e\x05\xb7c\x93\xf46\xdeZ\x83)\xbe\xaf\xd3Z\xa9\xfe\x06|\xb7cg\xa0\xe2\x9a\xf6\xf17\a\x97\x14d\xd6\xec#\xcfvM\x0f\x11\x90\xd4\xef\x8e\x1b\xb6U\xba䖝7[\xa1\xef\\\a\xf8\xf7\xf9\x9a\xb1\x9fT\x93>ҹW,\x02Ո\xb2*\x0e\xe81\xb1\xf3.\x98\xe7\tNT`\x03>\xb7\xaa\x10Y\xc4B\x1c\xbd\x92\xce58\xbaЇ.\xcd\xcb:Y\x101\x1f\x05\x9b\v\x7fa\xe1\xe0R\xe1\xad*\n\xf5tb\xe8\x82W\xe2?\xe9\xc1\x99\xb4\xd8\xd8\xd5\xed\rU\x0fRE\x8f\xd54\xd9s\x8d\x8cm`Z\xa1\xb7\x03'ӣ\vu${\xb5\xf9s\x02\"=\xf7\x10\xec\f\xaf\xc63\x85Z\xec\xf6\xc6a\xb9&\xc1\xe2\xf2\xc0\x94\xbf\xd0_\xe8|Uq\x1d\xdd\xd4c^\x1e\xccE\x0fð\x8e\xcfE\xb0&\x97\xb5\xe3\xe7+\xba\xa5G\xf3\xf0\x92\x05m\xf6\x1e\xaa\xfe6:Q\xbaC\xcf\xe7\xe04}py\xf6\xc8\xf2+\xe04m2\xad\x88\x8a\x91\x9f\xa2\xe9x/\x1e=4\xfe\x12\xf9\x9f\xd5\x1e>D\xa3\x88\xfd\xb7\x1d\x06MF\x12\xe8\x02ԩk\xd3۬\xb9\xf8u\xd6/\x90\x11\x17P\xf1\x17_/\x18\x9fo1\xf6\x9c\x86\xbf\xff;\xc0\x9eX\xdbp\xca\xde~#w\xb3Q\x97~\x86{g2\x84\n\aW\xb5F@\xc6\xde\xc3x)jY\xa5\xf9\x03|R\xeeɒ\x14j\xf5[\xf4^\xad\xf1\xc6\\\xc8&\xf6s-\xa6\xcc\xfd؆\x00\xdbC\x06G\xd7\xd1#\xb6'\u07b6om\x910\xb8\xfb\xfbOn@V\x94\xb0\xfe\xe0\x1f\xd6@\xbdk\x00)\x1d\x06\xea\x1am\xe2\xdai\xa7\x9e\xe8>\xf7\xee\xbb\"\x9d\x97\x9b\x80\x0e5P\xda\xe8I\xa3\xd9\xf7\x1e\x84\b\xa4K\x11\xf6o\xe3-;6T\x87\x89S)dj\x1b\x85ōQ\x99 \xb3\x8bb\xe6t\x96\xe0\xf5n\"\x9e2\x9f'\xf4gm\xe0˓\x04\xfd5LTs#c/0\xf4H\xf8\xcbQ\xc3\xe8\xeb\vV\x91\xb17\xa8>f\xe4KO \xe3\xde\xee\b\xc1\x7fa\x9a\xa7u\x8eI73\xff\xe3s\x7f|\xa5Z\x8d\xbff\xb3j\x1e\xd89K\xa0\xac{\x9b\xa4\x0fx\xfc\xed$\xf7\x88I\xc6+[\xeb\xf0\x9cM\xad\xe9Zf\x04\x02\xee\xd6\xe2\xd3^Oj_\xa3\x9b\xe1e\xfb>]\x1b\x0e\x9d}\ro\x84\x7f\xcd\xfbG\xd1\a\x81\x9c#\xe1^\xab[!\xfc\xd3\xd89:\x0f\xe8\x1a뙑\xdeb\x9d\xe6\x1c\x98'45\f\xd7_\xdf\xc5P\x1f?سb\x9f\xe1ؐ_\xb1\x8f\x12\aq\xbc\xbe\xbb\xd3;\x90S\x10u\xec\xe5\xb8\xc9!\xee\x9bVttjD[\xf4\xd5ܠ\xfa \xb1\x93\x9e3i\xaa\xb8cRcl\xfdg\xb1u\x11\xee\f\xc7\xf4/G5\xa2\x8akRi\xc5\x14\xd6\xe8\x94:\xfah@\xef\xe9\xfd\x90 $~\r\xef~\xa97\xedm\xe4\xec\xef\xff8\xfb\xdf\x00\x00\x00\xff\xff\xe7\xb2\xe8\xf4^t\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4V\xcfo\xeb6\f\xbe\xe7\xaf \xb0û\xcc\xce\xebv\x19r\x1b\xba\x1d\x8am\x0fE\xf3л\"\xd3\tWY\xf2H*]\xf6\xd7\x0f\x92\xec&\xb1\x9d\xb5\x1b0\xdd\"\xf1\xc7Ǐ\xe4\xe7TU\xb52==#\v\x05\xbf\x01\xd3\x13\xfe\xa9\xe8\xd3/\xa9_~\x90\x9a\xc2\xfax\xb7z!\xdfl\xe0>\x8a\x86\xee\t%D\xb6\xf8\x13\xb6\xe4I)\xf8U\x87j\x1a\xa3f\xb3\x020\xde\a5\xe9Z\xd2O\x00\x1b\xbcrp\x0e\xb9ڣ\xaf_\xe2\x0ew\x91\\\x83\x9c\x83\x8f\xa9\x8f\x9f\xeb\xbb\xef\xea\xcf+\x00o:܀ \xa775\x1a\x85\U0004f222R\x1f\xd1!\x87\x9a\xc2Jz\xb4)\xfe\x9eC\xec7p~(\xfeC\xee\x82{\x9bCms\xa8\xa7\x12*\xbf:\x12\xfd\xe5\x96ů4X\xf5.\xb2qˀ\xb2\x81\x1c\x02\xeb\x97s\xd2\nD\xb8\xbc\x90\xdfGgx\xd1y\x05 6\xf4\xb8\x81\xec\xdb\x1b\x8b\xcd\n` $Ǫ\x06.\x8ew%\x9c=`gJ\x12\x80У\xff\xf1\xf1\xe1\xf9\xfb\xed\xd55@\x83b\x99zʹ.T\x06$``@\x01\x1a\xc0X\x8b\"`#3z\x85\x82\x12ȷ\x81\xbb\xdcɷ\xd0\x00f\x17\xa2\x82\x1e\x10\x9e3\xe5Ce\xf5\x9bIϡGV\x1a\xd9\x18\xdc\xceCvq;\xc1\xfa)\x95S\xac\xa0IӅ\x923\r\x94`30\x00\xa1\x05=\x90\x00c\xcf(\xe8u\x8a2\xf3ӂ\xf1\x10v\xbf\xa3\xd5z\xe0AR\xb3\xa2k\xd2P\x1e\x91\x15\x18m\xd8{\xfa\xeb-\xb6$BRRgt\x9c\x93\xf3!\xaf\xc8\xde88\x1a\x17\xf1[0\xbe\x81Μ\x801e\x81\xe8/\xe2e\x13\xa9\xe1\xb7\xc0\x98\xc9\xdc\xc0A\xb5\x97\xcdz\xbd'\x1d\x97ˆ\xae\x8b\x9e\xf4\xb4\xce{B\xbb\xa8\x81e\xdd\xe0\x11\xddZh_\x19\xb6\aR\xb4\x1a\x19צ\xa7*C\xf7y\xc1\xea\xae\xf9\x86\x87u\x94OWX\xf5\x94&K\x94\xc9\xef/\x1e\xf2B\xfcC\a\xd2:\x94\xf9(\xae\xa5\x8a3\xd1\xe9*\xb1\xf3\xf4\xf3\xf6+\x8c\xa9s3\xa6\xecg\xdeώrnA\"\x8c|\x8b\\\x9a\xd8r\xe8rL\xf4M\x1fȗ鲎\xd0O闸\xebHe\x9c\xddԫ\x1a\xee\xb3\xe2\xc0\x0e!\xf6\x8dQljx\xf0po:t\xf7F\xf0\x7fo@bZ\xaaD\xec\xc7Zp)\x96S\xe3\xc2\xda\xc5\xc3(s7\xfa\xb5\xb0\xdd\xdb\x1em\xea`\"1ySK6\xaf\a\xb4\x81\xc1,\xb9\xd4\x1fB\x92=\xfe%\x96AI\n\x9a\x89\xbe\xa4\xfd|\x1fͲ\x9c䗃\x11\x9c^N0=&\x9bi~G-ړuXB\x145\xc1\xf7\xa1\xa4\x83>v\xf3\x9c\x15|\xc1ׅ\xdbG\x0eIY\xb3\xae_\x9f\x1b\xb3\x01\xe5{\xb3'?+wZY\xb1\xca߰K\xa9\xbe\x10\xe8!\x10p\xf4>\xed\xedL!3\x90\xa9\x92\xcflH\xb1[@\xb3\x88\xe7\xc1\xb7!\x7f\xf0MJl\xb4\xec\x13\x0e\xcd\x1e\xf2\x14\\\v\x01o\xf7\xba\x9c\xb9x}\x88\xd0r\xf2\x97\xf4\xbf9'\xb9!\xc6\xc5\xdcUF\xb5\xf8\x902.1\xbe\xbc_\x03\xca\xe8\x9c\xd99܀r\x9c{\x17_\xc3lNө\x19G\xed+u(j\xba\xfe\xbd\x01\x9a9\xa4=y=\xa0\xbf\xb5\r\xf0j\xa6*\x7f\x95\x19v\xa7[\xae\xf7o\xff\x01\xe7+UFw\x03I\xbb+\xa5\x05\xce>D\xcab\xf7\xcaH/\xfe\xf3\x98\x11\xb2\xbd\xb4\x1d5\xe3j5\xc6?\"\xf3\x1anBXl\xf6\xec2\x87o.\xca\x13\rl\xf6c\xc1\x7f\a\x00\x00\xff\xff\xb1J-\xe7\xa6\v\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VA\x93\xdb6\x0f\xbd\xfbW`&\x87\xbdDr\xf2}\x97\x8e/\x9d̦\x87L\x93f'N\xf7N\x8b\x90\x8d\x9a\"U\x10\xd4\xc6\xfd\xf5\x1d\x90\xd2\xdak\xcb\xc9n\xa7\xd5\xc5c\n\x04\x1f\xde\xc3\x03UU\xd5\xc2\xf4t\x8f\x1c)\xf8\x15\x98\x9e\xf0\x9b\xa0\xd7\x7f\xb1\xde\xff\x14k\n\xcb\xe1\xedbOޮ\xe06E\t\xdd\x17\x8c!q\x83\xef\xb1%OB\xc1/:\x14c\x8d\x98\xd5\x02\xc0x\x1f\xc4\xe8rԿ\x00M\xf0\xc2\xc19\xe4j\x8b\xbeާ\rn\x129\x8b\x9c\x93OG\x0fo\xea\xb7\xff\xab\xdf,\x00\xbc\xe9p\x05Cp\xa9\xc3\xe8M\x1fwA\\hJ\xcez@\x87\x1cj\n\x8b\xd8c\xa3Gl9\xa4~\x05\xc7\x17%\xc5x|\x81~\x9f\xb3\xad\xc7l\x1f\xc7l9\xc0Q\x94_\xbf\x13\xf4\x91\xa2\xe4\xc0\xde%6\xee*\xb2\x1c\x13w\x81\xe5\xb7\xe3\xe9\x15\fѕ7\xe4\xb7\xc9\x19\xbe\xb6\x7f\x01\x10\x9b\xd0\xe3\n\xf2\xf6\xde4h\x17\x00#?9]5Q\xf3\xb6dlvؙr\x0e@\xe8ѿ\xbb\xfbp\xff\xff\xf5\x93e\x00\x8b\xb1a\xea%\xb3<_\"P\x04\x03\x13\x12x\xd8!#\xdcg>!J`\x8c#\xe8Ǥ\x00\x13\xfeX?.\xf6\x1czd\xa1\xa9\xf8\xf2\x9c\xf4\xd7\xc9\xea\x19\xae\x1b\x85^\xa2\xc0jca\x04\xd9\xe1T>ڱZ\b-Ȏ\"0\xf6\x8c\x11\xbd\x1c\x85<>\xa1\x05\xe3!l\xfe\xc0FjX#k\x1a\xd5&9\xab\xfd8 \v06a\xeb\xe9\xaf\xc7\xdc\x11$\xe4C\x9d\x11\x1c5?>\xe4\x05\xd9\x1b\a\x83q\t_\x83\xf1\x16:s\x00F=\x05\x92?ɗCb\r\x9f\x02#\x90o\xc3\nv\"}\\-\x97[\x92\xc9WM\xe8\xba\xe4I\x0e\xcbl\x11\xda$\t\x1c\x97\x16\at\xcbH\xdb\xcap\xb3#\xc1F\x12\xe3\xd2\xf4Te\xe8\xbe\xf8\xa0\xb3\xafxtb\xbcy\x82U\x0e\xdaEQ\x98\xfc\xf6\xe4E6\xc2w\x14P\x0f\x94F([K\x15G\xa2uI\xd9\xf9\xf2\xcb\xfa+LGg1\xce\xd9ϼ\x1f7ƣ\x04J\x18\xf9\x16\xb9\x88\xd8r\xe8rN\xf4\xb6\x0f\xe4%\xffi\x1c\xa1?\xa7?\xa6MG\xa2\xba\xff\x990\x8ajU\xc3m\x1e6\xb0AH\xbd5\x82\xb6\x86\x0f\x1enM\x87\xee\xd6D\xfc\xcf\x05P\xa6c\xa5\xc4>O\x82\xd39y\x1e\\X;5\xd88ޮ\xe85\xef\xe4u\x8f\xcd\x13\x03i\x16jitv\x1b\xf8\x8cW3\xf9|>_\xfd$|\xde\xe0P\x86|K\xdb\xf3U\x00cm\xbe\"\x8c\xbb\xbb\xba\xf7;\x84\xcd\xd4}\x9bO\xd2Fm\x03+\xa2\x81,r5\xd59\"I<\x16L\xe8l\xac/R^\xe1<\x97\xc2hUc\xe3.\x81>E\xf2\x18\x98\xef8C\xbeP~L\x90[\x8f\xbbq\xc6zAo\xf3P\xbf@\x13r\x0fG\xb4\xf0@\xb2+\xe6p\xa7\x97\xd4\xf3T\xd0g\x8f\x87\xb9\xe53\xec_w\xa8\x91e\x9c\"Dl\x18EqDtj^uf\r\xf0)\xc5l/3\x9b\x11tD\x90\x9dv\xef\xf1pI4\xfcH\xdc\xf1\xbe\xff1\xe4\x1b\xbd\x17'\xc0\x8c-2z\x99\xb5\xb8~b\xb0G\xc1\xecr\x1b\x9a\xa8\x06o\xb0\x97\xb8\f\x03\xf2@\xf8\xb0|\b\xbc'\xbf\xad\x94\xf0\xaa4B\\\xe6\xef\x86\xe5\xab\xfcs\xa5䯟\xdf\x7f^\xc1;k!\xc8\x0eYUk\x93\x9b\x1a\xed\xe4\xb6{\x9d'\xeekHd\x7f\xbe\xf9'\xbc\x84\xbe8\xe7\x19ܬs\xf7\x1f\xf4\xe6Π\x94\xa2uQ%0\xe8\xdcT\xb1\xbbQ\xcd2\x1f\xe6\x1aq´\t\xc1\xa1\xb9l=\x9d\xbe\xc4h/!Uz\xc2Kl\x06\xf0\xad:\nUu\xa6\xafJ\xb4\x91\xd0Qs\x16=\xf9\xfc\a\x96\xbc\x1b\xc3t<(\aӶ\xa9m\xcaWL\xfe\xa61[\xbc6\x16f\x14\x99/\xbcz<\xe0Y\x03]\x8c\xa4\xf8\U000917b7\x8d\x91\x9bq\xac7\x89\xb5\xfdǜ3\x9f?\xff\xceX\xefw&\xcex\xf3\x19\xa8\xeft\xe7$\x83\xa3\x16\x9bC\xe3\xb0$\x84\xd0\xce\xf4ދ \xeb\x83>us\x8d\xf8n0\xe4\xcc\xc6\xe1̻߽\xb9\xfa\xf6\xaa\xf8\xb3z^,F\xfdƱ+\x10N%\xf7\xd8e\xe3\xca\xdf\x01\x00\x00\xff\xff\xec\xa0\xe0\xa1k\r\x00\x00"), } diff --git a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml index 324a98181..9f873afad 100644 --- a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml +++ b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml @@ -125,14 +125,6 @@ spec: description: SourcePVC is the name of the PVC which the snapshot is taken for. type: string - uploaderConfig: - description: UploaderConfig specifies the configuration for the uploader. - properties: - parallelFilesUpload: - description: ParallelFilesUpload is the number of files parallel - uploads to perform when using the uploader. - type: integer - type: object required: - backupStorageLocation - operationTimeout diff --git a/config/crd/v2alpha1/crds/crds.go b/config/crd/v2alpha1/crds/crds.go index f68d2ce64..acbdbed1f 100644 --- a/config/crd/v2alpha1/crds/crds.go +++ b/config/crd/v2alpha1/crds/crds.go @@ -30,7 +30,7 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcY_s\xe3\xb8\r\x7fϧ\xc0l\x1f\xf2\xb2Rn\xaf\x9dN\xc7o\xbbN;\x93\xe9m\xea\xb9\xec\xe4\x9d\x12a\x99\x17\x8adI\xc8i\xda\xe9w\uf014d\xfd\xa1\xe3d\xafwz3\t\x82?\xfc\x00\x02 ]\x14ŕp\xea\x11}P\xd6l@8\x85\xff\"4\xfc+\x94O\x7f\t\xa5\xb27\xc7OWO\xca\xc8\rl\xbb@\xb6\xfd\x19\x83\xed|\x8d\xb7\xb8WF\x91\xb2\xe6\xaaE\x12R\x90\xd8\\\x01\bc,\t\x1e\x0e\xfc\x13\xa0\xb6\x86\xbc\xd5\x1a}Ѡ)\x9f\xba\n\xabNi\x89>*\x1f\xb6>\xfeP~\xfa\xb1\xfc\xe1\n\xc0\x88\x167\xc0\xfa\xa4}6\xda\n\x19\xca#j\xf4\xb6T\xf6*8\xacYq\xe3m\xe76p\x9aH\v\xfbM\x13\xe0[A\xe2\xb6\xd7\x11\x87\xb5\n\xf4\xf7\xd5\xd4O*P\x9cv\xba\xf3B/\xf6\x8e3A\x99\xa6\xd3\xc2\xcf\xe7\xae\x00Bm\x1dn\xe0\x9e\xb7v\xa2F\x1e\xebm\x8aP\n\x10RF\x96\x84\xdeye\b\xfd\xd6\xea\xae\x1d\xd8)@b\xa8\xbdr\x14Y\x98\u0082@\x82\xba\x00\xa1\xab\x0f \x02\xdc\xe3\xf3͝\xd9y\xdbx\f\t\x16\xc0/\xc1\x9a\x9d\xa0\xc3\x06\xca$^\xba\x83\b\xd8\xcf&*\x1f\xe2D?D/\x8c7\x90W\xa6\xc9!\xf8\xa6Z\x04\xd9\xf9\xe8B\xb6\xbbF\xa0\x83\nsh\xcf\"0\xa0\xc7(Q%\t\x8e^P\xec;볮sX\x97I\xb6W6\xe8Z\xf8o\xbe\xd1\xff=\xb6j\x8f\"\x1b[C\xaa)\xa3\x84\xb2&\x1f`\x9f\x1b|SpMI4Vℱ\x19&\x15\xc0y[c\b\xaf\x04<+\x98\xa1\xb8?\r\xac\xa8I\x12\xc7\x1f\x85v\a\xf1)%\x99\xfa\x80\xad\xd8\xf4+\xacC\xf3yw\xf7\xf8LJ\xd90\xbc\x920DM\x813\x05\xc3wޒ\xad\xad\x86\n\xe9\x19\xd1$\u05f7\xf6\x88\x9e\xf3\\\xa3L\x185r֖S\x81S\xce\xe6\xf8\x8e\xfax6Mz\x8c\xd1\xc3\x00\xfd\xd4\xfb\xc0{:\xf4\xa4\x86,\xdc\xeb>\x15\x98\xc9\xe8\u008ek65I\x81\xe4ʂɌ>\x97\xa2\xec\xd9I\xceR\x01<:\x8f\x01\r\xcd!\xf4\xdc\xedA\x18\xb0\xd5/XS\t\x0f\xe8Y\r\x84\x83\xed\xb4d\xe3\x8e\xe8\t<ֶ1\xeaߣ\xee\x00d\xe3\xa6Z\x10\xf6%\xe1\xf4\xc5\xdcm\x84\x86\xa3\xd0\x1d~\x8c\x94\xb5\xe2\x05<\xf2.Й\x89\xbe(\x12J\xf8\xca<)\xb3\xb7\x1b8\x10\xb9\xb0\xb9\xb9i\x14\r\x85\xb5\xb6m\xdb\x19E/7\x91oUud}\xb8\x91xD}\x13TS\b_\x1f\x14aM\x9d\xc7\x1b\xe1T\x11\xa1\x9bX\\\xcbV\xfe\xc1\xf7\xa58\\ϰ\xaeb-}\xb1&\xbe\xe2\x01.\x8c\x1c\xe8\xa2_\x9a\xac8\x11\xcdC\xcc\xce\xcf\x7f}\xf8\x06\xc3\xd6\xd1\x19K\xf6#暈\xe1\xe4\x02&L\x99=\xfa\xe4Ľ\xb7mԉF:\xab\f\xc5\x1f\xb5Vh\x96\xf4\x87\xaej\x15\xb1\xdf\xff\xd9a \xf6U\t\xdb\xd8m@\x85\xd09>Ⲅ;\x03[Ѣފ\x80\xbf\xb9\x03\x98\xe9P0\xb1os\xc1\xb4QZ\n'\xd6&\x13C\xa7s\xc6_ӓ\xff\xe0\xb0f\xd71{\xbcL\xedU_\x01\xf8\xf8\x8a\x99l9S\x99?\xb2\xfce\xab\xc0Rh\x81\xe9Kn\xcd\x00\xccLrm_\x8eB\x92\\)\x05\xd0gK\x98Gg\x83\"\xeb_N\x85\xac\\i8\xe3\x00\xfejaj\xd4\x17,\xd9F!PF2\x938\xc6\x1d\xa7\x88\xa4 b\xb2\xa6\xb1|.\xce\x13\x9c\xbe;\xe2U\x1c\xa8\x01\x89m2\xd9\x1a\xa3\f\x9c:<\x98vrK\xcb*k5\x8ae\xde\xe3\xd8\xfa\xcaIzk\xcd^5k\x1b\xa7\xcd\xe89\xc7_\xa0/\x13\x86\x93-\xd9\n\x8e9FR\xc4zQ\f\x01ɉw\xaf\x9a\xce\xe726\x7f{\x85Z\x86s\xbe\\\x9d\x8f\xc1\xe0\xb8\xcb\x05w\x8e(\x87\xe3ї\x97I\xcd#\x1b\xf3H\x88\x8d&Of\x10\xa6\x10,\xe1n?Ѩ\x02|\xf8\x00\xd6Çt\x19\xf9\xf01\x85k\xa74\x15jZx3\x1a\x9f\x95\xd6þ\xef\x8a\xe2\xb1\xfar\x03d;\xba@\xc0?\x16\xe2\v\x1e\x88;\xb3h;Yx\x16\x8a\xc6r\x97\xc1B\x85{N\xb1\x1e\xa9\xf3\x86O\x02z\xcf)'D\x95\xb6\xa3w\x19\x15\x8cp\xe1`\xe9\xee\xf6\x829\x0f\xa3\xe0\x90]\xeen\x87\xdc\xf2\x18\xbd0\xa6\x98^\x12\xc8\xe6\x1c\x8aC;#c1z\x1f\xdaX\x01ǫ\xdf%\xc8s\xe9\x01\xb7\xf5\xaaQ\xdcV\x98q\xe6\x94\xf2\x8e|U\xcc\x05\xa2\n\xd1>\x94й\x04\x9cS\fW\xd7\nA\xaa\xfd\x1e=\x1aJ\xf55m\xbc{\xdc^\x87\xd3&9\x9d\xfb\t\x86\xd8a\xb5\xc29\x94ܱ\xb3g{\xa2\xdeE\x11\t\xdf =F3.\xf0\xf3m\":\x90Õ\x9b\xafW\\\bz\xef&\x8d\xb0{\xdcr\a\x961c\xf7\xb8Fx\xbe\xcaAߊ\x9f\xf1\xe0\n\xe5\xca\x7f=\x9e\u05c8\xbd\x90N\x01\xdc\xf1\r;\xef\x1es\x85t\xa4\x03\xe8 \x88%\xfa\xab\x13T/Y\x9d0\x9c\x8fޝ߇\xb7~\x13\xe0\xed\xab\x88\xb7K\xc8g\xf0V/\xbf\x1a2\x17o\xe5Q\xaeQ\x17\xafx\xae\x00w\xcc\x0e\xd6o/Q\xf9\x9d\x8b|w\xb5\x90Y\xa6\xf8\xc5\xf4)Y.'\xe6\x99f1;=\x92ojC\xe3\xe5\xf6\xad\x8dhz\xb2\xea\xdd^w>\xa6\xa1\xfe!\x8boe\xdfՊ\xd6\xe9\thzۿԾ\xadW\xc4\xfb\x9e\x97\x93z'ƫl|r\x18ޙr\xfd\xdbI_Z\x1a\xd3#\xabC\txD\x03\xdcj\v\xa5Q\x0e:C\t߸\x1b\x8f\x17\x9f\xeb\xe5\x15)\xf2\xdd+\x8ae\x97{\xa6\f\xe8\xf5\xbaὉ\xaf;\x05\xabXI\x98NkQi\xdc\x00\xf9\xee\\\xff\x98=(-\x86 \x9aK\x89\xfak\x92JW\xc5~\t\x88\x8a\x9b\x8aeO{\x1dz߿\xabh\x18+/a\xb8\xb72\x020\xdf\xf1~\xf3.,\xb1\a\xbf\x00f\xc72\xb9\x98\x1f\xa1\xbd~=@ӵ\xb9\xcct\x8fϙ\xd1\xcfu\x8d.\x97-\v\xd8yt\xc2g\xa7V\x0f\xc7\xd3\xc9t\xd9\xc9%\xcea.\xabs|\x99\xcd\xcc\xfd-\x1e\x86w1\xdd\xe3\xbbD\xf6p;:X=\x1c\xe6\xf8xj\xba\xb6Bό\xc7\xe7ف\xfa!Kf\x0e\xa00r沓\x86\xb1'\x8c\xaa\xf8$s\x95J\x17\xb8\xa1K\x96*8-rEv\xb0d־\x9c\x0e\xc8\xea\xfd\xec\xbd\xfd\xca\xf8\x98\x9d/¹\x17\xe9\x9c\x17\xa6oˋ\xf9\xf1\x91\xfa\xb7\xd9\xe1\x95\v\xdd\xfcO\x83K-\xf5L\xf8R\x82\xef\xff\xafȥ\xf7i\xa6^\xe7\xe5\xf96\xbfgJ\xce\x12\xb5\x1a\x8c\xc8\xe5Dw\xff\xac2\x1d\xe9\xaa\xf1\xb1p\x03\xff\xf9\xef\xd5\xff\x02\x00\x00\xff\xffG\x0e\xcf\xec\xfa\x1b\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcYK\x93۸\x11\xbeϯ\xe8r\x0e\xbe\x8c8\xebM*\x95\xd2\xcd\xd6d\xabTYOT\x96w\xee\x10\xd9\"\xb1\x03\x02\f\x1e\x9aLR\xf9\xef\xa9\x06\b\n$\xa1\x97c\x87\x87\xa9\x11\x80nt7\xd0_?\xb0X,\xeeXǟQ\x1b\xae\xe4\x12X\xc7\xf1\x9f\x16%\xfd2\xc5\xcb_L\xc1\xd5\xc3\xe1\xc3\xdd\v\x97\xd5\x12V\xceX\xd5~A\xa3\x9c.\xf1\x11\xf7\\r˕\xbckѲ\x8aY\xb6\xbc\x03`R*\xcbh\xd8\xd0O\x80RI\xab\x95\x10\xa8\x175\xca\xe2\xc5\xedp縨P{\xe6q\xeb\xc3OŇ\x9f\x8b\x9f\xee\x00$kq\t\xc4\xcfuB\xb1\xca\x14\a\x14\xa8U\xc1՝\xe9\xb0$\xb6\xb5V\xae[\xc2q\"\x90\xf5[\x06q\x1f\x99e\xbfy\x0e~Ppc\xff6\x99\xf8\x95\x1b\xeb';\xe14\x13\xa3]\xfd\xb8\xe1\xb2v\x82\xe9t\xe6\x0e\xc0\x94\xaa\xc3%<і\x1d+\x91\xc6zM\xbc\b\v`U\xe5m\xc3\xc4FsiQ\xaf\x94pm\xb4\xc9\x02*4\xa5\xe6\x9d\xf5\xba\x1f\x05\x02c\x99u\x06\x8c+\x1b`\x06\x9e\xf0\xf5a-7Z\xd5\x1aM\x10\t\xe0w\xa3\xe4\x86\xd9f\tEX^t\r3\xd8\xcf\x06\xf3m\xfdD?d\xdfHZc5\x97un\xff\xaf\xbcE\xa8\x9c\xf6\xc7F:\x97\b\xb6\xe1&\x15\xec\x95\x19\x12N[\xacN\x8a\xe1牙\xb1\xac\xed\xa6\xf2$\xa4A\xa0\x8aỶ\xb3Rm'\xd0b\x05\xbb7\x8bQ\x89\xbd\xd2-\xb3K\xe0\xd2\xfe\xf9O\xa7-ћ\xaa\xf0\xa4\x8fJ\x8e\xcd\xf2\x89F!\x19\x0e\x92\xd0\tը\xb3\xb6Q\x96\x89\xffE\x10K\f>%\xf4A\x92\xc07\x1d\xbf(\n]7P{\xb0\r\xc2'V\xbe\xb8\x0e\xb6ViV#\xfc\xaa\xcapx\xaf\r\xea\xfe\xf0va\x89i\x94\x13\x15\xec\xa2\xc6\x00\xc6*\x9d=\xc5\x0e\xcb\"P\xf5|#\xdb\xc9Q\x8e\xf7\xfcΗ\xac\xd4Ȳ\x97,\xa2L\xe1Wp%\xf37\xedc\x8dWݲԚRU8\x98\x0eS\x89\xb8\x81N\xab\x12\x8d9s\xef\x89|$\xc3\xd3q`f\x96\xb0\xe2\xf03\x13]\xc3>\x04\x94)\x1blٲ\xa7P\x1dʏ\x9b\xf5\xf3\x1f\xb7\xa3a8\x89\x19\xac\xb4\x86\xc0\x82Dﴲ\xaaT\x02vh_\x11\xa5\xc7-h\xd5\x015\x81\\ͥ\x01&\xab\x81'\xa4\v\x8ePM\x97\xdc\xf3\xa3\xd90\xd9_'աN\x8f\x1dh\xcb\x0e\xb5\xe5\x11}×\x84\x95dt\xa2\xc4{\xd23\xac\x82\x8a\xe2\t\x06-z,Ū7M8'n@c\xa7Ѡ\xb4c\x11z\xc3\xed\x81IP\xbb߱\xb4\x05lQ\x13\x9bx\xffK%\x0f\xa8-h,U-\xf9\xbf\x06\xde\x06\xac\xf2\x9b\nf\xb1\x0f\a\xc7\xcfc\xb7d\x02\x0eL8\xbc'\xdbA\xcb\xde@#\xed\x02N&\xfc\xfc\x12S\xc0g\xa5\x11\xb8ܫ%4\xd6vf\xf9\xf0Ps\x1b\xc3i\xa9\xda\xd6In\xdf\x1e\xbc\xb9\xf9\xceY\xa5\xcdC\x85\a\x14\x0f\x86\xd7\v\xa6ˆ[,\xad\xd3\xf8\xc0:\xbe\xf0\xa2K\x1fR\x8b\xb6\xfa\x83\xee\x03\xb0y?\x92uv\xd1\xc2\xe7c\xe1\x99\x13\xa0\x90H\xb7\x9c\xf5\xa4A\x8b\xa3\xa1i\x88\xac\xf3\xe5\xafۯ\x10\xb7\xf6\x871\xb5\xbe\xb7\xfb\x91\xd0\x1c\x8f\x80\f\xc6\xe5\x1eu8ĽV\xad牲\xea\x14\x97\xd6\xff(\x05G95\xbfq\xbb\x96[:\xf7\x7f84\x96Ϊ\x80\x95\xcf1`\x87\xe0:\xf2\uea80\xb5\x84\x15kQ\xac\x98\xc1\x1f~\x00di\xb3 \xc3^w\x04iz4]\x1c\xac\x96L\xc4\f\xe7\xc4y\x1d\xdd~\xdbaI\aG\xb6#\"\xbe\xe7}\f \xdfe\xc9\xcab\xc4.\xef\xae\xf4e\xa1\x7f\xbah\"ϧ\x1cM\x14K&\x10\x1b\xa3QX9c\n \xa6!l\xa0\xd1\xd8)í\xd2o\xc48D\xafb\xc6\xe1\x84\xf1\xe9+\x99,Q\\\xd0d\xe5\x17\x01\x97\x15\xd9\x11\x87;G\xf0\x10\x18x\x99\x94\xac\x15\xf9\xc4)\xf3\x86om\x89\x86\xae\xa8AK\x1a\xc9L`\xe1\x12\x8e\xb9\x1d\xa49\xdcT\xab\x9dR\x02\xd9\x14\xefJ÷\x92u\xa6Q\xf6\x82n\xeb=ĕ_\xdf:\xa4\xcdW\xdb\xf5=\xfd\x89\xe3t/\x0e\xbc\xea\x01\x98\x9c\x87\xb2\x9c9\xc8B\x00ZZ\xb4ڮ\xc1\xf4\xe4s#H'\x04\xdb\t\\\x82\xd5n\xae\xd8\xe9kH_d\xbb\x12\xccd\x17L\x14ܦ\xebs\xd7/2\x84ү\xb0\r\x9bB\xcd`q\x8a?\x94\xac'D|HK\xe0\x95\xdb&Ky\xe6\xfeA\x9ft\xb1\x1a\xafV(Y\x9eէO\u0082:j\x7fF\x99\xcd\xf3\xca\xeb{I3\x82\xe5o\xd1,\xb0<}\x13g\xba=\x8f\br\xdaM\xa4<\xa5\x9c\"\a#\x90\xc0\n\\w\xbb\xec\xe4\xe1\\c5\x97y1:\xaf\xcc\xf4X\xe9\x13n;\x03w\xe8\xf3\xadϔQ\xad\x94\xdc\xf3z\xbewZ:\x9e\U000d1cea͂F\xb2%Y\x9cb\x04I\xb2\xf0\xc9\xdd\"\x06\x10J\x93\xf6\xbcv\xfa\x94\xeb\xef9\x8a\xca\xdc\xec\xed\x17\xecᅸ\x80a\x83\x121\xda\xf5P\x95\xe4\xaf\xe1B8\xe3+G\x9a\xcc(\x10bJA\x90x\xe4\xc8\r\xbc{\aJû\xd0Qxw\x1f\xe2\x8f\xe3\xc2.x\x9aDg8\xber!\xe2\xbe7\x85\xa5!\x95\xa6BF\xb9K \xfe\xf7\xc9\xf2\x89\x1d,\xd5W^w\xab\xe0\x95q;\xe4\xae9\x04\x8f\xbc\xcc=\xecpO\xf9\x92F봤ІZS\x06a\x85\x1dz7\x97\x84\x1c\xaf\x8er*\xcdkNu\x81\x1cf\x8eyK\x00\x87\x8c\xa4}U\xee\xe1\xca\xe3mA\x99BL\xe2\b\x00\x8f\xec\xc8C\xc3\xe6\x04\xe0Tw\xac\xb6\xeb\fρ\xa2\xea\xfd+\xe3\x9d\x17\xad\xb1y^]e\a\x12%\x83\xd74\xfc\xda\xf0\xb2\x19\x9f۬F\xf0\xb2\xb0\x17\xf49\xeaMb\x86\xd6\xdbi\xd0\x1c\xc9\xfa\xdbh\xf1\xc4OF\b7Թ\x91\xff\\\xa8\xf3\x89J\xc74\x13\x02\xc5/\\\xa0\t\xfb^\x11\x017s\xaa\xc1\xac\xaeݡ&\xc3\xeeir\xd8\xe0D\f웕\xe4\"\x1djJ\xdaB\xb6\xe9L,\xdcNk\x06٦\xd3|6\x03\xdb\xf9ȹȗ\x10\x935S؛L\xa7\x002\x9d\x1a\xfbbvv\U000fceaa\xcc\xf2\x9d\x9b\xeb\n\xadВ\xedϧtZ\xa3\xb4\xb1Q\xab\xf6\xdfTj\x95\xa1ř6\xb1.\x95's\n\xdf\xcb\xd0U\x02\xff,\x96L\xbe\x91\x16ۨ\xb9\x93?\xb2\v\x94\xbe\xb7Bܰ\x02<\xa0\x04\xf2\r\xc6\x05\x85R\xcf\xd2\x14S\x9a<\xbe\r\\\xfa\xb0\x12\xae_\xbc\x8c\xb1\x89\xd4\xf7h\xbe\x12Z\xf8&\xc1{s\x86\xa7\x8fj\xe4\xa9\x19#\xcc!&\xf6g+fq\x91ezU\xb2\x92\x85\xa1!y\xfb\x82ƉL\xc4\xfe\x81\xc9[\xd82Կ&\x9b\xbc\x9d\xafژ\x01\x06:0\xe9q\xfb\\\x11\xfb\xed\x19]\x8bư\xfaR`\xfd\x1cV\x85\xdeSO\x02lG\x89\xcdX\xb4\xf7\xa6w\xb6\x9b\x02\x86T\xd5%\t\x9eT巗7w\x82o\x92\xa4c\xb6\xb9 Ɇ\xd9&\x02\xcc\xde\t\xe1ifiP_\x15퐼\xe9{eC\xbe\xedpI\xcd\x1c\xd3ly\xb6\xd6M\xfa\xa7\xb7U\x06\xa3\xd7\xdcK\x12\x8f\x16_\xc8L\xfaw\xe4\\^\xb2%\x17'`\xf1\x8f\x04\xab\xe9K\xdf\xfd\xf0p\xc8l\xffRQ6L\xd6\xe4\x10\x92\x82\x9b\x0f\x8e9ƳTc\x94X\x8c\xc5\xff\x7f\xe6\x14\xd9\xeb2\x1b\xf4\x92W\t\xef\xbe=\x95\x8e\xb8\xdd\xf02\xb4\x84\x7f\xff\xe7\xee\xbf\x01\x00\x00\xff\xff\xf8I\x957\xdd!\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcYOsۺ\x11\xbf\xfbS줇\\\"\xfa\xe5\xb5\xd3\xe9\xe8\x96\xc8팦/\xae'J}\x87\xc8\x15\x89g\x10`\xf1G\xae\xdb\xe9w\xef,@P \t\x89R\xfaR\x1e<\x16\xfe,v\x17\xbb\xbf\xfd\x01X\xadVw\xac\xe3Ϩ\rWr\r\xac\xe3\xf8O\x8b\x92~\x99\xe2\xe5O\xa6\xe0\xea\xfe\xf8\xf1\xee\x85\xcbj\r\x1bg\xacj\xbf\xa2QN\x97\xf8\x80\a.\xb9\xe5J\u07b5hY\xc5,[\xdf\x010)\x95e\xd4l\xe8'@\xa9\xa4\xd5J\bԫ\x1ae\xf1\xe2\xf6\xb8w\\T\xa8\xbd\xf0\xb8\xf4\xf1\xa7\xe2\xe3\xcf\xc5Ow\x00\x92\xb5\xb8\x06\x92\xe7:\xa1Xe\x8a#\nԪ\xe0\xea\xcetX\x92\xd8Z+\u05ed\xe1\xd4\x11\xa6\xf5K\x06u\x1f\x98e\x7f\xf7\x12|\xa3\xe0\xc6\xfeu\xd2\xf1\v7\xd6wv\xc2i&F\xab\xfav\xc3e\xed\x04\xd3i\xcf\x1d\x80)U\x87kx\xa4%;V\"\xb5\xf5\x96x\x15V\xc0\xaa\xca\xfb\x86\x89'ͥE\xbdQµ\xd1'+\xa8Д\x9aw\xd6\xdb~R\b\x8ce\xd6\x190\xael\x80\x19x\xc4\xd7\xfb\xad|Ҫ\xd6h\x82J\x00\xbf\x1a%\x9f\x98m\xd6P\x84\xe1E\xd70\x83}op\xdf\xcew\xf4M\xf6\x8d\xb45VsY\xe7\xd6\xff\xc6[\x84\xcai\xbfmds\x89`\x1bnR\xc5^\x99!\xe5\xb4\xc5\xea\xac\x1a\xbe\x9f\x84\x19\xcb\xdan\xaaO25(T1\x8b9u6\xaa\xed\x04Z\xac`\xfff1\x1aqP\xbaev\r\\\xda?\xfe\xe1\xbc'zW\x15~ꃒc\xb7|\xa6VH\x9a\x83&\xb4C5\xea\xaco\x94e\xe2\x7fQĒ\x80\xcf\xc9\xfc\xa0I\x90\x9b\xb6/\xaaB\xe1\x06\xea\x00\xb6A\xf8\xcc\xca\x17\xd7\xc1\xce*\xcdj\x84_T\x196\xef\xb5A\xddo\xde>\f1\x8dr\xa2\x82}\xb4\x18\xc0X\xa5\xb3\xbb\xd8aY\x84Y\xbd\xdc(v\xb2\x95\xe35\x7f\xe3 +5\xb2l\x90E\x94)\xfc\b\xaed>\xd2>\xd5xU\x94\xa5ޔ\xaa\xc2\xc1u\x98j\xc4\rtZ\x95h̅\xb8\xa7\xe9#\x1d\x1eO\r3\xb7\x84\x11ǟ\x99\xe8\x1a\xf61\xa0L\xd9`\xcb\xd6\xfd\fա\xfc\xf4\xb4}\xfe\xfdn\xd4\fg1\x83\x95\xd6\x10X\x90\xea\x9dVV\x95J\xc0\x1e\xed+\xa2\xf4\xb8\x05\xad:\xa2&\x90\xab\xb94\xc0d5Ȅt\xc0\t\xaa)Ƚ<\xea\r\x9d}8\xa9\x0eu\xba\xed@Kv\xa8-\x8f\xe8\x1b\xbe\xa4\xac$\xad\x13#ޓ\x9da\x14TTO0X\xd1c)V\xbdk\xc2>q\x03\x1a;\x8d\x06\xa5\x1d\xab\xd0;\xee\x00L\x82\xda\xff\x8a\xa5-`\x87\x9a\xc4\xc4\xf8/\x95<\xa2\xb6\xa0\xb1T\xb5\xe4\xff\x1ad\x1b\xb0\xca/*\x98ž\x1c\x9c>\x8fݒ\t82\xe1\xf0\x03\xf9\x0eZ\xf6\x06\x1ai\x15p2\x91燘\x02\xbe(\x8d\xc0\xe5A\xad\xa1\xb1\xb63\xeb\xfb\xfb\x9a\xdbXNKնNr\xfbv\xef\xdd\xcd\xf7\xce*m\xee+<\xa2\xb87\xbc^1]6\xdcbi\x9d\xc6{\xd6\xf1\x95W]\xfa\x92Z\xb4\xd5\xeft_\x80\xcd\xfb\x91\xae\xb3@\v\x9f\xaf\x85\x17v\x80J\"E9\xeb\xa7\x06+N\x8e\xa6&\xf2\xce\xd7?\xef\xbeA\\\xdao\xc6\xd4\xfb\xde柳\xe6\xb4\x05\xe40.\x0f\xa8\xc3&\x1e\xb4j\xbdL\x94U\xa7\xb8\xb4\xfeG)8ʩ\xfb\x8d۷\xdcҾ\xffá\xb1\xb4W\x05l<ǀ=\x82\xeb(\xbb\xab\x02\xb6\x126\xacE\xb1a\x06\x7f\xf8\x06\x90\xa7͊\x1c{\xdd\x16\xa4\xf4h:8x-\xe9\x88\f\xe7\xcc~\x9d\xd2~\xd7aI\x1bG\xbe\xa3I\xfc\xc0\xfb\x1a@\xb9˒\x91\xc5H\\>]\xe9\xcbB\xfft\xd0D\x9fϹ9Q-\x99@l\xacFa\xe4L(\x80\x98\x96\xb0a\x8e\xc6N\x19n\x95~#\xc1\xa1z\x153\tg\x9cO_\xc9d\x89b\xc1\x92\x8d\x1f\x04\\V\xe4G\x1cb\x8e\xe0!\b\xf0:)Y+ʉs\xee\r\xdf\xd6\xd2\x1c\nQ\x83\x96,\x92\x99\xc2\xc2%\x9c\xb8\x1d\xa4\x1cnj\xd5^)\x81l\x8aw\xa5\xe1;\xc9:\xd3(\xbb`\xdb\xf6\x00q䷷\x0ei\xf1\xcdn\xfb\x81\xfe\xc4v\x8a\x8b#\xafz\x00\xa6\xe4!\x963\aY\b@K\x836\xbb-\x98~\xfa\xdc\t\xd2\t\xc1\xf6\x02\xd7`\xb5\x9b\x1bv>\f\xe9\x8bb7\x82\x99쀉\x81\xbbt|.\xfc\xa2@(\xfd\b۰)\xd4\f\x1e\xa7\xfaCd=\x99\xc4\aZ\x02\xaf\xdc6ٙ\x17\xe2\x0fz\xd2\xc5j\xbcڠdx֞\x9e\x84\x05s\xd4\xe1\x821O\xcf\x1bo\xef\x92e\x04\xcb\xdfcY\x10y>\x12g\xb6=\x8f&䬛hy\xce8E\tF \x81\x15\xb8\xeev\xdd)ù\xc6j\xae\xf3j\xb4_\x99\xee\xb1\xd1g\xd2v\x06\xee\xd0\xf3\xad/Ĩ6J\x1ex=_;=:^ʑ\x8b\xa6͊F\xb2$y\x9cj\x04i\xb2\xf2\xe4n\x15\v\bѤ\x03\xaf\x9d>\x97\xfa\a\x8e\xa227g\xfb\x82?\xbc\x12\v\x186\x18\x11\xab]\x0fU\t\x7f\r\x01\xe1\x8c?9RgƀPS\n\x82ēDn\xe0\xdd;P\x1aޅ\x1b\x85w\x1fB\xfdq\\\xd8\x15OItF\xe2+\x17\"\xae{SY\x1a\xa84\x1dd\x94[\x02\xf1\xbfM\x86O\xfc`\xe9|\xe5m\xb7\n^\x19\xb7\x03w\xcd!x\x94e>\xc0\x1e\x0fė4Z\xa7%\x956Ԛ\x18\x84\xf1\"\x95\xcb`\xfb\x05\xa3LRg\x16\f\x9a\x96$o\x05\xfd?\xc5\xec4\xd13Ƹ\xee6\r=\x85\x1d\xeen\x96\x94\x1c\x8f\x8ez*\xcdkN\xe7\x029\xf4\x9cxK\x00\x87\x8c\xa6\xfd\xa9\xdcÕ\xc7ۂ\x98B$q\x04\x80'q\x94\xa1aq\x02p:wlvی\xccaF\xd5\xe7W&;\x17\xbd\xf1\xf4\xbc\xb9\xca\x0f\xa4J\x06\xaf\xa9\xf9\xb5\xe1e3\u07b7\xd9\x19\xc1\xeb\xc2^\xd0s\xd4\x1b\xd4\xcc\x03\xf5*\xcfX'c\xa6Y6\xe9N\xe3u\xda5\xde\xfal\xef\xd3\xf3\xe6*V\xef/\n\xae\xe3\xf5\xe1\x06\xb0\xf7r\xe9\xb4Fi\xe3\xbd \x1dq\xbf\x83ٗ\xe1F-\xbd3Yb\xc3\xf3\x19\xfe謫\x04mXd\xe8\xfe\xde&\xde\xda\xe5\xf8\xf0I\\\x98\xe9\x8f\xf2$\r+\xc0#J\xa0c\vゐۋ4\xc5tN>\x9d\x06)=\x8a\x85+\xdaxh\x8dw\x16\xfd\x95\xc07\nN\x7f&}o.\xc8\xf4 J\xe9\x97q\xc2<\xa2\xe3u \x9dDWY\xa1W\xd5\xc6lr\x0e\\\xe1+\x1a'2\x05\xe2\ar\x85\xb0d8n\x99,W\xb8|H`\x06\x18\xe8 \xa4\x87\x89Kg\xa6\xef'\x10-\x1a\xc3\xea%\x1c\xff\x12F\x85\xab\x8e~\n\xb0=\xd5ѱj\xefM\x9fl7\xc1\xa8TՒ\x06\x8f\xaa\xf2\xcb˛/\x1eoҤc\xb6Y\xd0\xe4\x89\xd9&\x02\xcc\xc1\t\xe1\xe7̪nO\xc2\xf7H\xd9\xf4[\x15_\x7f\xca]R\x8f\xc6\xe4\x00\x10\xaf\t$\x94\xae\xcd\x11\xfaG|ʹ~*K\xeclƲ\x154JDd\xf7\x0f\x13ҵ{\xd4\xe4o\xff\xf41>\x98\xe5\xb2]V\xa3\xedJ\xe6\x0f\x84\xc7K\"\x14\xa6Cg\xb8\x1d\x89|\xb5\xe2\xa6\x13\xec-#8\x1a\x92\xa2M\x92\xb7\xd3[\xe9y<\\\xbeh\x18\x9e\x89\xf2\xe7\xd6\xdc[On\x0f\xd2W\x9bI\xff\xf0\xfc\xf3cV\xb8\x00\x8c1\x93\xb7\x0fW\x12\xf1\xedC\xcc:^\xa1\xb4t\xb68\xbd\x04\x9cX\x9d\xbcx\xb4J\xae\xebn#\xa2\xa3\xc7\xc3%\x8dG\x83\x17\x98I\xffl\x99\xe3%;Jq\x02\x16\x7f'\xbd\x99>,}\x18ީ\x98\xed/\xc6ˆɚ\x12BRq\xf3\xc51'xF5F\xc4b\xac\xfe\xff\x93Sd\xc3e\xd6\xe85\xaf\x12\xd9\xfdmH\xda\xe2\xf6\xc3C\xc4\x1a\xfe\xfd\x9f\xbb\xff\x06\x00\x00\xff\xff?\xb7\xcf\xe0L \x00\x00"), } var CRDs = crds() diff --git a/design/velero-uploader-configuration.md b/design/velero-uploader-configuration.md index b565ba7ef..3bd75cb90 100644 --- a/design/velero-uploader-configuration.md +++ b/design/velero-uploader-configuration.md @@ -25,7 +25,7 @@ type UploaderConfig struct { } ``` -### Integration with Backup CRD +### Integration with Backup & Restore CRD The Velero CLI will support an uploader configuration-related flag, allowing users to set the value when creating backups or restores. This value will be stored in the `UploaderConfig` field within the `Backup` CRD and `Restore` CRD: ```go @@ -122,5 +122,44 @@ Roughly, the process is as follows: 3. Each respective controller within the CRs calls the uploader, and the ParallelFilesUpload from UploaderConfig in CRs is passed to the uploader. 4. When the uploader subsequently calls the Kopia API, it can use the ParallelFilesUpload to set the MaxParallelFileReads parameter, and if the uploader calls the Restic command it would output one warning log for Restic does not support this feature. +### Sparse Option For Kopia & Restic Restore +In many system files, there are numerous zero bytes or empty blocks that still occupy physical storage space. Sparse backup employs a more intelligent approach by only backing up the actual data-containing portions. For those empty blocks or zero bytes, it merely records their presence without actually storing them. This can significantly reduce the storage space required for backups, especially in situations where there is a substantial amount of empty data in large file systems. + +Below are the key steps that should be added to support this new feature. +#### Velero CLI +The Velero CLI will support a `--write-sparse-files` flag, allowing users to set the `WriteSparseFiles` value when creating restores with Restic or Kopia uploader. + +#### UploaderConfig +below the sub-option `WriteSparseFiles` is added into UploaderConfig: + +```go +type UploaderConfig struct { + // +optional + WriteSparseFiles bool `json:"writeSparseFiles,omitempty"` +} +``` + +### Enable Sparse in Restic +For Restic, it could be enabled by pass the flag `--sparse` in creating restore: + +```bash +restic restore create --sparse $snapshotID +``` + +### Enable Sparse in Kopia +For Kopia, it could be enabled this feature by the `WriteSparseFiles` field in the [FilesystemOutput](https://pkg.go.dev/github.com/kopia/kopia@v0.13.0/snapshot/restore#FilesystemOutput). + +```golang +fsOutput := &restore.FilesystemOutput{ + WriteSparseFiles: veleroCfg.WriteSparseFiles, + } +``` + +Roughly, the process is as follows: +1. Users pass the WriteSparseFiles parameter and its value through the Velero CLI. This parameter and its value are stored as a sub-option within UploaderConfig and then placed into the Restore CR. +2. When users perform file system restores, UploaderConfig is passed to the PodVolumeRestore CR. When users use the Data-mover for restores, it is passed to the DataDownload CR. +3. Each respective controller within the CRs calls the uploader, and the WriteSparseFiles from UploaderConfig in CRs is passed to the uploader. +4. When the uploader subsequently calls the Kopia API, it can use the WriteSparseFiles to set the WriteSparseFiles parameter, and if the uploader calls the Restic command it would append `--sparse` flag within the restore command. + ## Alternatives Considered To enhance extensibility further, the option of storing `UploaderConfig` in a Kubernetes ConfigMap can be explored, this approach would allow the addition and modification of configuration options without the need to modify the CRD. \ No newline at end of file diff --git a/pkg/apis/velero/shared/uploader_config.go b/pkg/apis/velero/shared/uploader_config.go deleted file mode 100644 index 347b0cc1e..000000000 --- a/pkg/apis/velero/shared/uploader_config.go +++ /dev/null @@ -1,23 +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. -*/ - -package shared - -// UploaderConfig defines the configuration for the uploader. -type UploaderConfig struct { - // ParallelFilesUpload is the number of files parallel uploads to perform when using the uploader. - ParallelFilesUpload int `json:"parallelFilesUpload,omitempty"` -} diff --git a/pkg/apis/velero/v1/backup_types.go b/pkg/apis/velero/v1/backup_types.go index 628eda393..1f03b89d8 100644 --- a/pkg/apis/velero/v1/backup_types.go +++ b/pkg/apis/velero/v1/backup_types.go @@ -19,8 +19,6 @@ package v1 import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" ) type Metadata struct { @@ -178,10 +176,16 @@ type BackupSpec struct { // +optional DataMover string `json:"datamover,omitempty"` - // UploaderConfig specifies the configuration for the uploader. + // BackupConfig defines the configuration for the backup. // +optional - // +nullable - UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"` + BackupConfig *BackupConfig `json:"backupConfig,omitempty"` +} + +// BackupConfig defines the configuration for the backup. +type BackupConfig struct { + // ParallelFilesUpload is the number of files parallel uploads to perform when using the uploader. + // +optional + ParallelFilesUpload int `json:"parallelFilesUpload,omitempty"` } // BackupHooks contains custom behaviors that should be executed at different phases of the backup. diff --git a/pkg/apis/velero/v1/pod_volume_backup_types.go b/pkg/apis/velero/v1/pod_volume_backup_types.go index 8b0d478ce..affcfaa3b 100644 --- a/pkg/apis/velero/v1/pod_volume_backup_types.go +++ b/pkg/apis/velero/v1/pod_volume_backup_types.go @@ -52,8 +52,10 @@ type PodVolumeBackupSpec struct { // +optional Tags map[string]string `json:"tags,omitempty"` - // UploaderConfig specifies the configuration for the uploader. - UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"` + // UploaderSettings are a map of key-value pairs that should be applied to the + // uploader configuration. + // +optional + UploaderSettings map[string]string `json:"uploaderSettings,omitempty"` } // PodVolumeBackupPhase represents the lifecycle phase of a PodVolumeBackup. diff --git a/pkg/apis/velero/v1/pod_volume_restore_type.go b/pkg/apis/velero/v1/pod_volume_restore_type.go index 84ffc770f..08d518ddc 100644 --- a/pkg/apis/velero/v1/pod_volume_restore_type.go +++ b/pkg/apis/velero/v1/pod_volume_restore_type.go @@ -48,6 +48,11 @@ type PodVolumeRestoreSpec struct { // SourceNamespace is the original namespace for namaspace mapping. SourceNamespace string `json:"sourceNamespace"` + + // UploaderSettings are a map of key-value pairs that should be applied to the + // uploader configuration. + // +optional + UploaderSettings map[string]string `json:"uploaderSettings,omitempty"` } // PodVolumeRestorePhase represents the lifecycle phase of a PodVolumeRestore. diff --git a/pkg/apis/velero/v1/restore_types.go b/pkg/apis/velero/v1/restore_types.go index 3438d8ff4..a811fe26e 100644 --- a/pkg/apis/velero/v1/restore_types.go +++ b/pkg/apis/velero/v1/restore_types.go @@ -123,6 +123,17 @@ type RestoreSpec struct { // +optional // +nullable ResourceModifier *v1.TypedLocalObjectReference `json:"resourceModifier,omitempty"` + + // RestoreConfig specifies the configuration for the restore. + // +optional + RestoreConfig *RestoreConfig `json:"restoreConfig,omitempty"` +} + +// RestoreConfig defines the configuration for the restore. +type RestoreConfig struct { + // WriteSparseFiles is a flag to indicate whether write files sparsely or not. + // +optional + WriteSparseFiles bool `json:"writeSparseFiles,omitempty"` } // RestoreHooks contains custom behaviors that should be executed during or post restore. diff --git a/pkg/apis/velero/v1/zz_generated.deepcopy.go b/pkg/apis/velero/v1/zz_generated.deepcopy.go index 69d727806..ca86bb05c 100644 --- a/pkg/apis/velero/v1/zz_generated.deepcopy.go +++ b/pkg/apis/velero/v1/zz_generated.deepcopy.go @@ -38,6 +38,21 @@ func (in *Backup) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupConfig) DeepCopyInto(out *BackupConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupConfig. +func (in *BackupConfig) DeepCopy() *BackupConfig { + if in == nil { + return nil + } + out := new(BackupConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackupHooks) DeepCopyInto(out *BackupHooks) { *out = *in @@ -381,7 +396,11 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) { *out = new(bool) **out = **in } - out.UploaderConfig = in.UploaderConfig + if in.BackupConfig != nil { + in, out := &in.BackupConfig, &out.BackupConfig + *out = new(BackupConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec. @@ -952,7 +971,13 @@ func (in *PodVolumeBackupSpec) DeepCopyInto(out *PodVolumeBackupSpec) { (*out)[key] = val } } - out.UploaderConfig = in.UploaderConfig + if in.UploaderSettings != nil { + in, out := &in.UploaderSettings, &out.UploaderSettings + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodVolumeBackupSpec. @@ -994,7 +1019,7 @@ func (in *PodVolumeRestore) DeepCopyInto(out *PodVolumeRestore) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -1052,6 +1077,13 @@ func (in *PodVolumeRestoreList) DeepCopyObject() runtime.Object { func (in *PodVolumeRestoreSpec) DeepCopyInto(out *PodVolumeRestoreSpec) { *out = *in out.Pod = in.Pod + if in.UploaderSettings != nil { + in, out := &in.UploaderSettings, &out.UploaderSettings + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodVolumeRestoreSpec. @@ -1115,6 +1147,21 @@ func (in *Restore) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreConfig) DeepCopyInto(out *RestoreConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreConfig. +func (in *RestoreConfig) DeepCopy() *RestoreConfig { + if in == nil { + return nil + } + out := new(RestoreConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestoreHooks) DeepCopyInto(out *RestoreHooks) { *out = *in @@ -1329,6 +1376,11 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) { *out = new(corev1.TypedLocalObjectReference) (*in).DeepCopyInto(*out) } + if in.RestoreConfig != nil { + in, out := &in.RestoreConfig, &out.RestoreConfig + *out = new(RestoreConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSpec. diff --git a/pkg/apis/velero/v2alpha1/data_upload_types.go b/pkg/apis/velero/v2alpha1/data_upload_types.go index 4fb822ff4..98f441064 100644 --- a/pkg/apis/velero/v2alpha1/data_upload_types.go +++ b/pkg/apis/velero/v2alpha1/data_upload_types.go @@ -60,9 +60,6 @@ type DataUploadSpec struct { // OperationTimeout specifies the time used to wait internal operations, // before returning error as timeout. OperationTimeout metav1.Duration `json:"operationTimeout"` - - // UploaderConfig specifies the configuration for the uploader. - UploaderConfig shared.UploaderConfig `json:"uploaderConfig,omitempty"` } type SnapshotType string diff --git a/pkg/apis/velero/v2alpha1/zz_generated.deepcopy.go b/pkg/apis/velero/v2alpha1/zz_generated.deepcopy.go index 7b85568a6..9a9afaa6d 100644 --- a/pkg/apis/velero/v2alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/velero/v2alpha1/zz_generated.deepcopy.go @@ -236,7 +236,6 @@ func (in *DataUploadSpec) DeepCopyInto(out *DataUploadSpec) { } } out.OperationTimeout = in.OperationTimeout - out.UploaderConfig = in.UploaderConfig } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataUploadSpec. diff --git a/pkg/builder/backup_builder.go b/pkg/builder/backup_builder.go index 038e75350..4c4a1128d 100644 --- a/pkg/builder/backup_builder.go +++ b/pkg/builder/backup_builder.go @@ -302,7 +302,10 @@ func (b *BackupBuilder) DataMover(name string) *BackupBuilder { // ParallelFilesUpload sets the Backup's uploader parallel uploads func (b *BackupBuilder) ParallelFilesUpload(parallel int) *BackupBuilder { - b.object.Spec.UploaderConfig.ParallelFilesUpload = parallel + if b.object.Spec.BackupConfig == nil { + b.object.Spec.BackupConfig = &velerov1api.BackupConfig{} + } + b.object.Spec.BackupConfig.ParallelFilesUpload = parallel return b } diff --git a/pkg/builder/restore_builder.go b/pkg/builder/restore_builder.go index bad4327e9..b286af4b4 100644 --- a/pkg/builder/restore_builder.go +++ b/pkg/builder/restore_builder.go @@ -171,3 +171,9 @@ func (b *RestoreBuilder) ItemOperationTimeout(timeout time.Duration) *RestoreBui b.object.Spec.ItemOperationTimeout.Duration = timeout return b } + +// WriteSparseFiles sets the Restore's uploader write sparse files +func (b *RestoreBuilder) WriteSparseFiles(val bool) *RestoreBuilder { + b.object.Spec.RestoreConfig.WriteSparseFiles = val + return b +} diff --git a/pkg/cmd/cli/nodeagent/server.go b/pkg/cmd/cli/nodeagent/server.go index 835b899c3..0881d194f 100644 --- a/pkg/cmd/cli/nodeagent/server.go +++ b/pkg/cmd/cli/nodeagent/server.go @@ -194,6 +194,7 @@ func newNodeAgentServer(logger logrus.FieldLogger, factory client.Factory, confi Scheme: scheme, NewCache: cache.BuilderWithOptions(cacheOption), }) + if err != nil { cancelFunc() return nil, err diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index 45e587623..6b1252539 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -98,6 +98,7 @@ type CreateOptions struct { AllowPartiallyFailed flag.OptionalBool ItemOperationTimeout time.Duration ResourceModifierConfigMap string + WriteSparseFiles flag.OptionalBool client kbclient.WithWatch } @@ -109,6 +110,7 @@ func NewCreateOptions() *CreateOptions { RestoreVolumes: flag.NewOptionalBool(nil), PreserveNodePorts: flag.NewOptionalBool(nil), IncludeClusterResources: flag.NewOptionalBool(nil), + WriteSparseFiles: flag.NewOptionalBool(nil), } } @@ -146,6 +148,9 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) { flags.BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait for the operation to complete.") flags.StringVar(&o.ResourceModifierConfigMap, "resource-modifier-configmap", "", "Reference to the resource modifier configmap that restore will use") + + f = flags.VarPF(&o.WriteSparseFiles, "write-sparse-files", "", "Whether to write sparse files when restoring volumes using restic or kopia") + f.NoOptDefVal = cmd.FALSE } func (o *CreateOptions) Complete(args []string, f client.Factory) error { @@ -318,6 +323,9 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { ItemOperationTimeout: metav1.Duration{ Duration: o.ItemOperationTimeout, }, + RestoreConfig: &api.RestoreConfig{ + WriteSparseFiles: boolptr.IsSetToTrue(o.WriteSparseFiles.Value), + }, }, } diff --git a/pkg/cmd/cli/restore/create_test.go b/pkg/cmd/cli/restore/create_test.go index 27f78416d..7409076fa 100644 --- a/pkg/cmd/cli/restore/create_test.go +++ b/pkg/cmd/cli/restore/create_test.go @@ -84,6 +84,7 @@ func TestCreateCommand(t *testing.T) { includeClusterResources := "true" allowPartiallyFailed := "true" itemOperationTimeout := "10m0s" + writeSparseFiles := "false" flags := new(pflag.FlagSet) o := NewCreateOptions() @@ -106,7 +107,7 @@ func TestCreateCommand(t *testing.T) { flags.Parse([]string{"--include-cluster-resources", includeClusterResources}) flags.Parse([]string{"--allow-partially-failed", allowPartiallyFailed}) flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout}) - + flags.Parse([]string{"--write-sparse-files", writeSparseFiles}) client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) f.On("Namespace").Return(mock.Anything) @@ -142,7 +143,7 @@ func TestCreateCommand(t *testing.T) { require.Equal(t, includeClusterResources, o.IncludeClusterResources.String()) require.Equal(t, allowPartiallyFailed, o.AllowPartiallyFailed.String()) require.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String()) - + require.Equal(t, writeSparseFiles, o.WriteSparseFiles.String()) }) t.Run("create a restore from schedule", func(t *testing.T) { diff --git a/pkg/cmd/const.go b/pkg/cmd/const.go index 7f4459899..806fff07b 100644 --- a/pkg/cmd/const.go +++ b/pkg/cmd/const.go @@ -1,3 +1,4 @@ package cmd var TRUE string = "true" +var FALSE string = "false" diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index b47d732d3..8de3da75d 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -88,7 +88,7 @@ func DescribeBackup( DescribeResourcePolicies(d, backup.Spec.ResourcePolicy) } - if backup.Spec.UploaderConfig.ParallelFilesUpload > 0 { + if backup.Spec.BackupConfig != nil && backup.Spec.BackupConfig.ParallelFilesUpload > 0 { d.Println() DescribeUploaderConfig(d, backup.Spec) } @@ -138,7 +138,7 @@ func DescribeResourcePolicies(d *Describer, resPolicies *v1.TypedLocalObjectRefe // DescribeUploaderConfig describes uploader config in human-readable format func DescribeUploaderConfig(d *Describer, spec velerov1api.BackupSpec) { d.Printf("Uploader config:\n") - d.Printf("\tParallel files upload:\t%d\n", spec.UploaderConfig.ParallelFilesUpload) + d.Printf("\tParallel files upload:\t%d\n", spec.BackupConfig.ParallelFilesUpload) } // DescribeBackupSpec describes a backup spec in human-readable format. diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index c64c1a88a..00812cda5 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -177,6 +177,11 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel d.Println() d.Printf("Preserve Service NodePorts:\t%s\n", BoolPointerString(restore.Spec.PreserveNodePorts, "false", "true", "auto")) + if restore.Spec.RestoreConfig != nil && restore.Spec.RestoreConfig.WriteSparseFiles { + d.Println() + d.Printf("Write Sparse Files:\t%T\n", restore.Spec.RestoreConfig.WriteSparseFiles) + } + d.Println() describeRestoreItemOperations(ctx, kbClient, d, restore, details, insecureSkipTLSVerify, caCertFile) diff --git a/pkg/cmd/util/output/schedule_describer.go b/pkg/cmd/util/output/schedule_describer.go index d02680206..70c397cc0 100644 --- a/pkg/cmd/util/output/schedule_describer.go +++ b/pkg/cmd/util/output/schedule_describer.go @@ -48,7 +48,7 @@ func DescribeSchedule(schedule *v1.Schedule) string { DescribeResourcePolicies(d, schedule.Spec.Template.ResourcePolicy) } - if schedule.Spec.Template.UploaderConfig.ParallelFilesUpload > 0 { + if schedule.Spec.Template.BackupConfig != nil && schedule.Spec.Template.BackupConfig.ParallelFilesUpload > 0 { d.Println() DescribeUploaderConfig(d, schedule.Spec.Template) } diff --git a/pkg/controller/data_download_controller.go b/pkg/controller/data_download_controller.go index bf4299ea4..235846423 100644 --- a/pkg/controller/data_download_controller.go +++ b/pkg/controller/data_download_controller.go @@ -335,7 +335,7 @@ func (r *DataDownloadReconciler) runCancelableDataPath(ctx context.Context, fsRe } log.WithField("path", path.ByPath).Info("fs init") - if err := fsRestore.StartRestore(dd.Spec.SnapshotID, path); err != nil { + if err := fsRestore.StartRestore(dd.Spec.SnapshotID, path, dd.Spec.DataMoverConfig); err != nil { return r.errorOut(ctx, dd, err, fmt.Sprintf("error starting data path %s restore", path.ByPath), log) } diff --git a/pkg/controller/data_upload_controller.go b/pkg/controller/data_upload_controller.go index feb1e9866..46cfdd493 100644 --- a/pkg/controller/data_upload_controller.go +++ b/pkg/controller/data_upload_controller.go @@ -344,7 +344,7 @@ func (r *DataUploadReconciler) runCancelableDataUpload(ctx context.Context, fsBa tags := map[string]string{ velerov1api.AsyncOperationIDLabel: du.Labels[velerov1api.AsyncOperationIDLabel], } - if err := fsBackup.StartBackup(path, fmt.Sprintf("%s/%s", du.Spec.SourceNamespace, du.Spec.SourcePVC), "", false, tags, du.Spec.UploaderConfig); err != nil { + if err := fsBackup.StartBackup(path, fmt.Sprintf("%s/%s", du.Spec.SourceNamespace, du.Spec.SourcePVC), "", false, tags, *du.Spec.DataMoverConfig); err != nil { return r.errorOut(ctx, du, err, "error starting data path backup", log) } diff --git a/pkg/controller/data_upload_controller_test.go b/pkg/controller/data_upload_controller_test.go index fd35cfe7d..1ef10bf7a 100644 --- a/pkg/controller/data_upload_controller_test.go +++ b/pkg/controller/data_upload_controller_test.go @@ -45,7 +45,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/builder" @@ -284,7 +283,7 @@ func (f *fakeDataUploadFSBR) Init(ctx context.Context, bslName string, sourceNam return nil } -func (f *fakeDataUploadFSBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfigs shared.UploaderConfig) error { +func (f *fakeDataUploadFSBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfigs map[string]string) error { du := f.du original := f.du.DeepCopy() du.Status.Phase = velerov2alpha1api.DataUploadPhaseCompleted @@ -294,7 +293,7 @@ func (f *fakeDataUploadFSBR) StartBackup(source datapath.AccessPoint, realSource return nil } -func (f *fakeDataUploadFSBR) StartRestore(snapshotID string, target datapath.AccessPoint) error { +func (f *fakeDataUploadFSBR) StartRestore(snapshotID string, target datapath.AccessPoint, uploaderConfigs map[string]string) error { return nil } diff --git a/pkg/controller/pod_volume_backup_controller.go b/pkg/controller/pod_volume_backup_controller.go index cebe777c1..2d4356b4f 100644 --- a/pkg/controller/pod_volume_backup_controller.go +++ b/pkg/controller/pod_volume_backup_controller.go @@ -178,7 +178,7 @@ func (r *PodVolumeBackupReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } - if err := fsBackup.StartBackup(path, "", parentSnapshotID, false, pvb.Spec.Tags, pvb.Spec.UploaderConfig); err != nil { + if err := fsBackup.StartBackup(path, "", parentSnapshotID, false, pvb.Spec.Tags, pvb.Spec.UploaderSettings); err != nil { return r.errorOut(ctx, &pvb, err, "error starting data path backup", log) } diff --git a/pkg/controller/pod_volume_backup_controller_test.go b/pkg/controller/pod_volume_backup_controller_test.go index 8e0a5dcf9..852e8d388 100644 --- a/pkg/controller/pod_volume_backup_controller_test.go +++ b/pkg/controller/pod_volume_backup_controller_test.go @@ -37,7 +37,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/datapath" @@ -104,7 +103,7 @@ func (b *fakeFSBR) Init(ctx context.Context, bslName string, sourceNamespace str return nil } -func (b *fakeFSBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfigs shared.UploaderConfig) error { +func (b *fakeFSBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfigs map[string]string) error { pvb := b.pvb original := b.pvb.DeepCopy() @@ -116,7 +115,7 @@ func (b *fakeFSBR) StartBackup(source datapath.AccessPoint, realSource string, p return nil } -func (b *fakeFSBR) StartRestore(snapshotID string, target datapath.AccessPoint) error { +func (b *fakeFSBR) StartRestore(snapshotID string, target datapath.AccessPoint, uploaderConfigs map[string]string) error { return nil } diff --git a/pkg/controller/pod_volume_restore_controller.go b/pkg/controller/pod_volume_restore_controller.go index 34d6c6551..94f0e8d3c 100644 --- a/pkg/controller/pod_volume_restore_controller.go +++ b/pkg/controller/pod_volume_restore_controller.go @@ -147,7 +147,7 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req return c.errorOut(ctx, pvr, err, "error to initialize data path", log) } - if err := fsRestore.StartRestore(pvr.Spec.SnapshotID, volumePath); err != nil { + if err := fsRestore.StartRestore(pvr.Spec.SnapshotID, volumePath, pvr.Spec.UploaderSettings); err != nil { return c.errorOut(ctx, pvr, err, "error starting data path restore", log) } diff --git a/pkg/datapath/file_system.go b/pkg/datapath/file_system.go index fcbc220c4..60a0c8347 100644 --- a/pkg/datapath/file_system.go +++ b/pkg/datapath/file_system.go @@ -24,7 +24,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/repository" repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" @@ -130,14 +129,14 @@ func (fs *fileSystemBR) Close(ctx context.Context) { fs.log.WithField("user", fs.jobName).Info("FileSystemBR is closed") } -func (fs *fileSystemBR) StartBackup(source AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfigs shared.UploaderConfig) error { +func (fs *fileSystemBR) StartBackup(source AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfig map[string]string) error { if !fs.initialized { return errors.New("file system data path is not initialized") } go func() { snapshotID, emptySnapshot, err := fs.uploaderProv.RunBackup(fs.ctx, source.ByPath, realSource, tags, forceFull, - parentSnapshot, source.VolMode, uploaderConfigs, fs) + parentSnapshot, source.VolMode, uploaderConfig, fs) if err == provider.ErrorCanceled { fs.callbacks.OnCancelled(context.Background(), fs.namespace, fs.jobName) @@ -151,13 +150,13 @@ func (fs *fileSystemBR) StartBackup(source AccessPoint, realSource string, paren return nil } -func (fs *fileSystemBR) StartRestore(snapshotID string, target AccessPoint) error { +func (fs *fileSystemBR) StartRestore(snapshotID string, target AccessPoint, uploaderConfigs map[string]string) error { if !fs.initialized { return errors.New("file system data path is not initialized") } go func() { - err := fs.uploaderProv.RunRestore(fs.ctx, snapshotID, target.ByPath, target.VolMode, fs) + err := fs.uploaderProv.RunRestore(fs.ctx, snapshotID, target.ByPath, target.VolMode, uploaderConfigs, fs) if err == provider.ErrorCanceled { fs.callbacks.OnCancelled(context.Background(), fs.namespace, fs.jobName) diff --git a/pkg/datapath/file_system_test.go b/pkg/datapath/file_system_test.go index eb3ad7a23..6efb58458 100644 --- a/pkg/datapath/file_system_test.go +++ b/pkg/datapath/file_system_test.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/uploader/provider" providerMock "github.com/vmware-tanzu/velero/pkg/uploader/provider/mocks" @@ -101,7 +100,7 @@ func TestAsyncBackup(t *testing.T) { fs.initialized = true fs.callbacks = test.callbacks - err := fs.StartBackup(AccessPoint{ByPath: test.path}, "", "", false, nil, shared.UploaderConfig{}) + err := fs.StartBackup(AccessPoint{ByPath: test.path}, "", "", false, nil, make(map[string]string)) require.Equal(t, nil, err) <-finish @@ -179,12 +178,12 @@ func TestAsyncRestore(t *testing.T) { t.Run(test.name, func(t *testing.T) { fs := newFileSystemBR("job-1", "test", nil, "velero", Callbacks{}, velerotest.NewLogger()).(*fileSystemBR) mockProvider := providerMock.NewProvider(t) - mockProvider.On("RunRestore", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(test.err) + mockProvider.On("RunRestore", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(test.err) fs.uploaderProv = mockProvider fs.initialized = true fs.callbacks = test.callbacks - err := fs.StartRestore(test.snapshot, AccessPoint{ByPath: test.path}) + err := fs.StartRestore(test.snapshot, AccessPoint{ByPath: test.path}, make(map[string]string)) require.Equal(t, nil, err) <-finish diff --git a/pkg/datapath/mocks/types.go b/pkg/datapath/mocks/types.go index 6096523ce..b3bf4b422 100644 --- a/pkg/datapath/mocks/types.go +++ b/pkg/datapath/mocks/types.go @@ -11,8 +11,6 @@ import ( mock "github.com/stretchr/testify/mock" repository "github.com/vmware-tanzu/velero/pkg/repository" - - shared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" ) // AsyncBR is an autogenerated mock type for the AsyncBR type @@ -44,13 +42,13 @@ func (_m *AsyncBR) Init(ctx context.Context, bslName string, sourceNamespace str return r0 } -// StartBackup provides a mock function with given fields: source, realSource, parentSnapshot, forceFull, tags, uploaderConfig -func (_m *AsyncBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfig shared.UploaderConfig) error { - ret := _m.Called(source, realSource, parentSnapshot, forceFull, tags, uploaderConfig) +// StartBackup provides a mock function with given fields: source, realSource, parentSnapshot, forceFull, tags, dataMoverConfig +func (_m *AsyncBR) StartBackup(source datapath.AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, dataMoverConfig map[string]string) error { + ret := _m.Called(source, realSource, parentSnapshot, forceFull, tags, dataMoverConfig) var r0 error - if rf, ok := ret.Get(0).(func(datapath.AccessPoint, string, string, bool, map[string]string, shared.UploaderConfig) error); ok { - r0 = rf(source, realSource, parentSnapshot, forceFull, tags, uploaderConfig) + if rf, ok := ret.Get(0).(func(datapath.AccessPoint, string, string, bool, map[string]string, map[string]string) error); ok { + r0 = rf(source, realSource, parentSnapshot, forceFull, tags, dataMoverConfig) } else { r0 = ret.Error(0) } @@ -58,13 +56,13 @@ func (_m *AsyncBR) StartBackup(source datapath.AccessPoint, realSource string, p return r0 } -// StartRestore provides a mock function with given fields: snapshotID, target -func (_m *AsyncBR) StartRestore(snapshotID string, target datapath.AccessPoint) error { - ret := _m.Called(snapshotID, target) +// StartRestore provides a mock function with given fields: snapshotID, target, uploaderConfigs +func (_m *AsyncBR) StartRestore(snapshotID string, target datapath.AccessPoint, uploaderConfigs map[string]string) error { + ret := _m.Called(snapshotID, target, uploaderConfigs) var r0 error - if rf, ok := ret.Get(0).(func(string, datapath.AccessPoint) error); ok { - r0 = rf(snapshotID, target) + if rf, ok := ret.Get(0).(func(string, datapath.AccessPoint, map[string]string) error); ok { + r0 = rf(snapshotID, target, uploaderConfigs) } else { r0 = ret.Error(0) } diff --git a/pkg/datapath/types.go b/pkg/datapath/types.go index 9530538d9..9964e9d8f 100644 --- a/pkg/datapath/types.go +++ b/pkg/datapath/types.go @@ -20,7 +20,6 @@ import ( "context" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" "github.com/vmware-tanzu/velero/pkg/repository" "github.com/vmware-tanzu/velero/pkg/uploader" ) @@ -63,10 +62,10 @@ type AsyncBR interface { Init(ctx context.Context, bslName string, sourceNamespace string, uploaderType string, repositoryType string, repoIdentifier string, repositoryEnsurer *repository.Ensurer, credentialGetter *credentials.CredentialGetter) error // StartBackup starts an asynchronous data path instance for backup - StartBackup(source AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, uploaderConfig shared.UploaderConfig) error + StartBackup(source AccessPoint, realSource string, parentSnapshot string, forceFull bool, tags map[string]string, dataMoverConfig map[string]string) error // StartRestore starts an asynchronous data path instance for restore - StartRestore(snapshotID string, target AccessPoint) error + StartRestore(snapshotID string, target AccessPoint, uploaderConfigs map[string]string) error // Cancel cancels an asynchronous data path instance Cancel() diff --git a/pkg/podvolume/backupper.go b/pkg/podvolume/backupper.go index 3239f10f2..202a21005 100644 --- a/pkg/podvolume/backupper.go +++ b/pkg/podvolume/backupper.go @@ -38,6 +38,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/repository" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/kube" + "github.com/vmware-tanzu/velero/pkg/util/uploaderconfig" ) // Backupper can execute pod volume backups of volumes in a pod. @@ -300,7 +301,11 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. } } - volumeBackup := newPodVolumeBackup(backup, pod, volume, repoIdentifier, b.uploaderType, pvc) + volumeBackup, err := newPodVolumeBackup(backup, pod, volume, repoIdentifier, b.uploaderType, pvc) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error creating PodVolumeBackup for volume %s", volumeName)) + continue + } if err := veleroclient.CreateRetryGenerateName(b.crClient, b.ctx, volumeBackup); err != nil { errs = append(errs, err) continue @@ -353,7 +358,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, uploaderType 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, error) { pvb := &velerov1api.PodVolumeBackup{ ObjectMeta: metav1.ObjectMeta{ Namespace: backup.Namespace, @@ -392,7 +397,6 @@ func newPodVolumeBackup(backup *velerov1api.Backup, pod *corev1api.Pod, volume c BackupStorageLocation: backup.Spec.StorageLocation, RepoIdentifier: repoIdentifier, UploaderType: uploaderType, - UploaderConfig: backup.Spec.UploaderConfig, }, } @@ -411,5 +415,15 @@ func newPodVolumeBackup(backup *velerov1api.Backup, pod *corev1api.Pod, volume c pvb.Spec.Tags["pvc-uid"] = string(pvc.UID) } - return pvb + if backup.Spec.BackupConfig != nil { + configJSON, err := uploaderconfig.MarshalToPVBConfig(backup.Spec.BackupConfig) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal backup config") + } + pvb.Spec.UploaderSettings = map[string]string{ + uploaderconfig.PodVolumeBackups: configJSON, + } + } + + return pvb, nil } diff --git a/pkg/podvolume/restorer.go b/pkg/podvolume/restorer.go index d54a7e66c..0d3c935fc 100644 --- a/pkg/podvolume/restorer.go +++ b/pkg/podvolume/restorer.go @@ -38,6 +38,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/repository" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/kube" + "github.com/vmware-tanzu/velero/pkg/util/uploaderconfig" ) type RestoreData struct { @@ -176,7 +177,11 @@ func (r *restorer) RestorePodVolumes(data RestoreData) []error { } } - volumeRestore := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, backupInfo.snapshotID, repoIdentifier, backupInfo.uploaderType, data.SourceNamespace, pvc) + volumeRestore, err := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, backupInfo.snapshotID, repoIdentifier, backupInfo.uploaderType, data.SourceNamespace, pvc) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error creating PodVolumeRestore for volume %s", volume)) + continue + } if err := veleroclient.CreateRetryGenerateName(r.crClient, r.ctx, volumeRestore); err != nil { errs = append(errs, errors.WithStack(err)) @@ -246,7 +251,7 @@ ForEachVolume: return errs } -func newPodVolumeRestore(restore *velerov1api.Restore, pod *corev1api.Pod, backupLocation, volume, snapshot, repoIdentifier, uploaderType, sourceNamespace string, pvc *corev1api.PersistentVolumeClaim) *velerov1api.PodVolumeRestore { +func newPodVolumeRestore(restore *velerov1api.Restore, pod *corev1api.Pod, backupLocation, volume, snapshot, repoIdentifier, uploaderType, sourceNamespace string, pvc *corev1api.PersistentVolumeClaim) (*velerov1api.PodVolumeRestore, error) { pvr := &velerov1api.PodVolumeRestore{ ObjectMeta: metav1.ObjectMeta{ Namespace: restore.Namespace, @@ -285,7 +290,19 @@ func newPodVolumeRestore(restore *velerov1api.Restore, pod *corev1api.Pod, backu // this label is not used by velero, but useful for debugging. pvr.Labels[velerov1api.PVCUIDLabel] = string(pvc.UID) } - return pvr + + if restore.Spec.RestoreConfig != nil { + configJSON, err := uploaderconfig.MarshalToPVRConfig(restore.Spec.RestoreConfig) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal restore config") + } + + pvr.Spec.UploaderSettings = map[string]string{ + uploaderconfig.PodVolumeRestores: configJSON, + } + } + + return pvr, nil } func getVolumesRepositoryType(volumes map[string]volumeBackupInfo) (string, error) { diff --git a/pkg/uploader/kopia/snapshot.go b/pkg/uploader/kopia/snapshot.go index a34a1e553..b89e59feb 100644 --- a/pkg/uploader/kopia/snapshot.go +++ b/pkg/uploader/kopia/snapshot.go @@ -18,6 +18,7 @@ package kopia import ( "context" + "encoding/json" "fmt" "math" "os" @@ -38,7 +39,8 @@ import ( "github.com/kopia/kopia/snapshot/snapshotfs" "github.com/pkg/errors" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/kopia" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" "github.com/vmware-tanzu/velero/pkg/uploader" @@ -105,11 +107,21 @@ func getDefaultPolicy() *policy.Policy { } } -func setupPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snapshot.SourceInfo, uploaderCfg shared.UploaderConfig) (*policy.Tree, error) { +func setupPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snapshot.SourceInfo, uploaderCfg map[string]string) (*policy.Tree, error) { // some internal operations from Kopia code retrieves policies from repo directly, so we need to persist the policy to repo + backupCfg := velerov1api.BackupConfig{} + // currently, we only have one uploader config in one uploader config so we can just loop through it + for configItem, jsonConfig := range uploaderCfg { + err := json.Unmarshal([]byte(jsonConfig), &backupCfg) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse %s uploader config", configItem) + } + break + } + curPolicy := getDefaultPolicy() - if uploaderCfg.ParallelFilesUpload > 0 { - curPolicy.UploadPolicy.MaxParallelFileReads = newOptionalInt(uploaderCfg.ParallelFilesUpload) + if backupCfg.ParallelFilesUpload > 0 { + curPolicy.UploadPolicy.MaxParallelFileReads = newOptionalInt(backupCfg.ParallelFilesUpload) } err := setPolicyFunc(ctx, rep, sourceInfo, curPolicy) @@ -133,7 +145,7 @@ func setupPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snap // Backup backup specific sourcePath and update progress func Backup(ctx context.Context, fsUploader SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, - forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { + forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { if fsUploader == nil { return nil, false, errors.New("get empty kopia uploader") } @@ -229,7 +241,7 @@ func SnapshotSource( forceFull bool, parentSnapshot string, snapshotTags map[string]string, - uploaderCfg shared.UploaderConfig, + uploaderCfg map[string]string, log logrus.FieldLogger, description string, ) (string, int64, error) { @@ -361,7 +373,7 @@ func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sour } // Restore restore specific sourcePath with given snapshotID and update progress -func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, +func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { log.Info("Start to restore...") @@ -384,13 +396,25 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress, return 0, 0, errors.Wrapf(err, "Unable to resolve path %v", dest) } + restoreCfg := velerov1api.RestoreConfig{} + // currently, we only have one uploader config in map so we can just loop through it + for configItem, jsonConfig := range uploaderCfg { + err := json.Unmarshal([]byte(jsonConfig), &restoreCfg) + if err != nil { + return 0, 0, errors.Wrapf(err, "failed to parse %s uploader config", configItem) + } + break + } + fsOutput := &restore.FilesystemOutput{ TargetPath: path, OverwriteDirectories: true, OverwriteFiles: true, OverwriteSymlinks: true, IgnorePermissionErrors: true, + WriteSparseFiles: restoreCfg.WriteSparseFiles, } + log.Debugf("Restore filesystem output %v", fsOutput) err = fsOutput.Init(ctx) if err != nil { diff --git a/pkg/uploader/kopia/snapshot_test.go b/pkg/uploader/kopia/snapshot_test.go index 645434942..f4a751d32 100644 --- a/pkg/uploader/kopia/snapshot_test.go +++ b/pkg/uploader/kopia/snapshot_test.go @@ -35,7 +35,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" repomocks "github.com/vmware-tanzu/velero/pkg/repository/mocks" "github.com/vmware-tanzu/velero/pkg/uploader" uploadermocks "github.com/vmware-tanzu/velero/pkg/uploader/mocks" @@ -97,7 +96,7 @@ func TestSnapshotSource(t *testing.T) { testCases := []struct { name string args []mockArgs - uploaderCfg shared.UploaderConfig + uploaderCfg map[string]string notError bool }{ { @@ -163,7 +162,7 @@ func TestSnapshotSource(t *testing.T) { {methodName: "Upload", returns: []interface{}{manifest, nil}}, {methodName: "Flush", returns: []interface{}{nil}}, }, - uploaderCfg: shared.UploaderConfig{ParallelFilesUpload: 10}, + uploaderCfg: map[string]string{"ParallelFilesUpload": "10"}, notError: true, }, { @@ -646,9 +645,9 @@ func TestBackup(t *testing.T) { var snapshotInfo *uploader.SnapshotInfo var err error if tc.isEmptyUploader { - snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), nil, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.volMode, shared.UploaderConfig{}, tc.tags, &logrus.Logger{}) + snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), nil, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.volMode, make(map[string]string), tc.tags, &logrus.Logger{}) } else { - snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), s.uploderMock, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.volMode, shared.UploaderConfig{}, tc.tags, &logrus.Logger{}) + snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), s.uploderMock, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.volMode, make(map[string]string), tc.tags, &logrus.Logger{}) } // Check if the returned error matches the expected error if tc.expectedError != nil { @@ -787,7 +786,7 @@ func TestRestore(t *testing.T) { repoWriterMock.On("OpenObject", mock.Anything, mock.Anything).Return(em, nil) progress := new(Progress) - bytesRestored, fileCount, err := Restore(context.Background(), repoWriterMock, progress, tc.snapshotID, tc.dest, tc.volMode, logrus.New(), nil) + bytesRestored, fileCount, err := Restore(context.Background(), repoWriterMock, progress, tc.snapshotID, tc.dest, tc.volMode, make(map[string]string), logrus.New(), nil) // Check if the returned error matches the expected error if tc.expectedError != nil { diff --git a/pkg/uploader/provider/kopia.go b/pkg/uploader/provider/kopia.go index 1ae69d21e..6d1dbbf72 100644 --- a/pkg/uploader/provider/kopia.go +++ b/pkg/uploader/provider/kopia.go @@ -30,7 +30,6 @@ import ( "github.com/vmware-tanzu/velero/pkg/uploader/kopia" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" repokeys "github.com/vmware-tanzu/velero/pkg/repository/keys" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" @@ -120,7 +119,7 @@ func (kp *kopiaProvider) RunBackup( forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, - uploaderCfg shared.UploaderConfig, + uploaderCfg map[string]string, updater uploader.ProgressUpdater) (string, bool, error) { if updater == nil { return "", false, errors.New("Need to initial backup progress updater first") @@ -205,6 +204,7 @@ func (kp *kopiaProvider) RunRestore( snapshotID string, volumePath string, volMode uploader.PersistentVolumeMode, + uploaderCfg map[string]string, updater uploader.ProgressUpdater) error { log := kp.log.WithFields(logrus.Fields{ "snapshotID": snapshotID, @@ -228,7 +228,7 @@ func (kp *kopiaProvider) RunRestore( // We use the cancel channel to control the restore cancel, so don't pass a context with cancel to Kopia restore. // Otherwise, Kopia restore will not response to the cancel control but return an arbitrary error. // Kopia restore cancel is not designed as well as Kopia backup which uses the context to control backup cancel all the way. - size, fileCount, err := RestoreFunc(context.Background(), repoWriter, progress, snapshotID, volumePath, volMode, log, restoreCancel) + size, fileCount, err := RestoreFunc(context.Background(), repoWriter, progress, snapshotID, volumePath, volMode, uploaderCfg, log, restoreCancel) if err != nil { return errors.Wrapf(err, "Failed to run kopia restore") diff --git a/pkg/uploader/provider/kopia_test.go b/pkg/uploader/provider/kopia_test.go index c1fc95724..507be4ae5 100644 --- a/pkg/uploader/provider/kopia_test.go +++ b/pkg/uploader/provider/kopia_test.go @@ -34,7 +34,6 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" "github.com/vmware-tanzu/velero/internal/credentials/mocks" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/repository" udmrepo "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" @@ -69,34 +68,34 @@ func TestRunBackup(t *testing.T) { testCases := []struct { name string - hookBackupFunc func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) + hookBackupFunc func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) volMode uploader.PersistentVolumeMode notError bool }{ { name: "success to backup", - hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { + hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { return &uploader.SnapshotInfo{}, false, nil }, notError: true, }, { name: "get error to backup", - hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { + hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { return &uploader.SnapshotInfo{}, false, errors.New("failed to backup") }, notError: false, }, { name: "got empty snapshot", - hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { + hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { return nil, true, errors.New("snapshot is empty") }, notError: false, }, { name: "success to backup block mode volume", - hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { + hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { return &uploader.SnapshotInfo{}, false, nil }, volMode: uploader.PersistentVolumeBlock, @@ -109,7 +108,7 @@ func TestRunBackup(t *testing.T) { tc.volMode = uploader.PersistentVolumeFilesystem } BackupFunc = tc.hookBackupFunc - _, _, err := kp.RunBackup(context.Background(), "var", "", nil, false, "", tc.volMode, shared.UploaderConfig{}, &updater) + _, _, err := kp.RunBackup(context.Background(), "var", "", nil, false, "", tc.volMode, map[string]string{}, &updater) if tc.notError { assert.NoError(t, err) } else { @@ -126,27 +125,27 @@ func TestRunRestore(t *testing.T) { testCases := []struct { name string - hookRestoreFunc func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) + hookRestoreFunc func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) notError bool volMode uploader.PersistentVolumeMode }{ { name: "normal restore", - hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { + hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { return 0, 0, nil }, notError: true, }, { name: "failed to restore", - hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { + hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { return 0, 0, errors.New("failed to restore") }, notError: false, }, { name: "normal block mode restore", - hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { + hookRestoreFunc: func(ctx context.Context, rep repo.RepositoryWriter, progress *kopia.Progress, snapshotID, dest string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { return 0, 0, nil }, volMode: uploader.PersistentVolumeBlock, @@ -160,7 +159,7 @@ func TestRunRestore(t *testing.T) { tc.volMode = uploader.PersistentVolumeFilesystem } RestoreFunc = tc.hookRestoreFunc - err := kp.RunRestore(context.Background(), "", "/var", tc.volMode, &updater) + err := kp.RunRestore(context.Background(), "", "/var", tc.volMode, map[string]string{}, &updater) if tc.notError { assert.NoError(t, err) } else { diff --git a/pkg/uploader/provider/mocks/Provider.go b/pkg/uploader/provider/mocks/Provider.go index 54ba8ac95..7651431b2 100644 --- a/pkg/uploader/provider/mocks/Provider.go +++ b/pkg/uploader/provider/mocks/Provider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.22.1. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -7,8 +7,6 @@ import ( mock "github.com/stretchr/testify/mock" - shared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" - uploader "github.com/vmware-tanzu/velero/pkg/uploader" ) @@ -32,28 +30,28 @@ func (_m *Provider) Close(ctx context.Context) error { } // RunBackup provides a mock function with given fields: ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater -func (_m *Provider) RunBackup(ctx context.Context, path string, realSource string, tags map[string]string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg shared.UploaderConfig, updater uploader.ProgressUpdater) (string, bool, error) { +func (_m *Provider) RunBackup(ctx context.Context, path string, realSource string, tags map[string]string, forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, uploaderCfg map[string]string, updater uploader.ProgressUpdater) (string, bool, error) { ret := _m.Called(ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater) var r0 string var r1 bool var r2 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, shared.UploaderConfig, uploader.ProgressUpdater) (string, bool, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, map[string]string, uploader.ProgressUpdater) (string, bool, error)); ok { return rf(ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater) } - if rf, ok := ret.Get(0).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, shared.UploaderConfig, uploader.ProgressUpdater) string); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, map[string]string, uploader.ProgressUpdater) string); ok { r0 = rf(ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, shared.UploaderConfig, uploader.ProgressUpdater) bool); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, map[string]string, uploader.ProgressUpdater) bool); ok { r1 = rf(ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater) } else { r1 = ret.Get(1).(bool) } - if rf, ok := ret.Get(2).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, shared.UploaderConfig, uploader.ProgressUpdater) error); ok { + if rf, ok := ret.Get(2).(func(context.Context, string, string, map[string]string, bool, string, uploader.PersistentVolumeMode, map[string]string, uploader.ProgressUpdater) error); ok { r2 = rf(ctx, path, realSource, tags, forceFull, parentSnapshot, volMode, uploaderCfg, updater) } else { r2 = ret.Error(2) @@ -62,13 +60,13 @@ func (_m *Provider) RunBackup(ctx context.Context, path string, realSource strin return r0, r1, r2 } -// RunRestore provides a mock function with given fields: ctx, snapshotID, volumePath, volMode, updater -func (_m *Provider) RunRestore(ctx context.Context, snapshotID string, volumePath string, volMode uploader.PersistentVolumeMode, updater uploader.ProgressUpdater) error { - ret := _m.Called(ctx, snapshotID, volumePath, volMode, updater) +// RunRestore provides a mock function with given fields: ctx, snapshotID, volumePath, volMode, uploaderConfig, updater +func (_m *Provider) RunRestore(ctx context.Context, snapshotID string, volumePath string, volMode uploader.PersistentVolumeMode, uploaderConfig map[string]string, updater uploader.ProgressUpdater) error { + ret := _m.Called(ctx, snapshotID, volumePath, volMode, uploaderConfig, updater) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uploader.PersistentVolumeMode, uploader.ProgressUpdater) error); ok { - r0 = rf(ctx, snapshotID, volumePath, volMode, updater) + if rf, ok := ret.Get(0).(func(context.Context, string, string, uploader.PersistentVolumeMode, map[string]string, uploader.ProgressUpdater) error); ok { + r0 = rf(ctx, snapshotID, volumePath, volMode, uploaderConfig, updater) } else { r0 = ret.Error(0) } diff --git a/pkg/uploader/provider/provider.go b/pkg/uploader/provider/provider.go index 5cffc4b32..20a3dc436 100644 --- a/pkg/uploader/provider/provider.go +++ b/pkg/uploader/provider/provider.go @@ -28,7 +28,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/uploader" ) @@ -50,7 +49,7 @@ type Provider interface { forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, - uploaderCfg shared.UploaderConfig, + uploaderCfg map[string]string, updater uploader.ProgressUpdater) (string, bool, error) // RunRestore which will do restore for one specific volume with given snapshot id and return error // updater is used for updating backup progress which implement by third-party @@ -59,6 +58,7 @@ type Provider interface { snapshotID string, volumePath string, volMode uploader.PersistentVolumeMode, + uploaderConfig map[string]string, updater uploader.ProgressUpdater) error // Close which will close related repository Close(ctx context.Context) error diff --git a/pkg/uploader/provider/restic.go b/pkg/uploader/provider/restic.go index 9c19782b9..2f16970a9 100644 --- a/pkg/uploader/provider/restic.go +++ b/pkg/uploader/provider/restic.go @@ -18,6 +18,7 @@ package provider import ( "context" + "encoding/json" "fmt" "os" "strings" @@ -27,7 +28,6 @@ import ( v1 "k8s.io/api/core/v1" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/uploader" @@ -123,7 +123,7 @@ func (rp *resticProvider) RunBackup( forceFull bool, parentSnapshot string, volMode uploader.PersistentVolumeMode, - uploaderCfg shared.UploaderConfig, + uploaderCfg map[string]string, updater uploader.ProgressUpdater) (string, bool, error) { if updater == nil { return "", false, errors.New("Need to initial backup progress updater first") @@ -146,8 +146,18 @@ func (rp *resticProvider) RunBackup( "parentSnapshot": parentSnapshot, }) - if uploaderCfg.ParallelFilesUpload > 0 { - log.Warnf("ParallelFilesUpload is set to %d, but restic does not support parallel file uploads. Ignoring.", uploaderCfg.ParallelFilesUpload) + backupCfg := velerov1api.BackupConfig{} + // currently, we only have one uploader config in one uploader config so we can just loop through it + for configItem, jsonConfig := range uploaderCfg { + err := json.Unmarshal([]byte(jsonConfig), &backupCfg) + if err != nil { + return "", false, errors.Wrapf(err, "failed to parse %s config", configItem) + } + break + } + + if backupCfg.ParallelFilesUpload > 0 { + log.Warnf("ParallelFilesUpload is set to %d, but restic does not support parallel file uploads. Ignoring.", backupCfg.ParallelFilesUpload) } backupCmd := resticBackupCMDFunc(rp.repoIdentifier, rp.credentialsFile, path, tags) @@ -191,6 +201,7 @@ func (rp *resticProvider) RunRestore( snapshotID string, volumePath string, volMode uploader.PersistentVolumeMode, + uploaderCfg map[string]string, updater uploader.ProgressUpdater) error { if updater == nil { return errors.New("Need to initial backup progress updater first") @@ -210,8 +221,34 @@ func (rp *resticProvider) RunRestore( if len(rp.extraFlags) != 0 { restoreCmd.ExtraFlags = append(restoreCmd.ExtraFlags, rp.extraFlags...) } + + extraFlags, err := rp.parseRestoreExtraFlags(uploaderCfg) + if err != nil { + return errors.Wrap(err, "failed to parse uploader config") + } else if len(extraFlags) != 0 { + restoreCmd.ExtraFlags = append(restoreCmd.ExtraFlags, extraFlags...) + } + stdout, stderr, err := restic.RunRestore(restoreCmd, log, updater) - log.Infof("Run command=%s, stdout=%s, stderr=%s", restoreCmd.Command, stdout, stderr) + log.Infof("Run command=%v, stdout=%s, stderr=%s", restoreCmd, stdout, stderr) return err } + +func (rp *resticProvider) parseRestoreExtraFlags(uploaderCfg map[string]string) ([]string, error) { + extraFlags := []string{} + restoreCfg := velerov1api.RestoreConfig{} + // currently, we only have one uploader config in map so we can just loop through it + for configItem, jsonConfig := range uploaderCfg { + err := json.Unmarshal([]byte(jsonConfig), &restoreCfg) + if err != nil { + return extraFlags, errors.Wrapf(err, "failed to parse %s uploader config", configItem) + } + break + } + + if restoreCfg.WriteSparseFiles { + extraFlags = append(extraFlags, "--sparse") + } + return extraFlags, nil +} diff --git a/pkg/uploader/provider/restic_test.go b/pkg/uploader/provider/restic_test.go index 62f289968..389f1ee63 100644 --- a/pkg/uploader/provider/restic_test.go +++ b/pkg/uploader/provider/restic_test.go @@ -20,6 +20,7 @@ import ( "context" "errors" "os" + "reflect" "strings" "testing" @@ -31,13 +32,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/uploader" "github.com/vmware-tanzu/velero/pkg/util" "github.com/vmware-tanzu/velero/pkg/util/filesystem" + "github.com/vmware-tanzu/velero/pkg/util/uploaderconfig" ) func TestResticRunBackup(t *testing.T) { @@ -150,9 +151,9 @@ func TestResticRunBackup(t *testing.T) { } if !tc.nilUpdater { updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: tc.rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(util.VeleroScheme).Build()} - _, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, tc.volMode, shared.UploaderConfig{}, &updater) + _, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, tc.volMode, map[string]string{}, &updater) } else { - _, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, tc.volMode, shared.UploaderConfig{}, nil) + _, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, tc.volMode, map[string]string{}, nil) } tc.rp.log.Infof("test name %v error %v", tc.name, err) @@ -223,9 +224,9 @@ func TestResticRunRestore(t *testing.T) { var err error if !tc.nilUpdater { updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: tc.rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(util.VeleroScheme).Build()} - err = tc.rp.RunRestore(context.Background(), "", "var", tc.volMode, &updater) + err = tc.rp.RunRestore(context.Background(), "", "var", tc.volMode, map[string]string{}, &updater) } else { - err = tc.rp.RunRestore(context.Background(), "", "var", tc.volMode, nil) + err = tc.rp.RunRestore(context.Background(), "", "var", tc.volMode, map[string]string{}, nil) } tc.rp.log.Infof("test name %v error %v", tc.name, err) @@ -411,3 +412,42 @@ func TestNewResticUploaderProvider(t *testing.T) { }) } } + +func TestParseUploaderConfig(t *testing.T) { + rp := &resticProvider{} + + testCases := []struct { + name string + uploaderConfig map[string]string + expectedFlags []string + }{ + { + name: "SparseFilesEnabled", + uploaderConfig: map[string]string{ + uploaderconfig.PodVolumeRestores: `{"WriteSparseFiles": true}`, + }, + expectedFlags: []string{"--sparse"}, + }, + { + name: "SparseFilesDisabled", + uploaderConfig: map[string]string{ + uploaderconfig.PodVolumeRestores: `{"WriteSparseFiles": false}`, + }, + expectedFlags: []string{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result, err := rp.parseRestoreExtraFlags(testCase.uploaderConfig) + if err != nil { + t.Errorf("Test case %s failed with error: %v", testCase.name, err) + return + } + + if !reflect.DeepEqual(result, testCase.expectedFlags) { + t.Errorf("Test case %s failed. Expected: %v, Got: %v", testCase.name, testCase.expectedFlags, result) + } + }) + } +} diff --git a/pkg/util/uploaderconfig/uploaderconfig.go b/pkg/util/uploaderconfig/uploaderconfig.go new file mode 100644 index 000000000..6fba47930 --- /dev/null +++ b/pkg/util/uploaderconfig/uploaderconfig.go @@ -0,0 +1,82 @@ +package uploaderconfig + +import ( + "encoding/json" + + "github.com/pkg/errors" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" +) + +const ( + PodVolumeBackups = "PodVolumeBackups" + PodVolumeRestores = "PodVolumeRestores" + DataUploads = "DataUploads" + DataDownloads = "DataDownloads" +) + +type PVBConfig struct { + ParallelFilesUpload int `json:"parallelFilesUpload,omitempty"` +} + +type PVRConfig struct { + WriteSparseFiles bool `json:"writeSparseFiles,omitempty"` +} + +func MarshalToPVBConfig(backupConfig *velerov1api.BackupConfig) (string, error) { + jsonData, err := json.Marshal(backupConfig) + if err != nil { + return "", errors.Wrap(err, "failed to marshal backup config") + } + + var pvb PVBConfig + err = json.Unmarshal(jsonData, &pvb) + if err != nil { + return "", errors.Wrap(err, "failed to unmarshal backup config") + } + + finalJSONData, err := json.Marshal(pvb) + if err != nil { + return "", errors.Wrap(err, "failed to marshal backup config") + } + + return string(finalJSONData), nil +} + +func MarshalToPVRConfig(restoreConfig *velerov1api.RestoreConfig) (string, error) { + jsonData, err := json.Marshal(restoreConfig) + if err != nil { + return "", errors.Wrap(err, "failed to marshal restore config") + } + + var pvr PVRConfig + err = json.Unmarshal(jsonData, &pvr) + if err != nil { + return "", errors.Wrap(err, "failed to unmarshal restore config") + } + + finalJSONData, err := json.Marshal(pvr) + if err != nil { + return "", errors.Wrap(err, "failed to marshal restore config") + } + + return string(finalJSONData), nil +} + +func ParseBackupConfig(str string) (velerov1api.BackupConfig, error) { + var config velerov1api.BackupConfig + err := json.Unmarshal([]byte(str), &config) + if err != nil { + return velerov1api.BackupConfig{}, err + } + return config, nil +} + +func ParseRestoreConfig(str string) (velerov1api.RestoreConfig, error) { + var config velerov1api.RestoreConfig + err := json.Unmarshal([]byte(str), &config) + if err != nil { + return velerov1api.RestoreConfig{}, err + } + return config, nil +}