From 235e579581d1d2babab78b5fe23d212e88229e9c Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 7 Apr 2026 15:30:24 +0800 Subject: [PATCH 1/2] remove restic for repo Signed-off-by: Lyndon-Li --- .../bases/velero.io_backuprepositories.yaml | 4 +- config/crd/v1/crds/crds.go | 2 +- pkg/apis/velero/v1/backup_repository_types.go | 6 +- .../backup_repository_controller.go | 42 +---- .../backup_repository_controller_test.go | 156 ------------------ pkg/datapath/file_system.go | 8 +- pkg/podvolume/backupper.go | 9 +- pkg/podvolume/backupper_test.go | 2 +- pkg/podvolume/restorer.go | 7 +- pkg/podvolume/util.go | 33 ++-- pkg/repository/manager/manager.go | 6 - pkg/repository/manager/manager_test.go | 16 +- pkg/repository/provider/restic.go | 99 ----------- pkg/repository/restic/repository.go | 147 ----------------- 14 files changed, 32 insertions(+), 505 deletions(-) delete mode 100644 pkg/repository/provider/restic.go delete mode 100644 pkg/repository/restic/repository.go diff --git a/config/crd/v1/bases/velero.io_backuprepositories.yaml b/config/crd/v1/bases/velero.io_backuprepositories.yaml index 19e4ce0dc..a7a2510bd 100644 --- a/config/crd/v1/bases/velero.io_backuprepositories.yaml +++ b/config/crd/v1/bases/velero.io_backuprepositories.yaml @@ -69,9 +69,7 @@ spec: - "" type: string resticIdentifier: - description: |- - ResticIdentifier is the full restic-compatible string for identifying - this repository. This field is only used when RepositoryType is "restic". + description: Deprecated type: string volumeNamespace: description: |- diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index d26e9cfd8..032a1ac84 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -29,7 +29,7 @@ import ( ) var rawCRDs = [][]byte{ - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xccXK\x8f۶\x13\xbf\xfbS\f\xf6\x7f\xfd\xcbnP\xb4(|K\xdc\x06\b\x9a\x04\v{\x91;-\x8dlf)\x92%\x87N\xdd\xc7w/\x86\x94lY\xa2\xad\xf5\x1e\x8a\xf2&r\u07bfy\xd9EQ̄\x95_\xd0yi\xf4\x12\x84\x95\xf8;\xa1\xe6/?\x7f\xfe\xc9ϥY\x1c\xde̞\xa5\xae\x96\xb0\n\x9eL\xb3Fo\x82+\xf1g\xac\xa5\x96$\x8d\x9e5H\xa2\x12$\x963\x00\xa1\xb5!\xc1מ?\x01J\xa3\xc9\x19\xa5\xd0\x15;\xd4\xf3\xe7\xb0\xc5m\x90\xaaB\x17\x85w\xaa\x0f\xdf\xcd\xdf\xfc8\xffa\x06\xa0E\x83K؊\xf29X\x87\xd6xI\xc6I\xf4\xf3\x03*tf.\xcd\xcc[,Y\xfaΙ`\x97p~Hܭ\xe6d\xf5\xbb(h\xdd\t:\xc6'%=\xfd\x9a}\xfe(=E\x12\xab\x82\x13*gH|\xf6R\xef\x82\x12nD\xc0\n|i,.\xe13\xdbbE\x89\xd5\f\xa0\xf54\xdaV\x80\xa8\xaa\x18;\xa1\x1e\x9dԄneTh\xba\x98\x15\xf0\xd5\x1b\xfd(h\xbf\x84y\x17\xddy\xe90\x06\xf6I6\xe8I46\xd2v\x01{\xbb\xc3\xf6\x9b\x8e\xac\xbc\x12\x84ca\x1c\xb9\xf9\xd9֧\xa3\xc5\v)\xe7@@\xef-I\xf4\xe4\xa4\xde\xcd\xceć7)\x14\xe5\x1e\x1b\xb1li\x8dE\xfd\xf6\xf1×\xef7\x17\xd7\x00\xd6\x19\x8b\x8ed\aO:\xbd\xf4\xeb\xdd\x02T\xe8K'-\xc5\xe4\xf8\xab\xb8x\x03`\x05\x89\v*\xceC\xf4@{\xecb\x8cUk\x13\x98\x1ah/=8\xb4\x0e=ꔙ|-4\x98\xedW,i>\x10\xbdA\xc7b\xc0\xefMP\x15\xa7\xef\x01\x1d\x81\xc3\xd2\xec\xb4\xfc\xe3$\xdb\x03\x99\xa8T\tBO\x10Q\xd4B\xc1A\xa8\x80\xff\a\xa1\xab\x81\xe4F\x1c\xc1!넠{\xf2\"\x83\x1f\xda\xf1\xc98\x04\xa9k\xb3\x84=\x91\xf5\xcb\xc5b'\xa9+\xca\xd24MВ\x8e\x8bX_r\x1b\xc88\xbf\xa8\xf0\x80j\xe1\xe5\xae\x10\xae\xdcK\u0092\x82Å\xb0\xb2\x88\x8e\xe8X\x98\xf3\xa6\xfa\x9fk\xcb\xd8_\xa8\x1d\x01\x9dN\xac\xa4;\xe0\xe1\xd2\x02\xe9A\xb4\xa2\x92\x8bg\x14\xf8\x8aC\xb7\xfee\xf3\x04\x9d%\t\xa9\x04ʙt\x14\x97\x0e\x1f\x8e\xa6\xd45\xba\xc4W;\xd3D\x99\xa8+k\xa4\xa6\xf8Q*\x89\x9a\xc0\x87m#\x89\xd3ව\x9e\x18\xba\xa1\xd8Ul\\\xb0E\b\x96K\xa7\x1a\x12|а\x12\r\xaa\x95\xf0\xf8/cŨ\xf8\x82Ax\x11Z\xfdv<$N\xe1\xed=t\xad\xf4\n\xb4\xc3\xf6\xb8\xb1X2\xb2\x1c\\f\x95\xb5,SM\xd5Ɓ\x18\xd1_F*\xdf\x02\xf8\xa4&\xba!\xe3\xc4\x0e?\x9a$sH4\x95v|\xde\xe5\x04u\x16s\xdbJ=\x01\xf3\x84\x19\x81\xb4\x17\xd4k\x06$\xa4>\xf5\x94\xac\x937\x90\x89\xe8\b\xee\x14Z\xe8\x12\xdf\xc7|\xd4\xe5q\xc2\xd1O\x19\x16vio\xbe\x81\xa9\tu_hkkƓ-\x82\v\xfa.c\xcf>\xae\x8c\xae\xe5nlh\x7f\x90]\x03wB\xc9\xc0\xdb\xf5@'{\xca\xc9u\xb6\xa5\xe82\x8f\x01\xa9\xe5.\xb8k\xe0\xd5\x12U5j!\x00:(%\xb6\n\x97@.\xe0\x95\x88\x8cj\xe52\"<\x1f'\x80[_\x10\x83\xd4\x15WK;\xacXI\x97\x8c\x9c\xfe\xa8+p\x97kJ\xff\xa0\x0e\xcdX]\x01\xcf\xc6J\x91\xb9w\xe8I\x96\x99\x87\x87\x87\xfb2\x80\xc5|\xa8\xb8\x1d\xd5\x12\xddkjr=\x90ѕc\x1d\x94j\x15\x14\xa5i\xac \xb9U\xd8\xcd\f\xc6\\&\x9ec.i`T\x86\xf0\x14'\x01c\xce*\x8cVG\b\x1e+\xf8\xb6G=\x02\xc3\xc3C\xd2\xfdpWI\x1cxQ\xc3\xd3j\xf7\x9ax|\xb9\x14\xd1\xefN\xe9\":\x96ZbϿ\xae\xfd\xf8\x8cHk\xaaֲ\x96/\xd6\xcc\x1d\x8eq_\x91\x0e\ac\xbe\xc87\xe6\x01M\xae\xa5\rH\x06Q{\xd1d\"A\xc1\xdf3\x9b\"C\x17\xcd28\x17g\x7f\xba\xe5\x95\xef\xd5\xd3I\tO\xbd&\xcc\v\xf8\x04\xee\x1f\xc7\x1c\x9da,\f\x88/\x18\xda~\xf02\xb8\xfaP\x96\x88\xd5x\x1d\x01Ʒ\x11\x94\x16\xfd\x82彮\xcb\xe5\x87\x14z/vSN~JTi\xd3kY@lM\xa0+\b\xd0>\xe7\xe3mT&,\xb5{\xe1\xa7\xec|d\x9a\\^\f\x96\x81[&\\k\xbf\x9f\xf1[\xe6v\x8d\xa2\x1a\xb7\xf0\x02>\x1b\xca?\xdd\xec\xc0%\xea~2M\x0e\x9d\x01={~\x81A+r\x94\x7fc\xaf%a\x93\x1d\xe7\xd7k%\x1dn\xe7\n\tO\xbfU\xf3d\x03\xd3WC\xae\x13h\xe9\x81W\xb9X9Ws\xa9\vٔc\xe9L\x97P:\x13\x85\x94\xce\xcd\r\an\x15U&\x12\xf7\x96\xd6\xd5P$\xb8_\x16\x8eI\x0f\x1c\xfa\xa0\xe8E\x0e\xac#i\x87_b<\xa7\xdf\xcb\xec\xc9\xd7\\:\x05l\xba\xd6x\x95⽐\xea\xea\U000e4cde\x84\xa3\xfb\xf2ws\xc1r\xfa\x9dķ\xfd\xbc\xfdO\xe6獝\xb7{\x14Ή\xe3\xf4\xe8\x1e]z\xfe\xc9^\xf5\x8c\xf3i\x9d\xe8߄\xed\xe9\x1f\x89%\xfc\xf9\xf7\xec\x9f\x00\x00\x00\xff\xff\x18\xd9g\x90\x9b\x14\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xccW͎\xdb6\x10\xbe\xfb)\x06鵲\x1b\x14-\n\xdf\x12\xa7\x01\x82&\xc1\xc2\x0e\xf6NSc\x99Y\x8adɡS\xf7\xe7\u074b!%[\x96dk\xbd\x87\xa2\xbc\x89\x9c\xffo\xe6#U\x14\xc5L8\xf5\x88>(k\x96 \x9c\xc2?\b\r\x7f\x85\xf9\xd3/a\xae\xec\xe2\xf0z\xf6\xa4L\xb9\x84U\fd\xeb5\x06\x1b\xbd\xc4w\xb8SF\x91\xb2fV#\x89R\x90X\xce\x00\x841\x96\x04o\a\xfe\x04\x90\u0590\xb7Z\xa3/*4\xf3\xa7\xb8\xc5mT\xbaD\x9f\x8c\xb7\xae\x0f?\xcc_\xff<\xffi\x06`D\x8dK\xd8\n\xf9\x14\x9dGg\x83\"\xeb\x15\x86\xf9\x015z;Wv\x16\x1cJ\xb6^y\x1b\xdd\x12\xce\aY\xbb\xf1\x9c\xa3~\x9b\f\xad[C\xc7t\xa4U\xa0\xdfF\x8f?\xaa@I\xc4\xe9\xe8\x85\x1e\v$\x1d\ae\xaa\xa8\x85\x1f\b\xb0\x83 \xad\xc3%|\xe6X\x9c\x90X\xce\x00\x9aLSl\x05\x88\xb2L\xb5\x13\xfa\xc1+C\xe8WVǺ\xadY\x01_\x835\x0f\x82\xf6K\x98\xb7՝K\x8f\xa9\xb0_T\x8d\x81D\xed\x92l[\xb07\x156\xdftd\xe7\xa5 \x1c\x1a\xe3\xca\xcdϱ~9:\xbc\xb0r.\x04tβ\xc5@^\x99jv\x16>\xbcΥ\x90{\xacŲ\x91\xb5\x0e͛\x87\x0f\x8f?n.\xb6\x01\x9c\xb7\x0e=\xa9\x16\x9e\xbc:\xed\xd7\xd9\x05(1H\xaf\x1c\xa5\xe6\xf8\xbb\xb88\x03`\aY\vJ\xeeC\f@{lk\x8ce\x13\x13\xd8\x1d\xd0^\x05\xf0\xe8<\x064\xb93y[\x18\xb0ۯ(i\xde3\xbdA\xcff \xecm\xd4%\xb7\xef\x01=\x81Gi+\xa3\xfe<\xd9\x0e@69Ղ0\x10$\x14\x8d\xd0p\x10:\xe2\xf7 Lٳ\\\x8b#xd\x9f\x10M\xc7^R\b\xfd8>Y\x8f\xa0\xcc\xce.aO\xe4\xc2r\xb1\xa8\x14\xb5C)m]G\xa3\xe8\xb8H\U000e5d91\xac\x0f\x8b\x12\x0f\xa8\x17AU\x85\xf0r\xaf\b%E\x8f\v\xe1T\x91\x121i0\xe7u\xf9\x9do\xc68\\\xb8\x1d\x00\x9dW\x9a\xa4;\xe0\xe1\xd1\x02\x15@4\xa6r\x8ag\x14x\x8bK\xb7\xfeu\xf3\x05\xdaH2R\x19\x94\xb3\xe8\xa0.->\\Mev\xe8\xb3\xde\xce\xdb:\xd9DS:\xab\f\xa5\x0f\xa9\x15\x1a\x82\x10\xb7\xb5\"n\x83\xdf#\x06b\xe8\xfafW\x89\xb8`\x8b\x10\x1d\x8fN\xd9\x17\xf8``%j\xd4+\x11\xf0?ƊQ\t\x05\x83\xf0,\xb4\xbat\xdc\x17\xce\xe5\xed\x1c\xb4Tz\x05\xda>=n\x1cJF\x96\x8b˪j\xa7d\x9e\xa9\x9d\xf5 \x06\xf2\x97\x95\x1a\xa7\x00^\x99D7d\xbd\xa8\xf0\xa3\xcd6\xfbBSm\xc7\xeb혡6b\xa6\xad\xcc\t8.8b\x90\xf6\x82:d@B\x99\x13\xa7\x8c&y\x03\x99\x84\x8e`\xa60\xc2H|\x9f\xfa\xd1\xc8\xe3D\xa2\x9fFT8\xa5\xbd\xfd\x06vGh\xbaF\x9bXG2\xd9\"\xf8h\xee\n\xf6\x9c\xe3ʚ\x9d\xaa\x86\x81v/\xb2k\xe0N8\xe9e\xbb\xee\xf9\xe4L\xb9\xb9α\x14m\xe71 ;UE\x7f\r\xbc\x9dB]\x0e(\x04\xc0D\xad\xc5V\xe3\x12\xc8G\xbcR\x91\xc1\xac\\V\x84\xef\xc7\t\xe0\xd6\x17\u00a0L\xc9\xd3\xd2\\V\xec\xa4mFn\x7f4%\xf8\xcbgJw\xa1\x89\xf5\xd0]\x01O\xd6)1\xb2\xef1\x90\x92#\a\xaf^\xdd\xd7\x01l\xe6C\xc9t\xb4S\xe8'2~Ǽ\xcd9\x0e\x1b\xf0\x86\x93\x03?~\xf0\xf4\\z\xc9\xdc?^\x9a\xe8N|\xdeH3\x9bi\xa6S\xe6v\xa4ÈIg\xcb&\xb2F/\xf5\xe1\x1d\xf3ó\xaa<\xf6\xae\xceb\x9c\xecz2c4\xd1\x13\xe9U\xedYlO\x82b\xb8\x87\xef\x93B[M\x19\xbdO\xf7i\xde\xe5gԋ\x19_\x8b@\x1db\xe3G\xed\x04\xee\x1f\x87\x1am`l\f\x887\x18\xdan\xf1Fp\rQJ\xc4rx\xc5\x03\xe3[\vʏ\xe7\x82\xed\xbd\x8c9Ɖ\x1fC\x10\xd5T\x92\x9f\xb2T~=5* \xb66\xd2\x15\x04h?\x96\xe3mT&\"u{\x11\xa6\xe2|`\x99\xb1\xbe\xe8]\xb0\xb7B\xb8Fi\x9f\xf1\xdb\xc8\xee\x1aE9\xa4\xc5\x02>[\x1a?\xba\xc9j\x12M\xb7\x99&\x89\xbc'ϙ_`И\x1c\xf4\xdf0kEX\x8f^\x91\xd7g%/ik\xa7\x91\xf0\xf4\xff7.\xd6\v}\xd5\xd7:\x81\x96\x0f\xf8y\x94&\xe7j/\xb5%\x9bJ,\xaf\xe9\x11\xcakb\x90\xf2\xba\xf9j\x80[C5R\x89{G\xebj)2\xdc\xcf+\xc7d\x06\x1eC\xd4\xf4\xac\x04\xd6I\xb4\xc5/+\x9e\xdb\xefy\xf1\x8c\xcf\\^\x05lZj\xbc*\xf1^(}\xf5x2\xd9@\xc2\xd3}\xfd\xbb\xb9P9\xfd{\xf0n\xb7o\xff\x97\xfdy\xe3\x1d\xd9\x1e\n\xef\xc5q\xfa\xea\x1el\x06\xfe\r.;\xc1\x85\xfc\x9c\xe8\xee\xc4\xed\xe9/\x7f\t\x7f\xfd3\xfb7\x00\x00\xff\xff\x96֥5\xef\x13\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=]s\xdb8\x92\xef\xf9\x15(\xdd\xc3\xecnI\xf6\xa6\ue8ee\xfc\x96q\x92\x1d\xd5\xcc$\xde\xd8\xe3}\x86Ȗ\x841\bp\x00P\xb6\xf6\xee\xfe\xfb\x15\x1a\x00?DP\x04eٓ\xdd\r_\x12\x8b`\x03\xfd\xddh4\x80\xc5b\xf1\x86\x96\xec\x1e\x94fR\\\x11Z2x2 \xec_\xfa\xe2\xe1\xbf\xf5\x05\x93\x97\xbb\xb7o\x1e\x98ȯ\xc8u\xa5\x8d,\xbe\x80\x96\x95\xca\xe0=\xac\x99`\x86I\xf1\xa6\x00Csj\xe8\xd5\x1bB\xa8\x10\xd2P\xfb\xb3\xb6\x7f\x12\x92Ia\x94\xe4\x1c\xd4b\x03\xe2\xe2\xa1Z\xc1\xaab<\a\x85\xc0C\u05fb?_\xbc\xfd\xaf\x8b\xff|C\x88\xa0\x05\\\x91\x15\xcd\x1e\xaaR_쀃\x92\x17L\xbe\xd1%d\x16\xe4Fɪ\xbc\"\xcd\v\xf7\x89\xef\xce\r\xf5{\xfc\x1a\x7f\xe0L\x9b\x1f[?\xfeĴ\xc1\x17%\xaf\x14\xe5uO\xf8\x9bfbSq\xaa¯o\bљ,\xe1\x8a|\xb2]\x944\x83\xfc\r!~\xd4\xd8\xe5\xc2\x0fx\xf7\xd6AȶPP7\x16Bd\t\xe2\xdd\xcd\xf2\xfe\xdfo;?\x13\x92\x83\xce\x14+\r\xe2\xfe\xbf\x8b\xfaw\xe2GI\x98&\x94\xdc#\x8eDy\x92\x13\xb3\xa5\x86((\x15h\x10F\x13\xb3\x05\x92\xd1\xd2T\n\x88\\\x93\x1f\xab\x15(\x01\x06t\v^\xc6+m@\x11m\xa8\x01B\r\xa1\xa4\x94L\x18\xc2\x041\xac\x00\xf2\x87w7K\"W\xbfBf4\xa1\"'Tk\x991j ';ɫ\x02ܷ\x7f\xbc\xa8\xa1\x96J\x96\xa0\f\vDwOK\x92Z\xbf\x1e\xc3\xd5>\x96<\xee+\x92[\x91\x02\x87\x96'1䞢\x16?\xb3e\xbaA\x1f\x85\xcc\xfeL\x85\x1f\xfe\xc5\x01\xe8[P\x16\f\xd1[Y\xf1\xdcJ\xe2\x0e\x94%`&7\x82\xfd\xbd\x86\xad\x89\x91\xd8)\xa7\x06\xb4\xa5\x8c\x01%(';\xca+\x98[\xa2\x1c@.\xe8\x9e(\xb0}\x92J\xb4\xe0\xe1\a\xfap\x1c?K\x05\x84\x89\xb5\xbc\"[cJ}uy\xb9a&\xe8W&\x8b\xa2\x12\xcc\xec/QUت2R\xe9\xcb\x1cv\xc0/5\xdb,\xa8ʶ\xcc@f\xd9|IK\xb6@D\x04\xea\xd8E\x91\xff[\x10\x0f\xdd\xe9\xd6\xec\xad\xd8j\xa3\x98ش^\xa0~L`\x8fU\x1d'\x8c\x0e\x94C\xb1\xe1\x82\xfdɒ\xeeˇۻ\xb6\xa02\xed\x99Ғ\xd7!\xfeXj2\xb1\x06\xe5\xbe[+Y L\x10\xb9\x13U\x94s\xce@\x18\xa2\xabU\xc1\x8c\x15\x83\xdf*\xd0V\a\xe4!\xd8k\xb4Ad\x05\xa4*s+Ƈ\r\x96\x82\\\xd3\x02\xf85\xd5\xf0ʼ\xb2\\\xd1\v˄$n\xb5-\xebacG\xde\u058b` \aX\xeb\f\xcbm\tYG\xd1\xecWl\xcd2\xa7Nk\xa9\x1a\xbb\xe3l`\x97BqշO\xa6٭\xa0\xa5\xdeJs\xc7\n\x90\x959l1&kȼ\xdb\xe5\x01\x940B?^\xb4Y\x95\x86\xdc*\xed#e\x06\xc7|}\xbb$\xf7h\xac\xc2\xd7h\xb4*ML\xa5\x84\x95\x92H__\x80\xe6\xfb;\xf9\x8b\x06\x92W(ܙ\x02\xa4Ü\xac`m%A\x81\xfd\u07be\x02\xa5,m4\x0e@V=cc\x9f\xbb-X\xdaҊ\x1b\xaf'L\x93\xb7\x7f&\x05\x13\x95\xe9\x89\xda בR\xd4\xd0B\xee@\x9dB\xc4\xf7\xd4П\xed\xc7\a\xb4\xb3@\tB\xb5\xc4[y:\xae\xf6\xf82\xc6m\xf7,\xd7-\x88L\x93ٌHEf\xce\x03\xcf\xe6\xee\xeb\x8aq\xb3`\xa2\xdd\xc7#\xe3<\xf42\ryGC\xc7P}'?j'\xbc'\xd1b\x00V\x8b4\x8f[0[P\xa4\x94\xb5\xc7[3\x0eD﵁\xc2\x13&x\x11\x8fO\xa4'\xd4\x1d\xce=\bm\xe9\xea\x11\xe9#/*\xce\xe9\x8a\xc3\x151\xaa\x82\x01ڬ\xa4\xe4@\xc5\bq\xbe\x806,;\ai\x1c\xa4\ba\x94\x7fѡ\x00:M\xfa\x00\x84F@{\x9aY\xef\xccy\x8b\xb0]\xaaD\xc7T*Ȭվ\xf2ހ\x01G\x0f$$\xe1Rl@\xb9\xdem\xa4\x12\x04L\x81\x15\xb8\x9cXC\xab\x80[oB֕\xb5\xc1\x17\xc4j\xf7\xa0\f0\xa1\rЈp>\x83?\xf0\x94\xf1*\x87\xfc\xda\x05^\xb76~\xccC\xd4ܳ\x9a)|\xfap\x14\xa2\xf7Μe\x18\x04\xfaxo\x81qkLL\x1b'\xbd/\xc1\x85Ζ\x95~؍\xf7=j\x0f4\x18\xfb\xd1\xecO\xb39r\xb8\xdbk\xb7\x0fM\xa8\x82\x9a,\xc9v\x13\x8a\xd2\xec\xfb\xad\x99\x81\"Bţ\xf6$\x91\x9fT)\xba\x1f\xe0f\x1d\xff\x9f\x91\x9fC0\x0f8*B\xb3W\xe6\xe9a\xbf\xff\xcc\\=\x0f\x1f5\xcev)\x13\x96\x7fv\xe2\xd9a\x9fv\xf37K6!M\x04\x1e\x13\x0e\x1eN͎p\xebw\"\xd6Yd~H\xc8k\xd9\xf2\xc2\xfb\x0fI\xa9\xad\x94\x0fc\xd4\xf9\xc1\xb6i&E$ì\nY\xc1\x96\xee\x98T\x1e\xf5\xc6\xd5\xc2\x13d\x95\x89j=5$g\xeb5(\v\xa7\xdcR\r\xdaM\x93\x87\t2\x1c\xbe\x93\x96\x19\x89\xbe<\xc0\xa3a\xa4e\x13b>4t\x1bG\x1cz\xc9\xf0\u0601\xda\xf0\x1a\x9dq\xcev,\xaf(G\xbfLE\xe6\xf0\xa1\xf5\xb8bV\xe6\b\x93{c\x8eJ\xa6{\\@\x10\x90\xb2L\xea̔\xa4\x00\x1b\xf3\x16vN\xd0o:\x8c\xf9\x8a\xdaXE\x0eaO\x90Y\xaa\xe2\xa0}W9\x86\x91\x8d͘7L\xc1D\x04\xe1t\x05\x9ch\xe0\x90\x19\xa9\xe2\x14\x19\xe3\xb3{R\x8c\xe0\x00!#\x96\xaf;\xd3h\x108\x02\x92\xe0\x14n˲\xad\v\xf5\xac\x10!\x1c\x92K\xb0\x01\x9f!\xb4,y\xc4]4\xcfQ\xe6\xfbN\x8e\xe9z\xf3\x8ch\xfd!\xbc\x98\xfe7O\x82\xcdl\x9e(i\x1b\xfd\xeaR\xb6\x16\x87\xf8\x9c\xb6y\xfe9\t\x1b,\xff\tB{D\xfb\tf\x85\x92ezPn-U\x19\xe8\v\x1bNa\xa43'̄_\xc74\xa1\x13s\xf5\x92e\x1d\"|ݼ\x99.\xf4\x89\xacIщ\x17bL\xdd\xc5? _\xd0e\xdcz\x8f\x91̓\x9f\xda_\xcd\t[\xd7D\xcf\xe7d\u0378\x01u@\xfd\x93L}\xe0\xcc9\x88\x91\xe2\xf5\b\xa6\xefM\xb6\xfd\xf0dC0ݬT%\xd2\xe5\xf0c\x17Ȇh\xbf\xeb\x9eG\xe0\x12Lc3\x05\x05\xa6\xc7q\xc6\xd4\xfe\x05C\xabw\x9f\xde\xc7\xe7W\xed'A\xf2z\x88\x8c(\x9d{\xde\x1d`\xd4\x1e\x9f\x0f\xe1\xc3\x1b\x8c\x81\xea\t\x90[\n\x99\x13J\x1e`\xefB\x17*\x88\xe5\x0f\r\x8d\x13\xbaW\x80k2(g\x0f\xb0G0\xf1E\x96\xfe\x93*\r\xeey\x80}J\xb3\x03\x1a\xda11\xed\x17\x8f,\x9d\xec\x0fH\b̭\xa7\x8a\x81{\xbc*D\x964\xe2O\xa2-\tO\xa0\xfd\th&\x89J\xbb\x8f\xf6*%J\xc0w\xda\xf1\xd2j̖\x95hV1\xe3 \xd7\xc9\fu\xcf=\xe5,\xaf;r:\xb2\x14s\xf2I\x1a\xfbχ'\xa6\xfdB\xe6{\t\xfa\x934\xf8ˋP\xd4\r\xfc%\xe9\xe9z@E\x13\xce\xca[\x82\xb5\x97\xe2\x9cO\xb3\xd2VӞi\xb2\x14v\xba\xe2H\x92\xd8\x15\xae\xba\xba\xee\\GE\xa5q\x15MH\xb1pi\x9bXO\x9e\xdeRu\xc8\xfd\xecN}\x87w\xd6Y\xb87n\xed\x97\xd3\f\xf2\xb0\\\x83\x8b\x92\xd4\xc0\x86e\x89\xfd\x15\xa06@Jk\xc2\xd3$\"Ѱzl\xa6\x89O\x9a\xf7n?O\x8b\x87z\x8d\x7fa]\xce\xc2C0\xb2H\xa0\x81\xb7\xdd\xf98>\v\xab\xb3\t\xad\x82$\x8c6\x1dX\xb3\x1cn\x9aB\x94g\x90\x03\xbd8\x868\xa3ܥy\x8eu.\x94\xdfL\xf0(\x13da\xaaih\x8dݹ\xe0\x82\xe2R\xcb\xffXO\x8b\xda\xf4\x7f\xa4\xa4L\xe9\v\xf2\x0eKZ8t\xde\xf9\xa4Y\vLB\x97X\x92b\xe5gG\xb9\xf5\xfdր\v\x02\xdcE\x02r\u074b\x8b\xe6\xe4q+\xb5s\xdb\xf5\"\xce\xec\x01\xf6n\xc5p\xb4˶\x91\x99-\xc5\xcc\xc5\x10=\x83Q\a\x1cR\xf0=\x99\xe1\xbb\xd9sB\xa9DIMl\xd6\x11т\x96i\x12\x8a%E\xa9\x81\xba\x9d\xb0\x86 \xc4~X\x97\xca\xd8 \xfb\x18\xb6I\"ZJ\x1dY\xc8\x1f\x18ʈ\xf0\xdeHm\\\xbe\xac\x133G\x13j2$\xd1\b]\xbb\xfa%\xa9B\xb1\x895\xcac\xa9\xdf\xf6s\xb7\x05\r~\xbd\xc2'\xe6\x1cP;\xb3\x9b5\xfa\xed\xac\xfḓ\x97`'4È\x05\xbf-\x95\xcc@Gײ\x9b'\xc1_D\xaa2ڸ\xd79G\xeafI\xae$\xe3x\n4<\xe9!\xaf%\xc4\xc4\xf9\u0087\xa7VB\xd4\xea\xbe\xfd{LƦ\x8e\x8b`\xc9`Q\xd0\xc32\xa5\xa4!^\xbb/\x836x@n\xf2\xa16\x15Z\x82T_^\v\xe0\xd7\x10(\x14L,\xb1\x03\xf2\xf6\x05\x02\voCc\xc5&\xb1\xe7\xb4P\xf6:t\xd2p\xa7\xfe\xc1\xa9r)q\xa9@A\x87y\xfd\xac:ơB\x9aVBbB\xb8Y\xca\xfc;M\xd6Li\xd3\x1e\x82\x1e(S\x89\x82\x998\xf1\x12\x1f\x94:i\xde\xf5\xd9}\xd9Jwm\xe5c(\xcfr\x84I\xc4\x1cח\x80\xb05a\x86\x80\xc8d%0\x81c\xf5\x18\xbbp\xc4u\x16\x96\xa5*I\x9a\xf6\xdb\aDU\xa4\x11`\x81\x92\xc2\xc4\xd1LO\xbb\xf9G\xca\xf8K\xb0\xcd\fU\xb1Ş\xd3t\"\x94\xb8\xb5\v\xf2\n\xfaĊ\xaa \xb4\xb0\x1f\xc0\xb0\x8c\x0f\xa1\xdd+\xc5\xc8E\xc5\r+9.\xa4\xeeX\x1eM6\x98-\xec\xeb\x034~\x95\xb8\xf5ԟ\x04\xf3\xf9K-\xb5\x17\a\x91>\xd5\xe4\x118'4\xa6W=\xcc3w\x12S&\x17`\xfd\x91\xd5N\x7f0\x88?\xbei\xee\xc4\x1dwעW+b)&*\x86O\x91\x19t\x1c)\xf6\xa6\x17\xc1\xba8\x1c\x7f\xfb\xad\x02\xb5'x\x8eM\x1d\xe74\x9b\xc0\xbcbj;\x11\v\xa6\u009b\xad\xa1\xfcy/\xe8oT\x99\xbc\x13\xce\xeb\x1e\x8e\a\xbf\xb16\xa2\x99\xd4X\xc3g\xe7+\xd1>\x06>\x17\xb2\xfe:\xf2\xd9X\x80\x9c\xba[\xeae\xa78\xd3'9\xa3QEz\xe4\xf7;\xed\x82:e\xf7SZ\x01\xc0\xe8n\xa7\x97\x9a\xf2\x8cMz\x92㼴\xddL\xd3\x16\v_p\xf7\xd2K\xecZJ\xa4T\xca.\xa5itz\x85]I\xaf\xba\x1b\xe9\xb5v!%\xef>J*qI^\x05N-Q9q;\xcd\xf8\x1a\xef\xf1\xddD\t\xbb\x88\x12V\x7fǑ<\x01\xbd\x84]B\xd3v\a%\xf0,U\x15_q\x17\xd0+\xee\xfey\xed]?#\x925\xf2z\xda\ue793\x97,\xa4\xcaA\x1d]\xf6I\x95£\xf2\x972\xb7\xe9\x0e\xe4`\xbd#\x9c\xfag[u\xe2et\x0f\xfe\xa0Q\x97G\xb3\xa2\xe4{;C!\xb3\xf6\a\xa7I@T\xdaBo7\x92\xb3,\x12\xbbE\xcffr\x8d{\x87e\xe0\x89QY\xbbd\xa0\xb4\r\xe3\xa1\x1b\x86y\xdd#0גs\xf98q\xeeOK\xf6\x17<\xb9\xfb\x19١w7K\x84\x11\xc4\x03\x8f\x02\xaf\x8b\xb3jlV`\xddr\x83\xe7\x90\xee/\xd7\x1d\x88\xdd:\xc7\xf6Ḑ\xbbs\x90CX\xe0Mg&\xadu\xb9Y\xbaq\f\xf5be\x86\x8a=\x91XQc\xb6L勒*\xb3w\x85\x1a\xf3\xce\x18\x82/=\x96\xdd\x19\xf4\x1e\xfd\xb3\x9d\xa3\xe4\rG:\xe3\n\xe5\xbe\xec.\xfa\x1e\xd2\xee\x94q\f\xef^\x1cݷx\xc6q\f\x87%\v\xa4T\xe4\xe7h\xe5\xd7ٲfڟL\xfc\xb3\xdc\xc1\xfbh\xf6\xacC\x9eۃ\xe6\x91\xf2\xac\x00\xd1\x1d\xba;X\xa5\xba\x02<\x90\xb7\xff\xea\x19\xf5V\xa1k\x7f\xa6\xea)\x89\xb2\xdb.\x88\b~\xe1\x84\xd9\xd0Y\xcc>\xe1\x01\xf0{rs\x8fs\xb4ڴy\x15\xf5s\xb4\x90*\v\x8b\xc1\x118\xfe\x83\xef\xcf_\x9a\xa6\x8dTt\x03?Iw\xc6\xf6\x18ۻ\xad;g\xaf\xfb\xa8'ԏ\x06\xa5\x89\x1d\xc0\xebO\xfb>\x00\xd6\xd4|\xf7\x0e5\xb6\xa3\x9cxL\xb31\xfc\x14\xbe\xdf\xdd\xfd\xe4\xb02\xac\x80\x8b\xf7\x95+w\xb06Q\x83%q\xc0\xd6AZ\xd9\xffn\xe5#\x1e\xfe\x1b\xcfc\x86;\x13\x1ad\x14`\xb19\x96 NB\xa9*\xb9\xa49\xa8k)\xd6l3\x82\xdd/\x9d\xc6\an6\xc3\x1f=r\xb5\x8f\n\xf0\xcf\\\x83`c\x1e\u0381\x7fd\x1c\xb4\x1bV\x82\x01\xbe\xe9\x7fU\xdb\xe3\xaaX\xb9\x18nm_\xd6\x1d\f\xf88\x87\x16\xa6\xa2KP6\x8arI\xebJ\aY\x1dF\xbc\xe1\b\x13\x066П\x05\x1e\xb1\xc0\xeeTit\x9f\xc1\x9c\xe0\\\xe6\xc7X~\xab\x83\xfc\xfd\xf0\x97\a\x9cl\xa5\xbcb'\xee\xb9 \xe4\xe6\xfeZ\x93J\xe4\x98.\xbe\xff\xcb\xed$\xa9\xdbuN\xae\x0f\xda:fT\xef\xe3_\xb5\x82㖽pѱ\\G\x10\x18\x82Ӻ\a\xe4\x91\x19\x7fp\xd7yOZ\x1d\x9a\xf2\f\xddp\x80G\xfa\x8f\xdfq\xe0N\xfe\xf77\xa3xu\xac\x14\x1e\x93\xeao\x05\xc0cEO\xba\xe6`U\x17l\xd5\xc5_\xfa\x9d1P\x94&\x16k\x8c\x9b\xc3\xef\x8f\x01\xac\xe34i(oi%\r\rb\x91\xb6ދ\xecXa\x99\xb7FG\xb8yL\x1fc\x04\xb8\xf6\xfb!\xceF\x80\x1a\xe0\x10\x01t\x95e\xa0\xf5\xba\xe2|_o\xc7\xf8J\xa8\xf1\x912~>R8h\x83\x82`\xd1;\ni\x14a_\xee\r\"\x0f\x9a\x1e\xb6*M#\x85炯\x86Ԇ\x16']\xd8p\xdd\a\x83W\xf6\xa8\xbcUTI\xeb\xb1Sݰ?\xe6\\\x1ap\xeeK\x9cdYh\x90\x13\u0601 \xd6;;\x12\x87;\xa7&B\xf1;\\\x9d\x87\v\xfe.\xa4B\xa2\x17\x13\x11\x9f\xed\xd0x\x01\xcew\xba\x86\x89\xb5\xa2x\x9fI\x9f\b\xfd\xe0\xd7e+\xael\xf4\x0f\v\v\u2d285j\x9b3ͺ~\xe1yF\xee\xfav9\x04\xee\x14\x13\u05ff\xee\xe5\x99j\xdcG\xf7Y&\xad\x8f\xee$\x83\x16\x81X\xcb\xf8\xf9qGU?\xedPw\xfc\xd2\x05\x1cY\xd8CG9\xf7\x1b\x1d\vКn\xc2i\xee\x8fv\xea\xb1\x01\x01.=\xe7\x16O\"@\x9b]qݳ̝\xca\xd0\xccT\xd4w\x10\n|[\xad\xbeӄ\xcb\x18T\xbcЅ\x85\x9b\xc2\u009cl\"\xa1\x9eJ\xa6R\xe6p\x1fꆖ6\x18\t#w\x9a\xbb݀\xb3\r\xb3s\x1d˹\rU+\xba\x81E&9\a\xb4\xd6\xfdq\xbd\xa4\xae\xfb\xbd\x87_\x80\xeaQ\xd4>\xb6\xdb\xfa\x15@\xc7m\xb7\xf0M]\xb9;\xde\xdee\x98\x82\xe6\"\xbdހ$v<)PvT\x88\xde2\xd7\x1fi\xbbm\xd0:o\x96}\x9e\xd7_27\xf7y\x81\xb8<\x16\xf4W\xa9\xe6\xa4`\xc2\xfeCE\xee\x16\xf0\xc2Ǔƿ\x95\xf2\xe16\x12\xc4\xf6\x06\xffCݰY\xea`\xc2\r\x1b7\x8c\xaed\xe5W\xdf\xeb\x806\xbe\xac\x82'\xf3\x9fy\xba\x890\x8f\xf8\x83\x1e:\x83\x19\xdd\x1f:\x90F]\x81\xeby\x00\xd6m\xb8Ɍ\xf3\xfd\xfc\x10\xf2\xc1\xad\x89\r\xec\xd6\xcd\x05>\fh\xce#\x18\xe8(\xacHE\x81\xd4\a_\xb4\r\xfa)\xb3^O\xe6\xa1`\xb2G\xe3\x1f\x9a\xd6Ctt\xc3l\x85{\x03\bv\x82\xc0\xf3N\xd8\xf1\x9a\x8a\x11\u1ff1m\xea\xb3\vZ\x13\xb7P%6\x98\xa5\x8b\xef}_\x90O\xd0_\xaeX\x90\xbfVPEh\xb0\b\x17\xc3\xdd\x1a\xaa\xfa)_\xb7\r\x1er\xac\xe8@m\x8c4Y\x8a\x1b%7\nt_X\x17\xe4o\x94\x19&6\x1f\xa5\xba\xe1Ն\x89\xcf\xc3[~\x8e5\xbe\xa1\xca0+\xecn<\xb1\x812A9\xfb{̮\xb5_\x8e\x03\xba\x1e\x9c`-H\xc20\x86^\xbc\a\x1b\xe3\x0e\xe6\x05\xa2&\xb4\xf4t=%^\t<\x19\xb3\xa9u,\xd1\xc4\"\xa1\xdb\v\xf2IF\r\x83/\x87b]\x986$\x03m\x16\xb0^Ke\xdcj\xf5bA\xd8:$\x1f\xac\xcd\xc1\xbc\x99\xbb\xab\x92\xb0\xd82s]hҸ/Lz+\xf4\xc2x\x94}A\xf7ne\x8afYe#\xacKm(\x8f\x048\xcf2\xfc\x98\xe5\xb1\xca\a\xf9/\xcfZ\xc9[\xb6\x01\xf5\x93\x8e؏#)\x1e\xa6\xe1\xa2>nQ\x04A\x1e\x153\xc6\xc6T\xf2H)\x81'\x95\xb1\xb1\x15\xe7D[R\x9f\x94}$Ό.\x87Kr\xd2P\xbe\xab\xa1\f\x99g\x8f5\xde̸B\xda\x10\x1b\xf7b\xf5\x91oeٜm\xa9\xd8\f\x9eP\xb0U\xb2\xdal\x83$\x0f\x04\xd3$\xaf\x00\x93\xb5hRt\xb8X\xd8TJ\xb4J\t\x8el\xfb&A\x18p\xb84{ U9\xf7\x17\xf7\xfa{\x99/\xfd\x1d(\x8b\xb5\x92\xc5\xc2\xf7\x8b\xb9Թ_\xc9WL\xda\xc8\xc5l\xa3T'.j\xf7\xd7\f\xa0$\x94%\bB\xb5\xef9ᤨ\x93\xdd\xd4o\xd65\xdcH\xcd\x12\xa2\xfd(\xc7\xff\xda\x06\x10\x18^\x86\xbf\xbb\xcc\xf03\x18\xec3\x86\xc7g\xbf\x05\x1fvT\x187\x9d\xa8]\xe4\xcc9\xb1٤\x89\x8c\xb6\x8e\xedYI\x9a\xdb\x0e\x84\x91\xfc\fv\x17gѭ/\xd7p\a\x81]\xfb\xebWk\xc0s\xa2\x99\b\x17_\xbb\xd2\x0f'\xfdѕ@\x81\x17UJ\x15\xaf\xc6<\x9ep\xe9\"\xf4\xba\xb9\x96]\x1dI|8y*~\x7f\x00\xe3`S7\xdeKZ7\t\xd3\xe7?\xb0\xd8z\x00\x96\xf1f\x16\x95?\xfe\ue6f5wIS\xbd8E\x8e\xcd\xfcpR7<\x85\xeb\xdeCz\xc3\xc1j\x9b\x06\xe8N*'\xe9\xdc\xee\x8cٴs\xa6\xd2\xc2\x15\xef\xe7\xc9%\xedΘD{\xb1\f\xdayQ~\xa4xA\xf4IZ\xfb7\xffm$\x85\xe6\xc1\x9e;\x89\xd6ʡ\x85\x81\xbfj\x16-\xeas{?\xa2\x9d\xce[\xd6\xc2\xf7\xe4\x7f\xf9\xff\x00\x00\x00\xff\xff<\x82OF\xb8\x82\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xccZK\x93۸\x11\xbe\xebWt\xed\x1e\xf6\xb2\x94줒J\xe96\x96\x93*W\xc6\xf1\xd4hvr]\bhJ\xb0@\x80\x01@\xc9\xca㿧\x1a\x0f\x89\xe2C\x0f;q\u008b-\x12h\xf4\xf3\xeb\x0f\xc0\x14E1a\xb5|E\xeb\xa4\xd1s`\xb5\xc4/\x1e5\xfdr\xd3\xed\x1f\xdcT\x9a\xd9\xee\xedd+\xb5\x98âq\xdeT\xcf\xe8Lc9\xbe\xc7Rj\xe9\xa5ѓ\n=\x13̳\xf9\x04\x80im<\xa3\u05ce~\x02p\xa3\xbd5J\xa1-֨\xa7\xdbf\x85\xabF*\x816\b\xcfK\xef\xdeL\xdf\xfe~\xfa\xbb\t\x80f\x15\xcea\xc5\xf8\xb6\xa9\x9d7\x96\xadQ\x19\x1eENw\xa8К\xa94\x13W#\xa7\x15\xd6\xd64\xf5\x1cN\x1f\xa2\x84\xb4z\xd4\xfc]\x10\xb6\x8c\xc2\x1e\x93\xb0\xf0]I\xe7\xff<>\xe6Q:\x1f\xc6ժ\xb1L\x8d\xa9\x15\x86\xb8\x8d\xb1\xfe/\xa7\xa5\vX9\x15\xbfH\xbdn\x14\xb3#\xd3'\x00\x8e\x9b\x1a\xe7\x10f\u05cc\xa3\x98\x00$\xd7\x04i\x050!\x82\xb3\x99z\xb2R{\xb4\v\xa3\x9aJ\x1f\xd7\x12踕\xb5\x0fΌ\xb6@2\x06\xb25\xe0<\xf3\x8d\x03\xd7\xf0\r0\a\x0f;&\x15[)\x9c\xfd\xa2Y\xfe\x7f\x90\a\xf0\xd9\x19\xfd\xc4\xfcf\x0e\xd38kZo\x98\xcb_c\x8c\x9eZo\xfc\x81\fp\xdeJ\xbd\x1eR\xe9\x919\xffʔ\x14A\x93\x17Y!H\a~\x83\xa0\x98\xf3\xe0\xe9\x05\xfd\x8a\x1e\x02r\x11B\xf6\x10\xec\x99K\xeb\x00좔\xe0\xa3aMUo\xad3\xb5I\x15x\xedH\x89\xfaӛ\xa4}Kl\xce\xef)\xb7x\x14\xe9<\xab\xea3\xb9\x0fk\x1c\x13v\xe6\x8a\xf7X\xb2F\xf9\xb6\xa9\x14%\xd5\xce\xcbs\xb3j\xe4S\x11g\x9d\xad\xf8\xfe\xec]\\ue\x8cB\x16\xa5\xc4Q\xbb\xb71\v\xf9\x06+6O\x83M\x8d\xfa\xe1\xe9\xc3\xebo\x97g\xafa(\x91:EA\x81c\xad\xd8l\xd0\"\xbc\x86\xfa\x8bqsɴ\xa3L\x00\xb3\xfa\x8cܟ\x82X[S\xa3\xf52\x17K|ZX\xd4z\xdb\xd1\xe9\x9f\xc5\xd97\x002#\xce\x02A\xa0\x841\xafR\xfd\xa0H\x96\x83)\xc1o\xa4\x03\x8b\xb5E\x87:\xc2\x14\xbdf:)8\xed\x88^\xa2%1Tۍ\x12\x84e;\xb4\x1e,r\xb3\xd6\xf2\xefG\xd9\x0e\xbcI\xc9\xec\xd1y\b\x15\xaa\x99\xa2dm\xf0g`Zt$W\xec\x00\x16iMhtK^\x98\xe0\xbaz|\xa4j\x90\xba4s\xd8x_\xbb\xf9l\xb6\x96>#47U\xd5h\xe9\x0f\xb3\x00\xb6r\xd5xc\xddL\xe0\x0e\xd5\xcc\xc9u\xc1,\xdfH\x8f\xdc7\x16g\xac\x96E0DGH\xadď6a\xba;[\xb6W\xd2\xf1\t\x90zGx\b^c\xcaDQ\xd1\xc4S\x14\xe8\x15\xb9\xee\xf9\x8f\xcb\x17Ț\xc4HŠ\x9c\x86\xf6\xfc\x92\xe3Cޔ\xbaD\x1b\xe7\x95\xd6TA&jQ\x1b\xa9}\xf8\xc1\x95D\xed\xc15\xabJzJ\x83\xbf5\xe8<\x85\xae+v\x11\xba\x18\xac\x10\x9a:\x80Dw\xc0\a\r\vV\xa1Z0\x87\xdf9V\x14\x15WP\x10n\x8aV\xbb7w\aG\xf7\xb6>\xe4\x9e:\x12\xdaA4X\xd6\xc8\xcf\xeaN\xa0\x93\x96*\xc33\x8f\xa1\xba:\x0eJP1ޔ\xf33\f\x12\xf40\xceѹ\x8fF`\xf7KG\xe5\x87\xe3\xc03\x1dk\xb4\x95t\xa1\xbdBil\xb7\xf3\xb0#\x92\xb7\x9f\x8cx݀\x03\xa0n\xaa\xbe\"\x05<#\x13\x9f\xb4:\x8c|\xfa\xab\x95\xbe\xbf\xd0H \xe9\x89*.\x0f\x9a?\xa1\x95F\\1\xfe]g\xf8\xd1\x05\x1b\xb3\x872\xe4\xbf\xf6\xea@\xd8\xe5\x0e\x9a\xf7Q;?\x0fO\x1f2\x82\xc7\xdaJ\x85\x99|5\x85\x87TԦ\x847 \xa4#\"\xe1\x82о\xb3t\xa3\x02ј\x83\xb7\xcd]\xe6s\xa3K\xb9\xee\x1b\xdd\xe6Fc\x19sEt\xc7s\x8b\xb0\x12\xa1\x16eGm\xcdN\n\xb4\x05Շ,%O\x9a46v\x90R\xa2\x12=l\x1a\xad\xb2`\x8aEAE\xcdԕ\x18.\x8e\x03\x03\x93fR\xc7\f>\t\bXc\xabԚ\xb5G-\xb0\xdbm\x826&\x00\x9aC\x01{\xe97\x11)\xd5P\xdd\xc1\xc5ڣg\x8b\x87\xa1\xd7\x1d\xdd_6H#c\xe3Ep\xc8-\xfa\x90m\xa8(}(\x95\xa6\x00\x1f\x1b\x17\xb0\xb6\x8b\x13\xf9\t\x84/\xcf\xde\xe2\xa1\xefh\xb8\x16\xdcD\x85FT\x0e$j\x0e?\xfcpݤ^w\xcb\x0fQ\xf7l\xa8\xc5\x12-\xea\x1e\x9b\xc8\xcfK\xe8Q\x944\x94aX\x96Ƚܡ:\x84\x9eD\xe0\xf93\xac\x1a\x0f\xa2\xc1\x105Ʒ{f\x85\x03n\xaa\x9ay\xb9\x92J\xfa\x03H7\"\x9f)e\xf6(Rı\xaa\xfda\n\x1f\xb4\xf3LstG\x1eD\x1e\x8b\xa9\xc0t\x1c\x95\xaa8\x10:f\x8700\x8a\xaf\x8c\xf3\xc0\xd1R:\xaa\x03\xec\xad\xd1\xeb1c\a\xda!\xed\x01\xadF\x8f\xa1#\n\xc3\x1d5C\x8e\xb5w3\xb3C\xbb\x93\xb8\x9f\xed\x8d\xddJ\xbd.H\xc1\"\x81\xcf,\xec\xecf?\x86\x7f\xbe&\vL\x1dq\xe2\x86\xe4]\x86Z?\x10\xbd\xf5\x1b\x8c-b\x19s\xd0X \x02A\xa9]\xa5܍\xc8:TvC\xbc\xbc\xfd\xe4\x90\x0f\xf5\x8f-\xf6[\xc7\x05P\x01\xf8R\x9c|[T\xac.\xe2h\xe6M%\xf9\xa4km\xcc\xfb\xcb\xf8\x937+R\vɉܞ\xe3F\xdeĉ\xb3=̀\x1b\xba\xbb\x9c1\xb4\x1cvS47q\x85+\x1a\x7fj\x8f=m}#t\xa7\xfe\xef\xd0\x13\xeft\xa0\x91\xf8\x01\xb3}?\a\xc0\xe4FkB*o\x80\x1d\xdb\xc0O\xae\xdb\xff\xeeD\xcfU÷8\xe0\xf8\x9e)\xef\xc2\xc0\xec\xe38\x8dti\x1c\x86\xc6tM\r\xb8^\x11\x9c-\xd0ޢ\xcb(\xf2-\x1eH\u0091[0X<\xc0\xaa\xd1BaVu\xbfAM\xdb1Y\x1e\x88\xec\xbf<.\xb3c\x03\x01K[\xa7\xec\xde1 yO\xbb\x00JA1\x87_\x1c\xa6u\x9f\xb1\x04\xa9\x9dG\xd6#\xe9\xf1\x89\xbdq\x0e\xab\xc3\x00\u05fa\xd9A\xcfX~\xbb\x8f\x82\xae\xe4\xa1\xd4 8\xc6\xc4J\xb0\x92\xfa{\xde\x0f-\x1e\x02\xc4\x12\xdf \"}\xe6ґe\xeetth\xd0i\xf1\fdR\xc7\x02a\xd5\xd8\":\x1fR\x01\x8by9H䇃q\xb9.\xe0\x12\xb3\xe89\xfb>v1*\x13\x80\xdd\xc80\xe0z\xb2\xc0E\xa6\x017\xb0\x8d\x9e\x99\xa39\x05w\xb2\x0e\xf8\x0e\xcc\x03\xfe\xfb\xec\x03\xeef \xf0\xddY\bܖ)\x97\xd9\b|\x13#\xb9\xe0\x8bK\\\x05\xae\xf2\x15\xb8\xc8Y`\x94\xb7\xc05\xee\x02w\xf2\x17\bx\x82\xa5\xfcr\x032?\x85\x81\xb9\x93\xd6\xcco\xa8kH\x81\xc0\x06\xfaj<\xa1\x18q\xd0q\xd3\xfb)\x85\xef+\xfa\xee%\xd2\x17չ\x87\xf7e@\xbfB\x8c\x9eҰ\xa3\x17\xf2\xef\x04 \xe7\a c\x04mТ\xdd\xf1\xb4\xfdO\xf1X\x81\x0f\xa0\xf8\x992\xaf\xfd\x19\x17\x8e'\xf2\x99\xff\x10K\xa3Ͱ\xb1\x16]m\xb4\xa0\xb6w\xdb\xe1\xc4I\xe5\xff\xdc\x11\xc5pX\x8bs\xfa\xda\xf9\x96\xa3p\xd3\xf9\\\xb8߸\xfb\x84.\xde\xfa\xb4Ͽ\xccʡݵ\x0e\xe9:6~\x97\xb3\xb9\xc1\xce\xd6:\xb0#\xaa\xa4\xa1\xd1\xe1\xc8\"4\xad\xe9d`F\x9b\x17\xfa\xd0<\xa4\x03m\xf64\xb9%-v=\x13\xe9M8\xb4dZ\xa4\xe3b\xfa4 y/\x95\xa2\x1ef\xb12\xe4,\xd4^Zj\x96,\xb4\xb1\xddo\xa6o\xfewg\x81\x8a9\xbf\xc6Qq\xff\x99\xa6\x00[\x99\xc6\x0f\xf4\xfeV\xc2\x0f\xd6t\xb8e\xbfG\xc7\xf0\xb7\x03\xd7\xe8\t\x8d\xc9\x11፵\xe1\xb2._\"ݱ\xd1\x1cC\xe0\x87Ο8\xb4\xbf\xf5\xff\x00\xe2\x06\xbb\x06\xbbt\xefe촭\xb8&'\xb7\xdf4\xab\xe3\x15\xec\x1c\xfe\xf1\xafɿ\x03\x00\x00\xff\xff%\xff\\)\x99#\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcVMo\x1b7\x10\xbd\xebW\f\xd0kwU\xa3hQ\xec\xadqr0\xda\x06\x82\x1d\xe4N\x91#-c.\xc9\xce\f\xe5\xba\x1f\xff\xbd \xb9+K\xab\x95\x93\\\xb27\x91Ù\xc7\xf7f\x1e\xd54\xcdJE\xfb\x11\x89m\xf0\x1d\xa8h\xf1/A\x9f\x7fq\xfb\xf8\v\xb76\xac\x0f7\xabG\xebM\a\xb7\x89%\f\xf7\xc8!\x91Ʒ\xb8\xb3ފ\r~5\xa0(\xa3Du+\x00\xe5}\x10\x95\x979\xff\x04\xd0\xc1\v\x05琚=\xfa\xf61mq\x9b\xac3H%\xf9T\xfa\xf0C{\xf3s\xfb\xd3\n\xc0\xab\x01;0\xe8Pp\xab\xf4c\x8a\x84\x7f&d\xe1\xf6\x80\x0e)\xb46\xac8\xa2\xce\xf9\xf7\x14R\xec\xe0e\xa3\x9e\x1fkW\xdcoK\xaa7%\xd5}MUv\x9de\xf9\xedZ\xc4\xefv\x8c\x8a.\x91rˀJ\x00[\xbfON\xd1b\xc8\n\x80u\x88\xd8\xc1\xfb\f+*\x8df\x050^\xbb\xc0l@\x19S\x88TnC\xd6\v\xd2mpi\x98\bl\xc0 k\xb2Q\nQ\x1fz,W\x84\xb0\x03\xe9\x11j9\x90\x00[\x1c\x11\x98r\x0e\xe0\x13\a\xbfQ\xd2w\xd0f\xbe\xda\x1a\x9a\x81\x8c\x01\x95\xea7\xf3ey\u0380Y\xc8\xfa\xfd5\b,J\x12O J]\x1b<\xd0\t\xbf\xe7\x00J|\x1b{\xc5\xe7\xd5\x1f\xcaƵ\xca5\xe6pS\x99\xd6=\x0e\xaa\x1bcCD\xff\xeb\xe6\xee\xe3\x8f\x0fg\xcbp\x8euAZ\xb0\fjB\x9a\x89\xab\xacA\xf0\b\x81`\b4\xb1\xca\xed1i\xa4\x10\x91\xc4N\xadU\xbf\x93\xe19Y\x9dA\xf8\xb79\xdb\x03Ȩ\xeb)0y\x8a\x90\v\x89cS\xa0\x19/Zɵ\f\x84\x91\x90\xd1\u05f9\xca\xcb\xcaC\xd8~B-\xed,\xf5\x03RN\x03܇\xe4L\x1e\xbe\x03\x92\x00\xa1\x0e{o\xff>\xe6\xe6|\xef\\\xd4))\x94\xe4\xb6\xf3\xca\xc1A\xb9\x84߃\xf2f\x96yP\xcf@\x98kB\xf2'\xf9\xca\x01\x9e\xe3\xf8#\x93h\xfd.tЋD\xee\xd6뽕\xc9Rt\x18\x86\xe4\xad<\xaf\x8b;\xd8m\x92@\xbc6x@\xb7f\xbbo\x14\xe9\xde\njI\x84k\x15mS.⋭\xb4\x83\xf9\x8eF\x13Ⳳ\x17\xddS\xbf\xe2\x02_!O\xf6\x84\xda#5U\xbd\xe2\x8b\ny)Sw\xff\xee\xe1\x03LH\xaaRU\x94\x97\xd0\v^&}2\x9b\xd6\xef\x90\xea\xb9\x1d\x85\xa1\xe4Dob\xb0^\xca\x0f\xed,z\x01N\xdb\xc1\nO\x1d\x9b\xa5\x9b\xa7\xbd-\xb6\x9b\x1d E\xa3\x04\xcd<\xe0\xceí\x1a\xd0\xdd*\xc6o\xacUV\x85\x9b,\xc2\x17\xa9u\xfa\x98̃+\xbd'\x1b\xd33pEڅ\xe1\x7f\x88\xa8\xb3\xb8\x99\xdf|\xda\ueb2ec\xb5\v\x04O\xbd\xd5\xfd4\xfc3\x9a\x8eFq\xce߲1\xe4\xef\xc5n\xe7;W/\x0fEdK8k\xd8\x06.\xbc\xfbu^\x8a\xa9~%3\xd5\xd1Gnt\"*\xcdw\xf4y\xb5t\xe8K\xb9@\xa2@\x17\xab3P\xefJP\xf9Ǡ\xacgP\xfey<\b\xd2+\x81'\xa4\r\x97\x95\x1ax\x8fO\v\xabw~CaO\xc8\xf3\x96ϛ\x9b\xca\x1e\xce߃WXZlʋE\xceVhNXd\t\xa4\xf6\xa7\xbcr\xda\x1e\x9d\xbe\x83\x7f\xfe[\xfd\x1f\x00\x00\xff\xff\xbeM\x1a\xea\xb1\n\x00\x00"), diff --git a/pkg/apis/velero/v1/backup_repository_types.go b/pkg/apis/velero/v1/backup_repository_types.go index 621ffcfcc..8e2b3b715 100644 --- a/pkg/apis/velero/v1/backup_repository_types.go +++ b/pkg/apis/velero/v1/backup_repository_types.go @@ -35,8 +35,7 @@ type BackupRepositorySpec struct { // +optional RepositoryType string `json:"repositoryType"` - // ResticIdentifier is the full restic-compatible string for identifying - // this repository. This field is only used when RepositoryType is "restic". + // Deprecated // +optional ResticIdentifier string `json:"resticIdentifier,omitempty"` @@ -58,8 +57,7 @@ const ( BackupRepositoryPhaseReady BackupRepositoryPhase = "Ready" BackupRepositoryPhaseNotReady BackupRepositoryPhase = "NotReady" - BackupRepositoryTypeRestic string = "restic" - BackupRepositoryTypeKopia string = "kopia" + BackupRepositoryTypeKopia string = "kopia" ) // BackupRepositoryStatus is the current status of a BackupRepository. diff --git a/pkg/controller/backup_repository_controller.go b/pkg/controller/backup_repository_controller.go index 16e0b740a..3f33bc814 100644 --- a/pkg/controller/backup_repository_controller.go +++ b/pkg/controller/backup_repository_controller.go @@ -238,6 +238,10 @@ func (r *BackupRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } + if backupRepo.Spec.RepositoryType != velerov1api.BackupRepositoryTypeKopia { + return ctrl.Result{}, nil + } + bsl, bslErr := r.getBSL(ctx, backupRepo) if bslErr != nil { log.WithError(bslErr).Error("Fail to get BSL for BackupRepository. Skip reconciling.") @@ -323,23 +327,6 @@ func (r *BackupRepoReconciler) getIdentifierByBSL(bsl *velerov1api.BackupStorage func (r *BackupRepoReconciler) initializeRepo(ctx context.Context, req *velerov1api.BackupRepository, bsl *velerov1api.BackupStorageLocation, log logrus.FieldLogger) error { log.WithField("repoConfig", r.backupRepoConfig).Info("Initializing backup repository") - var repoIdentifier string - // Only get restic identifier for restic repositories - if req.Spec.RepositoryType == "" || req.Spec.RepositoryType == velerov1api.BackupRepositoryTypeRestic { - var err error - repoIdentifier, err = r.getIdentifierByBSL(bsl, req) - if err != nil { - return r.patchBackupRepository(ctx, req, func(rr *velerov1api.BackupRepository) { - rr.Status.Message = err.Error() - rr.Status.Phase = velerov1api.BackupRepositoryPhaseNotReady - - if rr.Spec.MaintenanceFrequency.Duration <= 0 { - rr.Spec.MaintenanceFrequency = metav1.Duration{Duration: r.getRepositoryMaintenanceFrequency(req)} - } - }) - } - } - config, err := getBackupRepositoryConfig(ctx, r, r.backupRepoConfig, r.namespace, req.Name, req.Spec.RepositoryType, log) if err != nil { log.WithError(err).Warn("Failed to get repo config, repo config is ignored") @@ -349,11 +336,6 @@ func (r *BackupRepoReconciler) initializeRepo(ctx context.Context, req *velerov1 // defaulting - if the patch fails, return an error so the item is returned to the queue if err := r.patchBackupRepository(ctx, req, func(rr *velerov1api.BackupRepository) { - // Only set ResticIdentifier for restic repositories - if rr.Spec.RepositoryType == "" || rr.Spec.RepositoryType == velerov1api.BackupRepositoryTypeRestic { - rr.Spec.ResticIdentifier = repoIdentifier - } - if rr.Spec.MaintenanceFrequency.Duration <= 0 { rr.Spec.MaintenanceFrequency = metav1.Duration{Duration: r.getRepositoryMaintenanceFrequency(req)} } @@ -579,22 +561,6 @@ func dueForMaintenance(req *velerov1api.BackupRepository, now time.Time) bool { func (r *BackupRepoReconciler) checkNotReadyRepo(ctx context.Context, req *velerov1api.BackupRepository, bsl *velerov1api.BackupStorageLocation, log logrus.FieldLogger) (bool, error) { log.Info("Checking backup repository for readiness") - // Only check and update restic identifier for restic repositories - if req.Spec.RepositoryType == "" || req.Spec.RepositoryType == velerov1api.BackupRepositoryTypeRestic { - repoIdentifier, err := r.getIdentifierByBSL(bsl, req) - if err != nil { - return false, r.patchBackupRepository(ctx, req, repoNotReady(err.Error())) - } - - if repoIdentifier != req.Spec.ResticIdentifier { - if err := r.patchBackupRepository(ctx, req, func(rr *velerov1api.BackupRepository) { - rr.Spec.ResticIdentifier = repoIdentifier - }); err != nil { - return false, err - } - } - } - // we need to ensure it (first check, if check fails, attempt to init) // because we don't know if it's been successfully initialized yet. if err := ensureRepo(req, r.repositoryManager); err != nil { diff --git a/pkg/controller/backup_repository_controller_test.go b/pkg/controller/backup_repository_controller_test.go index 81a973200..cecd4d7d8 100644 --- a/pkg/controller/backup_repository_controller_test.go +++ b/pkg/controller/backup_repository_controller_test.go @@ -98,32 +98,6 @@ func TestPatchBackupRepository(t *testing.T) { } func TestCheckNotReadyRepo(t *testing.T) { - // Test for restic repository - t.Run("restic repository", func(t *testing.T) { - rr := mockBackupRepositoryCR() - rr.Spec.BackupStorageLocation = "default" - rr.Spec.ResticIdentifier = "fake-identifier" - rr.Spec.VolumeNamespace = "volume-ns-1" - rr.Spec.RepositoryType = velerov1api.BackupRepositoryTypeRestic - reconciler := mockBackupRepoReconciler(t, "PrepareRepo", rr, nil) - err := reconciler.Client.Create(t.Context(), rr) - require.NoError(t, err) - location := velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Config: map[string]string{"resticRepoPrefix": "s3:test.amazonaws.com/bucket/restic"}, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: rr.Spec.BackupStorageLocation, - }, - } - - _, err = reconciler.checkNotReadyRepo(t.Context(), rr, &location, reconciler.logger) - require.NoError(t, err) - assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) - assert.Equal(t, "s3:test.amazonaws.com/bucket/restic/volume-ns-1", rr.Spec.ResticIdentifier) - }) - // Test for kopia repository t.Run("kopia repository", func(t *testing.T) { rr := mockBackupRepositoryCR() @@ -149,32 +123,6 @@ func TestCheckNotReadyRepo(t *testing.T) { // ResticIdentifier should remain empty for kopia assert.Empty(t, rr.Spec.ResticIdentifier) }) - - // Test for empty repository type (defaults to restic) - t.Run("empty repository type", func(t *testing.T) { - rr := mockBackupRepositoryCR() - rr.Spec.BackupStorageLocation = "default" - rr.Spec.ResticIdentifier = "fake-identifier" - rr.Spec.VolumeNamespace = "volume-ns-1" - // Deliberately leave RepositoryType empty - reconciler := mockBackupRepoReconciler(t, "PrepareRepo", rr, nil) - err := reconciler.Client.Create(t.Context(), rr) - require.NoError(t, err) - location := velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Config: map[string]string{"resticRepoPrefix": "s3:test.amazonaws.com/bucket/restic"}, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: rr.Spec.BackupStorageLocation, - }, - } - - _, err = reconciler.checkNotReadyRepo(t.Context(), rr, &location, reconciler.logger) - require.NoError(t, err) - assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) - assert.Equal(t, "s3:test.amazonaws.com/bucket/restic/volume-ns-1", rr.Spec.ResticIdentifier) - }) } func startMaintenanceJobFail(client.Client, context.Context, *velerov1api.BackupRepository, string, logrus.Level, *logging.FormatFlag, logrus.FieldLogger) (string, error) { @@ -1605,58 +1553,6 @@ func TestInitializeRepoWithRepositoryTypes(t *testing.T) { corev1api.AddToScheme(scheme) velerov1api.AddToScheme(scheme) - // Test for restic repository - t.Run("restic repository", func(t *testing.T) { - rr := mockBackupRepositoryCR() - rr.Spec.BackupStorageLocation = "default" - rr.Spec.VolumeNamespace = "volume-ns-1" - rr.Spec.RepositoryType = velerov1api.BackupRepositoryTypeRestic - - location := &velerov1api.BackupStorageLocation{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: "default", - }, - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "test-bucket", - Prefix: "test-prefix", - }, - }, - Config: map[string]string{ - "region": "us-east-1", - }, - }, - } - - fakeClient := clientFake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(rr, location).Build() - mgr := &repomokes.Manager{} - mgr.On("PrepareRepo", rr).Return(nil) - - reconciler := NewBackupRepoReconciler( - velerov1api.DefaultNamespace, - velerotest.NewLogger(), - fakeClient, - mgr, - testMaintenanceFrequency, - "", - "", - logrus.InfoLevel, - nil, - nil, - ) - - err := reconciler.initializeRepo(t.Context(), rr, location, reconciler.logger) - require.NoError(t, err) - - // Verify ResticIdentifier is set for restic - assert.NotEmpty(t, rr.Spec.ResticIdentifier) - assert.Contains(t, rr.Spec.ResticIdentifier, "volume-ns-1") - assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) - }) - // Test for kopia repository t.Run("kopia repository", func(t *testing.T) { rr := mockBackupRepositoryCR() @@ -1707,58 +1603,6 @@ func TestInitializeRepoWithRepositoryTypes(t *testing.T) { assert.Empty(t, rr.Spec.ResticIdentifier) assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) }) - - // Test for empty repository type (defaults to restic) - t.Run("empty repository type", func(t *testing.T) { - rr := mockBackupRepositoryCR() - rr.Spec.BackupStorageLocation = "default" - rr.Spec.VolumeNamespace = "volume-ns-1" - // Leave RepositoryType empty - - location := &velerov1api.BackupStorageLocation{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: "default", - }, - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "test-bucket", - Prefix: "test-prefix", - }, - }, - Config: map[string]string{ - "region": "us-east-1", - }, - }, - } - - fakeClient := clientFake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(rr, location).Build() - mgr := &repomokes.Manager{} - mgr.On("PrepareRepo", rr).Return(nil) - - reconciler := NewBackupRepoReconciler( - velerov1api.DefaultNamespace, - velerotest.NewLogger(), - fakeClient, - mgr, - testMaintenanceFrequency, - "", - "", - logrus.InfoLevel, - nil, - nil, - ) - - err := reconciler.initializeRepo(t.Context(), rr, location, reconciler.logger) - require.NoError(t, err) - - // Verify ResticIdentifier is set when type is empty (defaults to restic) - assert.NotEmpty(t, rr.Spec.ResticIdentifier) - assert.Contains(t, rr.Spec.ResticIdentifier, "volume-ns-1") - assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) - }) } func TestRepoMaintenanceMetricsRecording(t *testing.T) { diff --git a/pkg/datapath/file_system.go b/pkg/datapath/file_system.go index f0f84acdb..61fac1e47 100644 --- a/pkg/datapath/file_system.go +++ b/pkg/datapath/file_system.go @@ -251,11 +251,9 @@ func (fs *fileSystemBR) boostRepoConnect(ctx context.Context, repositoryType str if err := repoProvider.NewUnifiedRepoProvider(*credentialGetter, repositoryType, fs.log).BoostRepoConnect(ctx, repoProvider.RepoParam{BackupLocation: fs.backupLocation, BackupRepo: fs.backupRepo, CacheDir: cacheDir}); err != nil { return err } - } else { - if err := repoProvider.NewResticRepositoryProvider(*credentialGetter, filesystem.NewFileSystem(), fs.log).BoostRepoConnect(ctx, repoProvider.RepoParam{BackupLocation: fs.backupLocation, BackupRepo: fs.backupRepo}); err != nil { - return err - } + + return nil } - return nil + return errors.Errorf("error getting provider for repo %s", repositoryType) } diff --git a/pkg/podvolume/backupper.go b/pkg/podvolume/backupper.go index 1747f1b33..6b534d5ed 100644 --- a/pkg/podvolume/backupper.go +++ b/pkg/podvolume/backupper.go @@ -272,7 +272,7 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. return nil, pvcSummary, []error{err} } - repositoryType := funcGetRepositoryType(b.uploaderType) + repositoryType := funcGetRepositoryType() if repositoryType == "" { err := errors.Errorf("empty repository type, uploader %s", b.uploaderType) skipAllPodVolumes(pod, volumesToBackup, err, pvcSummary, log) @@ -305,11 +305,6 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. } } - repoIdentifier := "" - if repositoryType == velerov1api.BackupRepositoryTypeRestic { - repoIdentifier = repo.Spec.ResticIdentifier - } - for _, volumeName := range volumesToBackup { volume, ok := podVolumes[volumeName] if !ok { @@ -366,7 +361,7 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. continue } - volumeBackup := newPodVolumeBackup(backup, pod, volume, repoIdentifier, b.uploaderType, pvc) + volumeBackup := newPodVolumeBackup(backup, pod, volume, "", b.uploaderType, pvc) // the PVB must be added into the indexer before creating it in API server otherwise unexpected behavior may happen: // the PVB may be handled very quickly by the controller and the informer handler will insert the PVB before "b.pvbIndexer.Add(volumeBackup)" runs, // this causes the PVB inserted by "b.pvbIndexer.Add(volumeBackup)" overrides the PVB in the indexer while the PVB inserted by "b.pvbIndexer.Add(volumeBackup)" diff --git a/pkg/podvolume/backupper_test.go b/pkg/podvolume/backupper_test.go index 846f65796..f7686978a 100644 --- a/pkg/podvolume/backupper_test.go +++ b/pkg/podvolume/backupper_test.go @@ -580,7 +580,7 @@ func TestBackupPodVolumes(t *testing.T) { require.NoError(t, err) if test.mockGetRepositoryType { - funcGetRepositoryType = func(string) string { return "" } + funcGetRepositoryType = func() string { return "" } } else { funcGetRepositoryType = getRepositoryType } diff --git a/pkg/podvolume/restorer.go b/pkg/podvolume/restorer.go index 47219ae99..ce662d5de 100644 --- a/pkg/podvolume/restorer.go +++ b/pkg/podvolume/restorer.go @@ -166,11 +166,6 @@ func (r *restorer) RestorePodVolumes(data RestoreData, tracker *volume.RestoreVo podVolumes[podVolume.Name] = podVolume } - repoIdentifier := "" - if repositoryType == velerov1api.BackupRepositoryTypeRestic { - repoIdentifier = repo.Spec.ResticIdentifier - } - for volume, backupInfo := range volumesToRestore { volumeObj, ok := podVolumes[volume] var pvc *corev1api.PersistentVolumeClaim @@ -185,7 +180,7 @@ func (r *restorer) RestorePodVolumes(data RestoreData, tracker *volume.RestoreVo } } - volumeRestore := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, backupInfo.snapshotID, backupInfo.snapshotSize, repoIdentifier, backupInfo.uploaderType, data.SourceNamespace, pvc) + volumeRestore := newPodVolumeRestore(data.Restore, data.Pod, data.BackupLocation, volume, backupInfo.snapshotID, backupInfo.snapshotSize, "", backupInfo.uploaderType, data.SourceNamespace, pvc) if err := veleroclient.CreateRetryGenerateName(r.crClient, r.ctx, volumeRestore); err != nil { errs = append(errs, errors.WithStack(err)) continue diff --git a/pkg/podvolume/util.go b/pkg/podvolume/util.go index 1864e9615..730932768 100644 --- a/pkg/podvolume/util.go +++ b/pkg/podvolume/util.go @@ -62,12 +62,12 @@ func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod // GetPvbRepositoryType returns the repositoryType according to the PVB information func GetPvbRepositoryType(pvb *velerov1api.PodVolumeBackup) string { - return getRepositoryType(pvb.Spec.UploaderType) + return getRepositoryType() } // GetPvrRepositoryType returns the repositoryType according to the PVR information func GetPvrRepositoryType(pvr *velerov1api.PodVolumeRestore) string { - return getRepositoryType(pvr.Spec.UploaderType) + return getRepositoryType() } // getVolumeBackupInfoForPod returns a map, of volume name -> VolumeBackupInfo, @@ -97,7 +97,7 @@ func getVolumeBackupInfoForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, snapshotID: pvb.Status.SnapshotID, snapshotSize: pvb.Status.Progress.TotalBytes, uploaderType: getUploaderTypeOrDefault(pvb.Spec.UploaderType), - repositoryType: getRepositoryType(pvb.Spec.UploaderType), + repositoryType: getRepositoryType(), } } @@ -111,7 +111,7 @@ func getVolumeBackupInfoForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, } for k, v := range fromAnnntation { - volumes[k] = volumeBackupInfo{v, 0, uploader.ResticType, velerov1api.BackupRepositoryTypeRestic} + volumes[k] = volumeBackupInfo{v, 0, uploader.KopiaType, velerov1api.BackupRepositoryTypeKopia} } return volumes @@ -135,7 +135,7 @@ func GetSnapshotIdentifier(podVolumeBackups *velerov1api.PodVolumeBackupList) ma VolumeNamespace: item.Spec.Pod.Namespace, BackupStorageLocation: item.Spec.BackupStorageLocation, SnapshotID: item.Status.SnapshotID, - RepositoryType: getRepositoryType(item.Spec.UploaderType), + RepositoryType: getRepositoryType(), UploaderType: item.Spec.UploaderType, Source: item.Status.Path, RepoIdentifier: item.Spec.RepoIdentifier, @@ -167,24 +167,11 @@ func getUploaderTypeOrDefault(uploaderType string) string { return uploader.ResticType } -// getRepositoryType returns the hardcode repositoryType for different backup methods - Restic or Kopia,uploaderType -// indicates the method. -// For Restic backup method, it is always hardcode to BackupRepositoryTypeRestic, never changed. -// For Kopia backup method, this means we hardcode repositoryType as BackupRepositoryTypeKopia for Unified Repo, -// at present (Kopia backup method is using Unified Repo). However, it doesn't mean we could deduce repositoryType -// from uploaderType for Unified Repo. -// TODO: post v1.10, refactor this function for Kopia backup method. In future, when we have multiple implementations of -// Unified Repo (besides Kopia), we will add the repositoryType to BSL, because by then, we are not able to hardcode -// the repositoryType to BackupRepositoryTypeKopia for Unified Repo. -func getRepositoryType(uploaderType string) string { - switch uploaderType { - case "", uploader.ResticType: - return velerov1api.BackupRepositoryTypeRestic - case uploader.KopiaType: - return velerov1api.BackupRepositoryTypeKopia - default: - return "" - } +// getRepositoryType returns the hardcode repositoryType. +// TODO: In future, when we have multiple implementations of Unified Repo (besides Kopia), we will add the repositoryType to BSL, +// because by then, we are not able to hardcode the repositoryType to BackupRepositoryTypeKopia for Unified Repo. +func getRepositoryType() string { + return velerov1api.BackupRepositoryTypeKopia } func isPVBMatchPod(pvb *velerov1api.PodVolumeBackup, podName string, namespace string) bool { diff --git a/pkg/repository/manager/manager.go b/pkg/repository/manager/manager.go index abe76299f..4d03931c0 100644 --- a/pkg/repository/manager/manager.go +++ b/pkg/repository/manager/manager.go @@ -109,10 +109,6 @@ func NewManager( log: log, } - mgr.providers[velerov1api.BackupRepositoryTypeRestic] = provider.NewResticRepositoryProvider(credentials.CredentialGetter{ - FromFile: credentialFileStore, - FromSecret: credentialSecretStore, - }, mgr.fileSystem, mgr.log) mgr.providers[velerov1api.BackupRepositoryTypeKopia] = provider.NewUnifiedRepoProvider(credentials.CredentialGetter{ FromFile: credentialFileStore, FromSecret: credentialSecretStore, @@ -275,8 +271,6 @@ func (m *manager) ClientSideCacheLimit(repo *velerov1api.BackupRepository) (int6 func (m *manager) getRepositoryProvider(repo *velerov1api.BackupRepository) (provider.Provider, error) { switch repo.Spec.RepositoryType { - case "", velerov1api.BackupRepositoryTypeRestic: - return m.providers[velerov1api.BackupRepositoryTypeRestic], nil case velerov1api.BackupRepositoryTypeKopia: return m.providers[velerov1api.BackupRepositoryTypeKopia], nil default: diff --git a/pkg/repository/manager/manager_test.go b/pkg/repository/manager/manager_test.go index ea38c7448..88d8db481 100644 --- a/pkg/repository/manager/manager_test.go +++ b/pkg/repository/manager/manager_test.go @@ -32,15 +32,13 @@ func TestGetRepositoryProvider(t *testing.T) { repo := &velerov1.BackupRepository{} // empty repository type - provider, err := mgr.getRepositoryProvider(repo) - require.NoError(t, err) - assert.NotNil(t, provider) + _, err := mgr.getRepositoryProvider(repo) + require.Error(t, err) - // valid repository type - repo.Spec.RepositoryType = velerov1.BackupRepositoryTypeRestic - provider, err = mgr.getRepositoryProvider(repo) - require.NoError(t, err) - assert.NotNil(t, provider) + // invalid repository type + repo.Spec.RepositoryType = "restic" + _, err = mgr.getRepositoryProvider(repo) + require.Error(t, err) // invalid repository type repo.Spec.RepositoryType = "unknown" @@ -61,6 +59,6 @@ func TestGetRepositoryConfigProvider(t *testing.T) { assert.NotNil(t, provider) // invalid repository type - _, err = mgr.getRepositoryProvider(velerov1.BackupRepositoryTypeRestic) + _, err = mgr.getRepositoryProvider("restic") require.Error(t, err) } diff --git a/pkg/repository/provider/restic.go b/pkg/repository/provider/restic.go deleted file mode 100644 index 51a46bbf1..000000000 --- a/pkg/repository/provider/restic.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright the Velero contributors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package provider - -import ( - "context" - "strings" - "time" - - "github.com/sirupsen/logrus" - - "github.com/vmware-tanzu/velero/internal/credentials" - "github.com/vmware-tanzu/velero/pkg/repository/restic" - "github.com/vmware-tanzu/velero/pkg/util/filesystem" -) - -func NewResticRepositoryProvider(credGetter credentials.CredentialGetter, fs filesystem.Interface, log logrus.FieldLogger) Provider { - return &resticRepositoryProvider{ - svc: restic.NewRepositoryService(credGetter, fs, log), - } -} - -type resticRepositoryProvider struct { - svc *restic.RepositoryService -} - -func (r *resticRepositoryProvider) InitRepo(ctx context.Context, param RepoParam) error { - return r.svc.InitRepo(param.BackupLocation, param.BackupRepo) -} - -func (r *resticRepositoryProvider) ConnectToRepo(ctx context.Context, param RepoParam) error { - return r.svc.ConnectToRepo(param.BackupLocation, param.BackupRepo) -} - -func (r *resticRepositoryProvider) PrepareRepo(ctx context.Context, param RepoParam) error { - if err := r.ConnectToRepo(ctx, param); err != nil { - // If the repository has not yet been initialized, the error message will always include - // the following string. This is the only scenario where we should try to initialize it. - // Other errors (e.g. "already locked") should be returned as-is since the repository - // does already exist, but it can't be connected to. - if strings.Contains(err.Error(), "Is there a repository at the following location?") { - return r.InitRepo(ctx, param) - } - - return err - } - - return nil -} - -func (r *resticRepositoryProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { - return nil -} - -func (r *resticRepositoryProvider) PruneRepo(ctx context.Context, param RepoParam) error { - return r.svc.PruneRepo(param.BackupLocation, param.BackupRepo) -} - -func (r *resticRepositoryProvider) EnsureUnlockRepo(ctx context.Context, param RepoParam) error { - return r.svc.UnlockRepo(param.BackupLocation, param.BackupRepo) -} - -func (r *resticRepositoryProvider) Forget(ctx context.Context, snapshotID string, param RepoParam) error { - return r.svc.Forget(param.BackupLocation, param.BackupRepo, snapshotID) -} - -func (r *resticRepositoryProvider) BatchForget(ctx context.Context, snapshotIDs []string, param RepoParam) []error { - errs := []error{} - for _, snapshot := range snapshotIDs { - err := r.Forget(ctx, snapshot, param) - if err != nil { - errs = append(errs, err) - } - } - - return errs -} - -func (r *resticRepositoryProvider) DefaultMaintenanceFrequency() time.Duration { - return r.svc.DefaultMaintenanceFrequency() -} - -func (r *resticRepositoryProvider) ClientSideCacheLimit(repoOption map[string]string) int64 { - return 0 -} diff --git a/pkg/repository/restic/repository.go b/pkg/repository/restic/repository.go deleted file mode 100644 index 7260542e1..000000000 --- a/pkg/repository/restic/repository.go +++ /dev/null @@ -1,147 +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 restic - -import ( - "os" - "time" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - "github.com/vmware-tanzu/velero/internal/credentials" - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" - "github.com/vmware-tanzu/velero/pkg/restic" - veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" - "github.com/vmware-tanzu/velero/pkg/util/filesystem" -) - -func NewRepositoryService(credGetter credentials.CredentialGetter, fs filesystem.Interface, log logrus.FieldLogger) *RepositoryService { - return &RepositoryService{ - credGetter: credGetter, - fileSystem: fs, - log: log, - } -} - -type RepositoryService struct { - credGetter credentials.CredentialGetter - fileSystem filesystem.Interface - log logrus.FieldLogger -} - -func (r *RepositoryService) InitRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error { - return r.exec(restic.InitCommand(repo.Spec.ResticIdentifier), bsl) -} - -func (r *RepositoryService) ConnectToRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error { - snapshotsCmd := restic.SnapshotsCommand(repo.Spec.ResticIdentifier) - // use the '--latest=1' flag to minimize the amount of data fetched since - // we're just validating that the repo exists and can be authenticated - // to. - // "--last" is replaced by "--latest=1" in restic v0.12.1 - snapshotsCmd.ExtraFlags = append(snapshotsCmd.ExtraFlags, "--latest=1") - - return r.exec(snapshotsCmd, bsl) -} - -func (r *RepositoryService) PruneRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error { - return r.exec(restic.PruneCommand(repo.Spec.ResticIdentifier), bsl) -} - -func (r *RepositoryService) UnlockRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error { - return r.exec(restic.UnlockCommand(repo.Spec.ResticIdentifier), bsl) -} - -func (r *RepositoryService) Forget(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository, snapshotID string) error { - return r.exec(restic.ForgetCommand(repo.Spec.ResticIdentifier, snapshotID), bsl) -} - -func (r *RepositoryService) DefaultMaintenanceFrequency() time.Duration { - return restic.DefaultMaintenanceFrequency -} - -func (r *RepositoryService) exec(cmd *restic.Command, bsl *velerov1api.BackupStorageLocation) error { - file, err := r.credGetter.FromFile.Path(repokey.RepoKeySelector()) - if err != nil { - return err - } - // ignore error since there's nothing we can do and it's a temp file. - defer os.Remove(file) - - cmd.PasswordFile = file - - // if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic - var caCertFile string - if bsl.Spec.ObjectStorage != nil { - var caCertData []byte - - // Try CACertRef first (new method), then fall back to CACert (deprecated) - if bsl.Spec.ObjectStorage.CACertRef != nil { - caCertString, err := r.credGetter.FromSecret.Get(bsl.Spec.ObjectStorage.CACertRef) - if err != nil { - return errors.Wrap(err, "error getting CA certificate from secret") - } - caCertData = []byte(caCertString) - } else if bsl.Spec.ObjectStorage.CACert != nil { - caCertData = bsl.Spec.ObjectStorage.CACert - } - - if caCertData != nil { - caCertFile, err = restic.TempCACertFile(caCertData, bsl.Name, r.fileSystem) - if err != nil { - return errors.Wrap(err, "error creating temp cacert file") - } - // ignore error since there's nothing we can do and it's a temp file. - defer os.Remove(caCertFile) - } - } - cmd.CACertFile = caCertFile - - // CmdEnv uses credGetter.FromFile (not FromSecret) to get cloud provider credentials. - // FromFile materializes the BSL's Credential secret to a file path that cloud SDKs - // can read (e.g., AWS_SHARED_CREDENTIALS_FILE). This is different from caCertRef above, - // which uses FromSecret to read the CA certificate data directly into memory, then - // writes it to a temp file because restic CLI only accepts file paths (--cacert flag). - env, err := restic.CmdEnv(bsl, r.credGetter.FromFile) - if err != nil { - return err - } - cmd.Env = env - - // #4820: restrieve insecureSkipTLSVerify from BSL configuration for - // AWS plugin. If nothing is return, that means insecureSkipTLSVerify - // is not enable for Restic command. - skipTLSRet := restic.GetInsecureSkipTLSVerifyFromBSL(bsl, r.log) - if len(skipTLSRet) > 0 { - cmd.ExtraFlags = append(cmd.ExtraFlags, skipTLSRet) - } - - stdout, stderr, err := veleroexec.RunCommandWithLog(cmd.Cmd(), r.log) - r.log.WithFields(logrus.Fields{ - "repository": cmd.RepoName(), - "command": cmd.String(), - "stdout": stdout, - "stderr": stderr, - }).Debugf("Ran restic command") - if err != nil { - return errors.Wrapf(err, "error running command=%s, stdout=%s, stderr=%s", cmd.String(), stdout, stderr) - } - - return nil -} From dca3d3001f0c42ad68af699094eb21428bf2b31d Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 7 Apr 2026 15:58:28 +0800 Subject: [PATCH 2/2] remove restic for repo Signed-off-by: Lyndon-Li --- changelogs/unreleased/9676-Lyndon-Li‎‎ | 1 + .../backup_deletion_controller_test.go | 6 +- .../backup_repository_controller.go | 18 +- .../backup_repository_controller_test.go | 32 +-- pkg/podvolume/restorer_test.go | 18 -- pkg/repository/config/config.go | 68 ----- pkg/repository/config/config_test.go | 261 ------------------ 7 files changed, 15 insertions(+), 389 deletions(-) create mode 100644 changelogs/unreleased/9676-Lyndon-Li‎‎ delete mode 100644 pkg/repository/config/config_test.go diff --git a/changelogs/unreleased/9676-Lyndon-Li‎‎ b/changelogs/unreleased/9676-Lyndon-Li‎‎ new file mode 100644 index 000000000..2fb765e29 --- /dev/null +++ b/changelogs/unreleased/9676-Lyndon-Li‎‎ @@ -0,0 +1 @@ +Fix issue #9470, remove restic from repository \ No newline at end of file diff --git a/pkg/controller/backup_deletion_controller_test.go b/pkg/controller/backup_deletion_controller_test.go index ab3687438..58d9b0420 100644 --- a/pkg/controller/backup_deletion_controller_test.go +++ b/pkg/controller/backup_deletion_controller_test.go @@ -821,12 +821,12 @@ func TestGetSnapshotsInBackup(t *testing.T) { { VolumeNamespace: "ns-1", SnapshotID: "snap-3", - RepositoryType: "restic", + RepositoryType: "kopia", }, { VolumeNamespace: "ns-1", SnapshotID: "snap-4", - RepositoryType: "restic", + RepositoryType: "kopia", }, }, }, @@ -876,7 +876,7 @@ func TestGetSnapshotsInBackup(t *testing.T) { { VolumeNamespace: "ns-1", SnapshotID: "snap-3", - RepositoryType: "restic", + RepositoryType: "kopia", }, }, }, diff --git a/pkg/controller/backup_repository_controller.go b/pkg/controller/backup_repository_controller.go index 3f33bc814..eb90660f4 100644 --- a/pkg/controller/backup_repository_controller.go +++ b/pkg/controller/backup_repository_controller.go @@ -43,7 +43,6 @@ import ( "github.com/vmware-tanzu/velero/pkg/constant" "github.com/vmware-tanzu/velero/pkg/label" "github.com/vmware-tanzu/velero/pkg/metrics" - repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config" "github.com/vmware-tanzu/velero/pkg/repository/maintenance" repomanager "github.com/vmware-tanzu/velero/pkg/repository/manager" "github.com/vmware-tanzu/velero/pkg/util/kube" @@ -249,7 +248,7 @@ func (r *BackupRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) } if backupRepo.Status.Phase == "" || backupRepo.Status.Phase == velerov1api.BackupRepositoryPhaseNew { - if err := r.initializeRepo(ctx, backupRepo, bsl, log); err != nil { + if err := r.initializeRepo(ctx, backupRepo, log); err != nil { log.WithError(err).Error("error initialize repository") return ctrl.Result{}, errors.WithStack(err) } @@ -267,7 +266,7 @@ func (r *BackupRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request) switch backupRepo.Status.Phase { case velerov1api.BackupRepositoryPhaseNotReady: - ready, err := r.checkNotReadyRepo(ctx, backupRepo, bsl, log) + ready, err := r.checkNotReadyRepo(ctx, backupRepo, log) if err != nil { return ctrl.Result{}, err } else if !ready { @@ -315,16 +314,7 @@ func (r *BackupRepoReconciler) getBSL(ctx context.Context, req *velerov1api.Back return loc, nil } -func (r *BackupRepoReconciler) getIdentifierByBSL(bsl *velerov1api.BackupStorageLocation, req *velerov1api.BackupRepository) (string, error) { - repoIdentifier, err := repoconfig.GetRepoIdentifier(bsl, req.Spec.VolumeNamespace) - if err != nil { - return "", errors.Wrapf(err, "error to get identifier for repo %s", req.Name) - } - - return repoIdentifier, nil -} - -func (r *BackupRepoReconciler) initializeRepo(ctx context.Context, req *velerov1api.BackupRepository, bsl *velerov1api.BackupStorageLocation, log logrus.FieldLogger) error { +func (r *BackupRepoReconciler) initializeRepo(ctx context.Context, req *velerov1api.BackupRepository, log logrus.FieldLogger) error { log.WithField("repoConfig", r.backupRepoConfig).Info("Initializing backup repository") config, err := getBackupRepositoryConfig(ctx, r, r.backupRepoConfig, r.namespace, req.Name, req.Spec.RepositoryType, log) @@ -558,7 +548,7 @@ func dueForMaintenance(req *velerov1api.BackupRepository, now time.Time) bool { return req.Status.LastMaintenanceTime == nil || req.Status.LastMaintenanceTime.Add(req.Spec.MaintenanceFrequency.Duration).Before(now) } -func (r *BackupRepoReconciler) checkNotReadyRepo(ctx context.Context, req *velerov1api.BackupRepository, bsl *velerov1api.BackupStorageLocation, log logrus.FieldLogger) (bool, error) { +func (r *BackupRepoReconciler) checkNotReadyRepo(ctx context.Context, req *velerov1api.BackupRepository, log logrus.FieldLogger) (bool, error) { log.Info("Checking backup repository for readiness") // we need to ensure it (first check, if check fails, attempt to init) diff --git a/pkg/controller/backup_repository_controller_test.go b/pkg/controller/backup_repository_controller_test.go index cecd4d7d8..8a458033f 100644 --- a/pkg/controller/backup_repository_controller_test.go +++ b/pkg/controller/backup_repository_controller_test.go @@ -107,17 +107,8 @@ func TestCheckNotReadyRepo(t *testing.T) { reconciler := mockBackupRepoReconciler(t, "PrepareRepo", rr, nil) err := reconciler.Client.Create(t.Context(), rr) require.NoError(t, err) - location := velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Config: map[string]string{"resticRepoPrefix": "s3:test.amazonaws.com/bucket/restic"}, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: rr.Spec.BackupStorageLocation, - }, - } - _, err = reconciler.checkNotReadyRepo(t.Context(), rr, &location, reconciler.logger) + _, err = reconciler.checkNotReadyRepo(t.Context(), rr, reconciler.logger) require.NoError(t, err) assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) // ResticIdentifier should remain empty for kopia @@ -411,17 +402,8 @@ func TestInitializeRepo(t *testing.T) { reconciler := mockBackupRepoReconciler(t, "PrepareRepo", rr, nil) err := reconciler.Client.Create(t.Context(), rr) require.NoError(t, err) - location := velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Config: map[string]string{"resticRepoPrefix": "s3:test.amazonaws.com/bucket/restic"}, - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: velerov1api.DefaultNamespace, - Name: rr.Spec.BackupStorageLocation, - }, - } - err = reconciler.initializeRepo(t.Context(), rr, &location, reconciler.logger) + err = reconciler.initializeRepo(t.Context(), rr, reconciler.logger) require.NoError(t, err) assert.Equal(t, velerov1api.BackupRepositoryPhaseReady, rr.Status.Phase) } @@ -1442,7 +1424,7 @@ func TestDeleteOldMaintenanceJobWithConfigMap(t *testing.T) { MaintenanceFrequency: metav1.Duration{Duration: testMaintenanceFrequency}, BackupStorageLocation: "default", VolumeNamespace: "test-ns", - RepositoryType: "restic", + RepositoryType: "kopia", }, Status: velerov1api.BackupRepositoryStatus{ Phase: velerov1api.BackupRepositoryPhaseReady, @@ -1479,7 +1461,7 @@ func TestDeleteOldMaintenanceJobWithConfigMap(t *testing.T) { MaintenanceFrequency: metav1.Duration{Duration: testMaintenanceFrequency}, BackupStorageLocation: "default", VolumeNamespace: "test-ns", - RepositoryType: "restic", + RepositoryType: "kopia", }, Status: velerov1api.BackupRepositoryStatus{ Phase: velerov1api.BackupRepositoryPhaseReady, @@ -1498,8 +1480,8 @@ func TestDeleteOldMaintenanceJobWithConfigMap(t *testing.T) { Name: "repo-maintenance-job-config", }, Data: map[string]string{ - "global": `{"keepLatestMaintenanceJobs": 5}`, - "test-ns-default-restic": `{"keepLatestMaintenanceJobs": 2}`, + "global": `{"keepLatestMaintenanceJobs": 5}`, + "test-ns-default-kopia": `{"keepLatestMaintenanceJobs": 2}`, }, }, }, @@ -1596,7 +1578,7 @@ func TestInitializeRepoWithRepositoryTypes(t *testing.T) { nil, ) - err := reconciler.initializeRepo(t.Context(), rr, location, reconciler.logger) + err := reconciler.initializeRepo(t.Context(), rr, reconciler.logger) require.NoError(t, err) // Verify ResticIdentifier is NOT set for kopia diff --git a/pkg/podvolume/restorer_test.go b/pkg/podvolume/restorer_test.go index 36a1fc034..e10146578 100644 --- a/pkg/podvolume/restorer_test.go +++ b/pkg/podvolume/restorer_test.go @@ -204,24 +204,6 @@ func TestRestorePodVolumes(t *testing.T) { }, }, }, - { - name: "get repository type fail", - pvbs: []*velerov1api.PodVolumeBackup{ - createPVBObj(true, true, 1, "restic"), - createPVBObj(true, true, 2, "kopia"), - }, - kubeClientObj: []runtime.Object{ - createNodeAgentDaemonset(), - }, - restoredPod: createPodObj(false, false, false, 2), - sourceNamespace: "fake-ns", - errs: []expectError{ - { - err: "multiple repository type in one backup", - prefixOnly: true, - }, - }, - }, { name: "ensure repo fail", pvbs: []*velerov1api.PodVolumeBackup{ diff --git a/pkg/repository/config/config.go b/pkg/repository/config/config.go index 46a5478e6..04761e95b 100644 --- a/pkg/repository/config/config.go +++ b/pkg/repository/config/config.go @@ -17,14 +17,7 @@ limitations under the License. package config import ( - "fmt" - "path" "strings" - - "github.com/pkg/errors" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "github.com/vmware-tanzu/velero/pkg/persistence" ) type BackendType string @@ -40,56 +33,6 @@ const ( CredentialsFileKey = "credentialsFile" ) -// this func is assigned to a package-level variable so it can be -// replaced when unit-testing -var getAWSBucketRegion = GetAWSBucketRegion - -// getRepoPrefix returns the prefix of the value of the --repo flag for -// restic commands, i.e. everything except the "/". -func getRepoPrefix(location *velerov1api.BackupStorageLocation) (string, error) { - var bucket, prefix string - - if location.Spec.ObjectStorage != nil { - layout := persistence.NewObjectStoreLayout(location.Spec.ObjectStorage.Prefix) - - bucket = location.Spec.ObjectStorage.Bucket - prefix = layout.GetResticDir() - } - - backendType := GetBackendType(location.Spec.Provider, location.Spec.Config) - - if repoPrefix := location.Spec.Config["resticRepoPrefix"]; repoPrefix != "" { - return repoPrefix, nil - } - - switch backendType { - case AWSBackend: - var url string - // non-AWS, S3-compatible object store - if s3Url := location.Spec.Config["s3Url"]; s3Url != "" { - url = strings.TrimSuffix(s3Url, "/") - } else { - var err error - region := location.Spec.Config["region"] - if region == "" { - region, err = getAWSBucketRegion(bucket, location.Spec.Config) - } - if err != nil { - return "", errors.Wrapf(err, "failed to detect the region via bucket: %s", bucket) - } - url = fmt.Sprintf("s3-%s.amazonaws.com", region) - } - - return fmt.Sprintf("s3:%s/%s", url, path.Join(bucket, prefix)), nil - case AzureBackend: - return fmt.Sprintf("azure:%s:/%s", bucket, prefix), nil - case GCPBackend: - return fmt.Sprintf("gs:%s:/%s", bucket, prefix), nil - } - - return "", errors.Errorf("invalid backend type %s, provider %s", backendType, location.Spec.Provider) -} - // GetBackendType returns a backend type that is known by Velero. // If the provider doesn't indicate a known backend type, but the endpoint is // specified, Velero regards it as a S3 compatible object store and return AWSBackend as the type. @@ -111,14 +54,3 @@ func GetBackendType(provider string, config map[string]string) BackendType { func IsBackendTypeValid(backendType BackendType) bool { return (backendType == AWSBackend || backendType == AzureBackend || backendType == GCPBackend || backendType == FSBackend) } - -// GetRepoIdentifier returns the string to be used as the value of the --repo flag in -// restic commands for the given repository. -func GetRepoIdentifier(location *velerov1api.BackupStorageLocation, name string) (string, error) { - prefix, err := getRepoPrefix(location) - if err != nil { - return "", err - } - - return fmt.Sprintf("%s/%s", strings.TrimSuffix(prefix, "/"), name), nil -} diff --git a/pkg/repository/config/config_test.go b/pkg/repository/config/config_test.go deleted file mode 100644 index aac5fc9bc..000000000 --- a/pkg/repository/config/config_test.go +++ /dev/null @@ -1,261 +0,0 @@ -/* -Copyright 2018, 2019 the Velero contributors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" -) - -func TestGetRepoIdentifier(t *testing.T) { - testCases := []struct { - name string - bsl *velerov1api.BackupStorageLocation - repoName string - getAWSBucketRegion func(s string, config map[string]string) (string, error) - expected string - expectedErr string - }{ - { - name: "error is returned if BSL uses unsupported provider and resticRepoPrefix is not set", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "unsupported-provider", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket-2", - Prefix: "prefix-2", - }, - }, - }, - }, - repoName: "repo-1", - expectedErr: "invalid backend type velero.io/unsupported-provider, provider unsupported-provider", - }, - { - name: "resticRepoPrefix in BSL config is used if set", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "custom-repo-identifier", - Config: map[string]string{ - "resticRepoPrefix": "custom:prefix:/restic", - }, - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "repo-1", - expected: "custom:prefix:/restic/repo-1", - }, - { - name: "s3Url in BSL config is used", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "custom-repo-identifier", - Config: map[string]string{ - "s3Url": "s3Url", - }, - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "repo-1", - expected: "s3:s3Url/bucket/prefix/restic/repo-1", - }, - { - name: "s3.amazonaws.com URL format is used if region cannot be determined for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - }, - }, - }, - }, - repoName: "repo-1", - getAWSBucketRegion: func(s string, config map[string]string) (string, error) { - return "", errors.New("no region found") - }, - expected: "", - expectedErr: "failed to detect the region via bucket: bucket: no region found", - }, - { - name: "s3.s3-.amazonaws.com URL format is used if region can be determined for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - }, - }, - }, - }, - repoName: "repo-1", - getAWSBucketRegion: func(string, map[string]string) (string, error) { - return "eu-west-1", nil - }, - expected: "s3:s3-eu-west-1.amazonaws.com/bucket/restic/repo-1", - }, - { - name: "prefix is included in repo identifier if set for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "repo-1", - getAWSBucketRegion: func(s string, config map[string]string) (string, error) { - return "eu-west-1", nil - }, - expected: "s3:s3-eu-west-1.amazonaws.com/bucket/prefix/restic/repo-1", - }, - { - name: "s3Url is used in repo identifier if set for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - Config: map[string]string{ - "s3Url": "alternate-url", - }, - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "repo-1", - getAWSBucketRegion: func(s string, config map[string]string) (string, error) { - return "eu-west-1", nil - }, - expected: "s3:alternate-url/bucket/prefix/restic/repo-1", - }, - { - name: "region is used in repo identifier if set for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - Config: map[string]string{ - "region": "us-west-1", - }, - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "aws-repo", - getAWSBucketRegion: func(s string, config map[string]string) (string, error) { - return "eu-west-1", nil - }, - expected: "s3:s3-us-west-1.amazonaws.com/bucket/prefix/restic/aws-repo", - }, - { - name: "trailing slash in s3Url is not included in repo identifier for AWS BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "aws", - Config: map[string]string{ - "s3Url": "alternate-url-with-trailing-slash/", - }, - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "bucket", - Prefix: "prefix", - }, - }, - }, - }, - repoName: "aws-repo", - getAWSBucketRegion: func(s string, config map[string]string) (string, error) { - return "eu-west-1", nil - }, - expected: "s3:alternate-url-with-trailing-slash/bucket/prefix/restic/aws-repo", - }, - { - name: "repo identifier includes bucket and prefix for Azure BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "azure", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "azure-bucket", - Prefix: "azure-prefix", - }, - }, - }, - }, - repoName: "azure-repo", - expected: "azure:azure-bucket:/azure-prefix/restic/azure-repo", - }, - { - name: "repo identifier includes bucket and prefix for GCP BSL", - bsl: &velerov1api.BackupStorageLocation{ - Spec: velerov1api.BackupStorageLocationSpec{ - Provider: "gcp", - StorageType: velerov1api.StorageType{ - ObjectStorage: &velerov1api.ObjectStorageLocation{ - Bucket: "gcp-bucket", - Prefix: "gcp-prefix", - }, - }, - }, - }, - repoName: "gcp-repo", - expected: "gs:gcp-bucket:/gcp-prefix/restic/gcp-repo", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - getAWSBucketRegion = tc.getAWSBucketRegion - id, err := GetRepoIdentifier(tc.bsl, tc.repoName) - assert.Equal(t, tc.expected, id) - if tc.expectedErr == "" { - assert.NoError(t, err) - } else { - require.EqualError(t, err, tc.expectedErr) - assert.Empty(t, id) - } - }) - } -}