diff --git a/changelogs/unreleased/5752-sseago b/changelogs/unreleased/5752-sseago new file mode 100644 index 0000000000..73b8af0456 --- /dev/null +++ b/changelogs/unreleased/5752-sseago @@ -0,0 +1 @@ +Define itemoperations.json format and update DownloadRequest API diff --git a/config/crd/v1/bases/velero.io_downloadrequests.yaml b/config/crd/v1/bases/velero.io_downloadrequests.yaml index 7c5304b6be..49e7f4d4b4 100644 --- a/config/crd/v1/bases/velero.io_downloadrequests.yaml +++ b/config/crd/v1/bases/velero.io_downloadrequests.yaml @@ -46,10 +46,11 @@ spec: - BackupLog - BackupContents - BackupVolumeSnapshots - - BackupItemSnapshots + - BackupItemOperations - BackupResourceList - RestoreLog - RestoreResults + - RestoreItemOperations - CSIBackupVolumeSnapshots - CSIBackupVolumeSnapshotContents type: string diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index 65a3378067..92c513b8bc 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -33,7 +33,7 @@ var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Ms\xdc:rw\xfd\x8a.\xe5\xe0\xdd*\xcd\xe8\xb9rHJ7?Y\xae\xa8ދ\xad\xb2\xb4\xdaC\x92\x03\x86\xec\x99\xc1\x13\bp\x01p\xe4I*\xff=\xd5\x00\xf8\r\x92\x18Y\xda}/e\\l\x91@\x03\xe8n\xf4\x17\x9a=g\xab\xd5ꌕ\xfc\x11\xb5\xe1J^\x01+9~\xb3(\xe9/\xb3~\xfaW\xb3\xe6\xea\xf2\xf0\xfe\xec\x89\xcb\xfc\n\xae+cU\xf1\x15\x8d\xaat\x86\x1fq\xcb%\xb7\\ɳ\x02-˙eWg\x00LJe\x19=6\xf4'@\xa6\xa4\xd5J\bԫ\x1d\xca\xf5S\xb5\xc1M\xc5E\x8e\xda\x01\xaf\xa7>\xfc\xb4\xfe\x97\xf5Og\x00\x99F7\xfc\x81\x17h,+\xca+\x90\x95\x10g\x00\x92\x15x\x05\x1b\x96=U\xa5Y\x1fP\xa0Vk\xae\xceL\x89\x19͵Ӫ*\xaf\xa0}ᇄu\xf8=\xfc\xecF\xbb\a\x82\x1b\xfbK\xe7\xe1\xaf\xdcX\xf7\xa2\x14\x95f\xa2\x99\xc9=3\\\xee*\xc1t\xfd\xf4\f\xc0d\xaa\xc4+\xf8LS\x94,\xc3\xfc\f l\xc7M\xb9\n\v>\xbc\xf7\x10\xb2=\x16̯\x05@\x95(?\xdc\xdd>\xfe\xf3}\xef1@\x8e&Ӽ\xb4\x0e)~a\xc0\r0xt\xdb\x02\x1d\xd0\x0fv\xcf,h,5\x1a\x94ր\xdd#d\xac\xb4\x95FP[\xf8\xa5ڠ\x96h\xd14\xa0\x012Q\x19\x8b\x1a\x8ce\x16\x81Y`P*.-p\t\x96\x17\b\u007f\xfapw\vj\xf3\x1bf\xd6\x00\x9390cTƙ\xc5\x1c\x0eJT\x05\xfa\xb1\u007f^7PK\xadJԖ\xd7x\xf6\xad\xc3U\x9d\xa7\x83\xed\xbd#\f\xf8^\x90\x13;\xa1\xdfF\xc0\"\xe6\x01i\xb4\x1f\xbb\xe7\xa6ݮ\xe3\x90\x1e`\xa0NL\x86ů\xe1\x1e5\x81\x01\xb3W\x95ȉ\v\x0f\xa8\ta\x99\xdaI\xfe\xdf\rl\x03V\xb9I\x05\xb3\x18\x18\xa0m\\ZԒ\t80Q\xe1\x85CI\xc1\x8e\xa0\x91f\x81Jv\xe0\xb9.f\r\xff\xae4\x02\x97[u\x05{kKsuy\xb9\xe3\xb6>M\x99*\x8aJr{\xbct\a\x83o*\xab\xb4\xb9\xcc\xf1\x80\xe2\xd2\xf0݊\xe9l\xcf-fD\xc8KV\xf2\x95[\xbat'j]\xe4\xffT3\x80y\xd7[\xab=\x123\x1a\xab\xb9\xdcu^8\xae\x9f\xa1\x00\x1d\x00\xcf_~\xa8\xdfE\x8bhzD\xd8\xf9zs\xff\xd0\xe5=n\x86\xd8wx\xef0dK\x02B\x18\x97[Ԟ\x88[\xad\n\a\x13e\xee\xb9ϱ\xae\xe0(\x87\xe87զ\xe0\x96\xe8\xfe\xb7\n\r1\xb9Zõ\x131\xb0A\xa8ʜ8s\r\xb7\x12\xaeY\x81\xe2\x9a\x19|s\x02\x10\xa6͊\x10\x9bF\x82\xaet\x1cv\xf6X뼨e\xd9\x04\xbd\xbc@\xb8/1\xeb\x1d\x18\x1aŷ\xd8V$K\xc6x\x06 ^\x9e\xe4\x01.\x8dE\x96\xaf\xcf_\x93@\xf8-\x13U\x8eyc\xb6\x8cd\xc0\x8087\xa3\x01Τc\\\x92\xd6 #\x8a\x90+۷d\x98D\xb6\xca4\x02\xc9m.=\x97\xf3\xeeGZ.͋3h\xfa\x192\x13\"\xfa\xd4+\xa3\xf4D\xe1\xf4\x1c\x99\xf9\xa4\x96S2c\x86y/\x93@\x97\xf3aR<Džܗ\x17d\xbc$f;~\xf7\xc5XJNˋ2Y\x16\x13\x02\x13\xf3W\xfa\x99)\xf3 O\xc8ZIB\xcer\x86\xca\xc9y)!\x0fdv\x1f\xc9\xd9(\x91<\x93Y\xc0\x939(s\xd9%\vQ\xa9q\xe6IzN\xc9,h\x97o\xb2\x9cI\xf2z\xf9\xa2\xafa\x03O\x8b\x9a\xc5l\x90E\x1by~}\x8b\xf9\x1e\xa7dy,b\xec\x85\x19\x1dM\xc6\xc6ļ\xa7\xe6q\xf4\xf34&\x80\xa6doLdgL@\x9c\xcd\xd9H\xcdɘ\x80\xbd\xa0vg\xb9d\xe6e\xfcC`X\xd4o\xe2\xef\xc5Q/ݘ\xd2=sq\xc9B\xff2\xe8N\xb4\xac\xad\xa6y\xf33fyr\xbb?\xdd\xfc,*ay)\\8\xff\xc0\xf3\xa8\xd3h\xf7xl>\xeb\xfcM\xb9Ϝ6G\a\xe9\xcb׆=\xd7\x03#\x9a\x19xF!\x80Řk\xb4\xf3\xcc\u007f˞\xa9\x15\x92̧\x03\x17>X\r\x9f\xbc_x\x0ev_r\xc5\"\x9ev\x8f\x05A\xa9\xbf|=\xc1\xfd\x987\x10\xbd-\xeb\x9e\xfd\xadB}\x04u@\xddZ\f\v\xdf\x11\xf8\x83f*\xd1&n\x05\xf9\xe1+(\f\f\xe7\xf6\xc0\xc1\a\xe9UX\x14\xec`\x8d\x0e\x0e\x9dy\xd1К\xc4\x1b\xf9\x01\x13]\xe3\x81\x0fՌ\x8e\xbc_\xb2=S\x93\xf0\xdf\xd6u8\xddyXT\xdbo\xe2@\xbc܅\x98\x01\x99\x9aT\x9fv\x01\xb5\x98D\xffV\xaeĒ3\x91lE\xa5%ɿEr\xfc\tI\xf1'8\x15\xa7\xb9\x15\xc9hJI~\u007f\x13\xe7\xe2\r\u074b\xb7p0^\xe6b,\x80\x1c$\xb5\xa7\xa4\xab']\xae&\xdf/\xa4\\\x8e._\x01̧\xa1'\xa4\x9f'\\\x0e,\xad4!\xcd\xfc\xb4\xf4\xf2\x04\x1c\xbe\x91\xf3\xf1F\xee\xc7[8 o\xeb\x82,:!\x8b\x9c3\xfb\xfa\xc5\xd1e\xa5sԳ\xc1\xf8TV\x9be\xb2\x81\xbfПs\xf0Em]\xe1\x85z\xf5L\xd3XH\xb9\xf9\xfa3\x83_\xb8\xcc==\x88\xa9:z\xbcwC\xd0\x1a\x16\xf1\x04\x81\xd6j\v\x15\xb0\xfc\xb5\x82\xc1\x92iW6ms\xf4W\x93f\r7,\xdb\x0f\xa0\xef\xa3~\xc2V\xe9\x82Y8o\xeed.=p\xfa\xfb|\r\xf0I5\x97^݊\n\x86\x17\xa58\x92\x1f\x10\x81y\xde\x05\xf12\x86\x882\x93\t\xe5\x9aB\xfd\x9a\x05\xdf\xef\xbe\xdf;r\x99W\x97\xeeɄ\xaa\xf2\x06\xfa\x041\x99<\xc2ݣ\xb3Q\\ِ\xac-\xa1\x12\xac\x90\xda\a\x1cVX\xf9\xf9\xf5/\xf7\x8cU\x9a\xed\xf0W\xe5\xebp-a\xa2\u07fbW\x84-Ȏ\xfa\xb2\xbd\xfe\x16#\xa6SCE\xb0\x01\xb06\x87fT\r\x8aV\x19\x13*3\xe7\xd1Z\xb1\xb0\x99\x87\x87_\xfd\x06,/p\xfd\xb1\xf2\x17\xa9\xab\x92i\x83\x84\xcdzc~І\xfe\xbbWϱX\x87\n{\xfey\xb8n\x8d.O\xc7\xddמ\xb4\xfaC\xaf\xaaX\x8d\xa2%F}\x8c\x8f\xea8j\x1d\"\xf93\x1fuЧ\xe0t\n+\xba\x10\x86\xfb\xce\xe6u\xcb\xfeLI\xf1\xa9\xd2s\xae\xdc\xdar\xf19_\x95-\x94\x9a\f\xd9^\x95v5{B\xc56W\xe3\xe6e\xf5\xe7|rJ\xaf\xfc\xe7<\x9d\xae\xc7#\\\x91G\x9dw\xea\xcf5e\xc0\x9e\x99i\x12`\xa2\x8a\xb5\x05\xe7G:˖\xa0a\x0ex@\tJ\xba|\x17W\r\xc7\x17\"\x1d\x8e\x89@\xedB\t\t5U)\x14\xcb\xeb\x13^\xeb\xb0P\xbc\xf2\xc1\xc9/}@\xfd\xce\xcc\xc0l\n\xbbE\x900\x16\x98^\xa9\\A\xce,\xae\xa2@\x93d_\x94\xd92\xc3\xfb\x8cn>XK~B\xccv\x1e\x16\x10\x9c\x1aY\xebc\xab,\x13 \xabb\xe3\x15<\xab;\xc4\xe87*#hB\x06\xd4\xcc\xf1\xf2\x1b\xe3\xd2\xe2n\x14c\x1c\xef\xec\xba染w\u058c\x9cڙ\xa9\xb2\f\x8d\xd9VB\xc4L\xfd\x86s_\u007f\x9b.\xb7o\xb1\xe6\x99\xeb\xe4E\xa0K\f\xac\xcb\xea\xf9\xcc\xc0\x02\x8da\xbb\xba\xd8\xd93i\xa0\x1dJtfP,\xfe\xe8\x1d\xc56\x93\xac_\xea\xcbG\xb4Xf+\x16&\xa83\x01:\xbd\xde\xc5\xec\x02\xa1v\xbe \"\xaf\xcb\xc1֪\xf9D\x9c|+\xb9NQ\xe57MG\u008d\vF;B\xb4\xe5{Q\xf0\x1d'=HD\xda1\xbda;\\eJ\bti\xe7\xe3u\xbd\xe5a\r\xf9z_\x91\x99ŭ}\xea\xf6\r\x91\x0fOm_)\x83\xf9r\x8b\xae\x9a\xab\xe5\x1a\xdb\xf2ȣ\x05)7\xf1I\xaa\xdbc!ZHx\xbc\xd2n\xdf\xfa\x80\x05\xb9\x1a\xec\xe9PW\xf8\"\x18\x83q\xef\xb6`\xbf)}\x01\x05\x97\xf4\x0fY\xff.4Q\x0f>i\xfd\xae\x86\xddº\xef\xa8O\x936\xddQ\xa4X\x1f\x88)S5\x9e*\xbb\x82\xcf8\xb6\xac|\xf6+\xe6.\x18\x17\xab\x9eL]n\xe5\x9dV;\xf2\x8f#/\xffʸ\xe5r\xf7I\xe9;Q\xed\xb8\xfcR\x86t\xb9\xd3:\xdf1m9\x13\xe2\xe8\xd7\x13\x19\xdbH\xc9Ȼ\xe5ѓ/>\"i\xc8I3)N\xbf\x80\x8e%\x12\x86n\xad\xcfϥg9\x97 \xbbQ\x95\xedɬV\xe6\xc5\xef\x1b\x1c\xb05|V\x16\xebP2\xef\xc3$)\x8fƮp\xbbU\xda\xfa\x10\xc3j\x05|\x1b̮\x98OɸpWa\xbej2p\xdbf\x03\xb4\xc7\xc4yTڝvWZ\xaa`G\x9f,ɲ\x8c\xacz\xbc4\x96\x89\x88 \xfd\xae\xe4+g\xdf\x12\x9bc\xfe\x97\x88\xc17B\xf8m\xb7\u007f\xf3\xc5z\xa3F\x1d8\x8f9\x97\xcc\xee\x95HT\xa5\x82KqF\tϚ[K\x82\xbb{W\b\x96D\xb5\x10`HxM\xd4'\x9cS!\xee=)\xf9\xdb\xe9\xd8eߑj:O\xd9\bas\x8aȲq(\x98ؖ\xffX\x8a\x9bz,\x912\xdb3\xb9#\xa6Ҫ\xda\xedk\xbe\x9cP\xc1S\xa1\xbf\x8a\x16\x05\xa5;ئ\xbe\xa7\xb1\x95\x96\x9d\xd8O\xb8\xb9\xc9;\xcbe\xd9\xd3\xe4JC,\xba\xae\xdc\u007f\x19*\x0e\xae\xb6Z\x15\xab@\vw\xbdr\x11\xe21\x9a+r4\xec>\x8ar\xf0%\x91Ci/\xc7\x06e\x89\x12\x98\t\xebI\xf8\x92k\x9e\xacs\x01\x11˴Mu_\xee{\x9d\x17<\x17\a9\xbe\xde\xfb\x10o\xf2_\xb4]\x0f\u007fC\xe1\x02\f\x97\xf5\x8f\x06\xf8h\x96g\x05C\x0e\x8dF\x17\x14\x88ޜ\x8d\\\x91\x9e\xe3\xd1_\xfe\xdf\xd7\xe784\xaa\xec&\xc5x}\x1ct\x1f\xa4\x05\xbb\xda\xd8M\x97`pF\xf0\xf1'\xbe\xf5\x97y\x19\xad\xfa\xcf\xff\xf0t\xdfC\x92q\xf4n\xd6.r&Oc\xe0,T¾\x13H\x06\x8bA\xec\x9b\\\xefN\xb2\xad\x0f/\xf3\x16_\xd3U\xac\u007f\xcf\xe2u\x1c\xa8\xc3˜\xc47\xf3\x10_ww\xcf\xcc\xd5\xd0_:c\u007f\r\xdd\".b\x80\x10q\x12#\xdbh\xdc\xc6E'\xb1\xe3#\xd6k\x9c(\xb3=\xf0\x1b_\xc9K\x8c\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm\xe4\x8de\x19\x12\xbb~\x1e\xfen\u0379/\x02_\xff4\x8d\xfb3Sҫ[s\x05\xff\xf1_g\x10B\xbb\x8f\xf5o\xd0\xd0\xc3\xff\v\x00\x00\xff\xfflA\x99e\xe3g\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKo#\xb9\x11\xbe\xebW\x14f\x0f\xbe\x8cZ\xb3\xc9!\x81.\x81F\x93\x00\x83x\xd6\xc6\xc8q\x0eI\x80\xa5Ȓ\xc45\x9b\xec\xf0!\xad\x12\xe4\xbf\aŇ\xba\xd5ݲ\xe4A\xb2ˋ->\x8aU_\xbdٓ\xe9t:a\x8d|F\xeb\xa4\xd1s`\x8dğ=j\xfa媗\u07fbJ\x9a\xd9\xfe\xfbɋ\xd4b\x0e\xcb༩\xbf\xa23\xc1r\xfc\x84\x1b\xa9\xa5\x97FOj\xf4L0\xcf\xe6\x13\x00\xa6\xb5\xf1\x8c\xa6\x1d\xfd\x04\xe0F{k\x94B;ݢ\xae^\xc2\x1a\xd7A*\x816\x12/W\xef?T\xbf\xab>L\x00\xb8\xc5x\xfcI\xd6\xe8<\xab\x9b9\xe8\xa0\xd4\x04@\xb3\x1a\xe7\xb0f\xfc%4\xce\x1b˶\xa8\fOwU{ThM%\xcd\xc45\xc8\xe9\xea\xad5\xa1\x99C\xbb\x90(d\xb6\x92H\x1f#\xb1U\"v\x9f\x89\xc5u%\x9d\xff\xf3\xe5=\xf7\xd2\xf9\xb8\xafQ\xc12u\x89\xad\xb8\xc5\xed\x8c\xf5?\xb4WOa\xedTZ\x91z\x1b\x14\xb3\x17\x8eO\x00\x1c7\r\xce!\x9en\x18G1\x01ȘEjS`BD-0\xf5h\xa5\xf6h\x97F\x85Z\x9f\xee\x12踕\x8d\x8f('Y \v\x03E\x1ap\x9e\xf9\xe0\xc0\x05\xbe\x03\xe6`\xb1gR\xb1\xb5\xc2\xd9_4+\xffGz\x00?9\xa3\x1f\x99\xdf͡J\xa7\xaaf\xc7\\YM:z\xec\xcc\xf8#\t༕z;\xc6\xd2=s\xfe\x99))NZ\a\xe9\xc0\xef\x10\x14s\x1e\x8c\xb6e,\x1e?\x97ț\x1c(\xfb[ƪ\x82E\xf6\\\xb3\x81\x0f \xa4\xa3\x02\xc0E\xa2C\xb0\xa8<\xa3\xf59x\x1b\xde$>7z#\xb7C\xa1\xbb5\xcd%\x8b\xb9B\xba\x87\xdc2\xdeD\xa1\x89\xac\xa3\xb1f/\x05\xda)\xf9\x87\xdcH\x9e9\t6e\xae\x8dD%\xdcP\xd2\v^\x16E\xb1(ȫ\x99\xba\xa2\xc3\xe5ic,\x8d\x99\xd4ɂ[\x021\xd8\xd8:\xa7T\xedQ\x8bS5rƍ\x89Qˡ\x80\x83\xf4\xbb\x14\x0e\u0558\xdf\xc1\xab\xbeG\xe3\x05\x8fc\xd3=ޟvH;S\x02Ep\xc8-\xfahm\xa8\xc8|Ȕ*\x80/\xc1ŀڏ\x13e\xc4B\xad\x9c~\xc1\xe3\x10h\xb8\xa6\xdc\\\xc2\\g\xf9\x8eJ\xe7°\xc5\rZ\xd4~4\xa8Sgb5z\x8cq]\x18\xee(\xa4sl\xbc\x9b\x99=ڽ\xc4\xc3\xec`\xec\x8b\xd4\xdb)\x01>\xcd\x1e4\x8bm\xc5\xec\xbb\xf8\xe7\x82\xc8O\x0f\x9f\x1e\xe6\xb0\x10\x02\x8cߡ%\xadm\x82*\x86֩o\xde\xc7\x1c\xfb\x1e\x82\x14\u007f\xb8\xfb\x16\\L\x93<\xe7\x06lV\xd1\xfa\x8fT\xa8E\xa6\b\xa2UҊ\xb1@\x99\x92\x94]gm\xa6X3f\x88c\x15fwP`\xa2\f2\x16Q_p\x18L_q\xb3\\\xec^\xf1\xb1RHK-$\xa7B\xec\xdc7J\x83!\xce\xea\xed\x11\xc1\xfa\x15\xf8\xa5\x880.x\x12 \xe7\xc3+\x1c?t\xf7\xb6mY\nO9\xc79\xf4T@9\xd0H9\x90\xd9!r1(p\xa35y\xa37\xc0N\xa1\xee\xce\xf5c\xfc\x1b#\xc4:\xf0\x17\x1c\x01~ \xcaǸ\xb1`\x9c\x8e\x11/\xc1a\f\xbe\xd7\u0600\xeb6\xce\xd9\x12\xed-\xbc,\x17\xb4\xf1\x94&\x19,\x17\xb0\x0eZ(,\x1c\x1dv\xa8\xa9C\x90\x9b\xe3\xf8]4\x9e\xeeW\x05\xd5Xa\xe4\x1a\xbf`;.C\x8a\xe1sX\x1fGj\x82\x1b\x84l,n\xe4\xcf7\b\xf9\x187\x16\xc0\x1b\xe6w \xb5\x93\x02\x81\x8d\xc0\x9f\x8a\xb5\v\x82\x9e\xf2\xffC\x8e\"ߠ\x9e\u05fc=\xb1\xf3\x16\x87/\x18_\xf1\x9fǼ\xed\x84B\xf9\x9d#\xffy-xɏG%ڟ\x1e\f\xfe\x94*,>\x92*Ϙy\x1e\x9ex\xa5R+\xcf\x16c\xceLu\x81\xb1\x16]c\xb4\xa0\xe6\xe9\xb6:\xade\xf9\u007fW\xad\x8d\xabuz\x1e\xe5zkE\v7\xb5*\xf1\x89\xe6\xcd\xcdJz\xb8\xea\xb6\x02f\xed\xa8Sl\xfb\x95\x9e\x8c\xbfH\x9b\xf2\xaeӧP?\xac!\xe8X\xa9Ō_\xc1\xdf5|\xa2ޖ\xb2\x93\x98\x13\xdfv\xcc\x00\xa4\x03m\x0et\xbcC/\x92\x00\xa3S\xbe\xa6n\x8di\x91\x9b\xe1\xb8t\x90JQƶX\x9b\xfdhƦBӢ:\x02sd:\xfb\xdfT\x1f\xaaw\xbfZ\x17\xa4\x98\xf3\xd4Ԡ\xf8\x8a{9|\xe5\x19\xa2{?8Q\x1c\xff\xe4\x0e\xf4\xe3\xc7\xd2,\xcfl\xde\xf6\xe3\b\x18\x1b\xa9\xa8\x16\x1c\x89\x13m\xc50|\x8f\xfc\xb8\xba\xbfs\xb1\x84G\xed\xc7ʾ\x03Z\x8c\x1d\x13\n\xaa\xe2M~\x97\bΣ\x1d1\x80\x93\xf6\xa2\xceA\x19\xbd\xed9N\x1a\xf9\x95\x82*\xb4dPƂ@O\xa9Io\x81\xef\x98\xdeb\xfb\n\x95\xf9\u007f\x9dS2\x9f\x9eʹ\x16\"\xf5%\xf3\xb8I\xa3Or\xacL\x1f\xbc\x00\xb7\x9b\xc7_\u007f\v\xf7E\xb3\x17ۜ+\xb8\x0f\xf6\x97,M\xa0N}\xfb\"\u070eooo\x87\xcf\xcd7 \xf1ַ\xf0W\xde5\xe0\xc0\\\xfb*\xfe\xeb\xe1PS\xb5z\xb5\x04\xfe\x92v\xa5\xe7\xc3|\x04\xd8\xda\x04\xff\x9agލ\x19t~\xee\u007f\v\x8f\xf1#Ƶ\"\x83\xf6\x14\x8d\xf0`\xa9\x95l_\xc5bP\x18\xcb-\xb7?/-z\xdfZ\xbak\xc3/17\xc85\x9ak\a\x93)_v\xf4\x9aA\xee΄\xf5\xe9\xa5x\x0e\xff\xfeϤMה\x13\x1b\x8f\xe2\x87\xfeǵw)d\x94/d\xf1'\xa7:&}\x1d\x84\xbf\xfdc\x92\xaeB\xf1\\>i\xd1\xe4\u007f\x03\x00\x00\xff\xff\x1d\r\x93\v\x97\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96Ms\xe36\x0f\xc7\xef\xfa\x14\x98}\x0e{y$\xefN\x0f\xed\xe8\xd6\xcd\xee!\xd36\xe3I2\xb9tz\xa0I\xd8\xe2F\"Y\x00t\xeav\xfa\xdd;$%\xbf\xc8v6=\x947\x91 \xf0\xe7\x0f\x04Ī\xae\xebJ\x05\xfb\x84\xc4ֻ\x16T\xb0\xf8\x87\xa0K_\xdc<\xff\xc0\x8d\xf5\x8b\xed\xc7\xea\xd9:\xd3\xc2Md\xf1\xc3=\xb2\x8f\xa4\xf13\xae\xad\xb3b\xbd\xab\x06\x14e\x94\xa8\xb6\x02P\xceyQi\x9a\xd3'\x80\xf6N\xc8\xf7=R\xbdA\xd7<\xc7\x15\xae\xa2\xed\rRv>\x85\xde~h\xbeo>T\x00\x9a0o\u007f\xb4\x03\xb2\xa8!\xb4\xe0b\xdfW\x00N\r\u0602\xc1\x1e\x05WJ?\xc7@\xf8{D\x16n\xb6\xd8#\xf9\xc6\xfa\x8a\x03\xea\x14xC>\x86\x16\x0e\ve\xff(\xaa\x1c\xe8sv\xf5)\xbb\xba/\xae\xf2joY~\xbaf\xf1\xb3\x1d\xadB\x1fI\xf5\x97\x05e\x03\xb6n\x13{E\x17M*\x00\xd6>`\vwIVP\x1aM\x050\xf2\xc82kP\xc6dª_\x92u\x82t\xe3\xfb8Ldk0Țl\x90L\xf0\xb1\xc3|D\xf0k\x90\x0e\xa1\x84\x03\xf1\xb0\xc2Q\x81\xc9\xfb\x00\xbe\xb2wK%]\vM\xe2\xd5\x14\xd3$d4(\xa8?ͧe\x97\x04\xb3\x90u\x9bk\x12X\x94D\x9eD\xe4\xb8\xd6;\xa0#\xbe\xa7\x02\xb2}\x13:ŧ\xd1\x1f\xf2µ\xc8\xc5f\xfb\xb1\x90\xd6\x1d\x0e\xaa\x1dm}@\xf7\xe3\xf2\xf6黇\x93i8\xd5z!\xb5`\x19Ԥ4\x81+\xd4\xc0;\x04O0x\x9a\xa8r\xb3w\x1a\xc8\a$\xb1\xd3\xd5*㨪\x8efg\x12\xde'\x95\xc5\nL*'\xe4\fm\xbc\x04hƃ\x15\x98\x96\x810\x102\xbaR`'\x8e!\x19)\a~\xf5\x15\xb54\xf0\x80\x94\xdc\x00w>\xf6&U\xe1\x16I\x80P\xfb\x8d\xb3\u007f\xee}s:g\n\xda+9\xe4g\x1a\xf9\xd29\xd5\xc3V\xf5\x11\xff\x0f\xca\x19\x18\xd4\x0e\bS\x14\x88\xee\xc8_6\xe1\x06~I\x98\xac[\xfb\x16:\x91\xc0\xedb\xb1\xb12u\x13\xed\x87!:+\xbbEn\fv\x15\xc5\x13/\fn\xb1_\xb0\xddԊtg\x05\xb5D\u0085\n\xb6\xce\xd2]\xee(\xcd`\xfeGc\xff\xe1\xf7'Z\xcf.H\x19\xb9\xd0_\xc9@*\xf3\x92\xf6\xb2\xb5\x9c\xe2\x00:M%:\xf7_\x1e\x1ea\n\x9d\x931\xa7\x9f\xb9\x1f6\xf2!\x05\t\x98uk\xa4\x92\xc45\xf9!\xfbDg\x82\xb7N\xf2\x87\xee-\xba9~\x8e\xab\xc1\nOW2媁\x9b\xdcbSQ\xc7`\x94\xa0i\xe0\xd6\xc1\x8d\x1a\xb0\xbfQ\x8c\xffy\x02\x12i\xae\x13ط\xa5\xe0\xf8\xef07.Ԏ\x16\xa6\xf6}%_\x17\x8a\xf6!\xa0N\x19L\x10\xd3n\xbb\xb6:\x97\a\xac=\xc1Kgu7\x15\xed\x8c\xee\xbe\xc0\x9b\x93\x85\xcb\x05\x9dơM\xceW\xae\x1e\x1er\xee,\xe1\xec\x16\xd6p\xd6s_璛\xe1\xbf$S:\xf1\xc8FG\"trԟեMoe\x81D\x9e\xcefg\xa2\xbed\xa3\xfc\x04P\xd61(\xb7\x1b7\x82tJ\xe0\x05)\x95\x81\xf61\xf5\x194`\xe2\x19\xbf\x11\xcb\xf1\xbf$\x90\xd7\xc8ܜ\xd9Y\xc1ႦW\xb2\x93Fz^\xa8U\x8f-\bE\xbc\x92YE\xa4v\xb3\xb5\xfc\xcf\xfa\x06\x82e\xb2\xb9\x94\x83\xfd\u007f\xfa\x9bIȸ]\x1c\xce#\xd5p\x87/\x17foݒ\xfc\x86\x90\xe7W>-.\v\xbd\xfdc\xe0\r\x94.^ʳIN\xfd\xce\x1cQd\xf1\xa46\xc7\\9\xae\xf6\xfd\xbb\x85\xbf\xfe\xae\x0e\xf7Zi\x8dA\xd0\xdc\xcd_i\xefޝ<\xb7\xf2\xa7\xf6\xae\xbc\x8c\xb8\x85_\u007f\xabJ(4O\xd3\xeb)M\xfe\x13\x00\x00\xff\xff--\nM\xde\n\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WM\x8f\xdb6\x10\xbd\xfbW\f\xd2CZ \x92\x13\xf4\xd0·v\x93âi\x10\xd8\xe9^\x8a\x1ehj,\xb1K\x91,g\xe8\xcd\xf6\xd7\x17CJ\xfe\x90\xe5\xdd͡\xbc\x89\x1c\x0e\x1f\x1f\xdf.\xbd\u007f[\xffT\xbf]\x00\xe8\x88y\xfa\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\x83\xb3^5\x11\xffIHL\xf5\x1e-F_\x1b\xbf\xa0\x80Z\x16m\xa3Oa\x05ǁ2w\x00T6\xf3~H\xb3.i\xf2\x885Ŀ͍~4CD\xb0)*{\t\"\x0f\x92qm\xb2*^\f/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xbdgXհ\xbb\xfd\xbb\x92Jwث\x82\x17\xc0\at\xbf|\xbe\xbd\xfbqs\xd6\r\xd0 \xe9h\x02g\x06'\x98\xc1\x10(\x18\x10\x00\xfb\x03(P\x0eTd\xb3S\x9aa\x17}\x0f[\xa5\xefS8d\x05\xf0ۿQ3\x10\xfb\xa8Z|\x03\x94t\aJ\xf2\x95P\xb0\xbe\x85\x9d\xb1X\x1f&\x85\xe8\x03F6#˥\x9d\x88\xeb\xa4w\x02\xfc\xb5\xec\xadDA#\xaaB\x02\xeep\xe4\a\x9b\x81\x0e\xf0;\xe0\xce\x10D\f\x11\t]\xd1\xd9Yb\x90 \xe5\x86\x1d\u0530\xc1(i\x80:\x9fl#b\xdccd\x88\xa8}\xeb̿\x87\xdc$\fɢV\xf1(\x87c3\x8e1:ea\xafl\xc27\xa0\\\x03\xbdz\x84\x88\x99\xa7\xe4N\xf2\xe5\x10\xaa\xe1w\x1f\x11\x8c\xdb\xf9\x15t́V\xcbekx,*\xed\xfb>9Ï\xcb\\\x1ff\x9b\xd8GZ6\xb8G\xbb$\xd3V*\xea\xce0jN\x11\x97*\x98*Cw\xb9\xb0\xea\xbe\xf9.\x0eeH\xafϰ\xf2\xa3Ȍ8\x1aמ\fd\xcd?q\x02\xa2\xfa\"\x982\xb5\xec\xe2H\xb4t\t;\xeb\x0f\x9b/0.\x9d\x0fc\xca~Q\xcea\"\x1d\x8f@\b3n\x87\xb1\x1cbV\x9e\xe4D\xd7\x04o\x1c\xe7\x0fm\r\xba)\xfd\x94\xb6\xbda\x1a\xc5,gU\xc3Mv\x1a\xd8\"\xa4\xd0(Ʀ\x86[\a7\xaaG{\xa3\b\xff\xf7\x03\x10\xa6\xa9\x12b_v\x04\xa7&9\r.\xac\x9d\f\x8cNv\xe5\xbc&\xa5\xbe\t\xa8\xe5\xf4\x84@\x99ivF\xe7Ҁ\x9d\x8f\xa0\x8e\x95?\x10X\x9fe\x9e\xaf\xdc\fN\xc5\x16y\xda;\xc1\xf2%\a\xc9\xf2\x0f\x9d:7\x9a\xef\xb1nk\xf1\n\x1a\x80\x14\xf7\xf8\xa1\xbe\xc8x\x1d\x03̪w\x16\xc9(b\xa1Ax\x15+\x10\x93:\xc5t\xb9\xb44t\xa9\x9f_\xa0\x82_3揾}r\xfc\xc6;\x16\xb9?\x19t\xe7m\xeaq\xe3T\xa0\xce?\x13{\xcbؿ,r\xbc\x90\x0f\x97\xd4e\xe0\x1a\xc5\xca\xf1\xfa&\x86\x805R\xb2W\x97\xbb\xd9\xdc~\xcb>\xae\x84?\xc9ԕ\xda\x19[\xbe#\x9f\x17\x82ܲ\xa3\x10dJ\xb98\x10\xe4\xed\x11\x1d2\xd2\xd1\xc3\x1e\fw\xb3\x19\x01\x1e:\xa3\xbb<1\xabH\xec\x91\xc8k\x93\xcd\xe6\xdb\xe1K\xf1\x99\x883J\xae\xb2\xc2g\xba\x05\xfcE\xf7\x15˸\xb6@5\x94\xf1\x8bl\x87\x15'\xfa\x06\xe3\xc9\xf1#\xd5:ň\x8e\x87,\xf9\"\x9eNx\xa9\xf3\x8c\xe5\xfa\xc7\xfa\xe33\xf6\xf3\xfe\x18\x99\x9f\x9aʸ\x82&D\xacȴ\xf2|\x9011\xa0l\f\x97d\x94v\xfe\x9c9'j\xf6D\xf1k01\xdb\xec3\x10?\x1c\x02\x8bK\xa2+7\xe0\xf4\xc1\x96\x13\"\xe5ׅV\xd3w\x8d\xb4-B\x83\x16\x19\x1b\xd8>\x16\xbb\u007f$\xc6\xfe\x12\xf7\xce\xc7^\xf1\n\xe4f\xac\xd8\xcc\xc8H\x1e\xd5jkq\x05\x1c\xd35\x95\xcdnn\xbd{[\xffP\xbf]\x00\xe8\x88y\xf9\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\xa3\xb3^5\x11\xffNHL\xf5\x0e-F_\x1b\xbf\xa0\x80Z6m\xa3Oa\x05\x87\x89\xb2v\x00T\x92y?\x84\xb9+a\xf2\x8c5Ŀ\xcc\xcd~4\x83G\xb0)*{\x0e\"O\x92qm\xb2*\x9eM/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xdc3\xacj\xc8n\xf7\xae\x84\xd2\x1d\xf6\xaa\xe0\x05\xf0\x01\xddO\x9fo\xef\xbf_\x9f\x98\x01\x1a$\x1dM\xe0\xcc\xe0\x043\x18\x02\x05\x03\x02`\xbf\a\x05ʁ\x8al\xb6J3l\xa3\xefa\xa3\xf4C\n\xfb\xa8\x00~\xf3\x17j\x06b\x1fU\x8bo\x80\x92\xee@I\xbc\xe2\nַ\xb05\x16\xeb\xfd\xa2\x10}\xc0\xc8fd\xb9\x8c#q\x1dY'\xc0_Kn\xc5\v\x1aQ\x15\x12p\x87#?\xd8\ft\x80\xdf\x02w\x86 b\x88H\xe8\x8a\xceN\x02\x838)7dP\xc3\x1a\xa3\x84\x01\xea|\xb2\x8d\x88q\x87\x91!\xa2\xf6\xad3\xff\xecc\x930$\x9bZţ\x1c\x0e\xc38\xc6蔅\x9d\xb2\t߀r\r\xf4\xea\t\"f\x9e\x92;\x8a\x97]\xa8\x86_}D0n\xebW\xd01\aZ-\x97\xadᱨ\xb4\xef\xfb\xe4\f?-s}\x98Mb\x1fi\xd9\xe0\x0e\xed\x92L[\xa9\xa8;è9E\\\xaa`\xaa\f\xdd\xe5ª\xfb\xe6\u007fq(Cz}\x82\x95\x9fDf\xc4Ѹ\xf6h\"k\xfe\xca\t\x88\xea\x8b`\xcaҒŁh1\t;w\x1f\xd6_`\xdc:\x1fƔ\xfd\xa2\x9c\xfdB:\x1c\x81\x10f\xdc\x16c9Ĭ<\x89\x89\xae\t\xde8\xce\x1f\xda\x1atS\xfa)mz\xc34\x8aYΪ\x86\x9b\xdci`\x83\x90B\xa3\x18\x9b\x1an\x1dܨ\x1e\xed\x8d\"\xfc\xcf\x0f@\x98\xa6J\x88}\xd9\x11\x1c7ɩsa\xedhb\xecd\x17\xcekR\xea\xeb\x80ZNO\b\x94\x95fkt.\r\xd8\xfa\b\xeaP\xf9\x03\x81\xf5I\xe4\xf9\xca\xcd\xe0Tl\x91\xa7\xd6\t\x96/\xd9I\xb6\u007f\xec\xd4i\xa3\xf9?\xd6m-\xbd\x82\x06 \xa5{|W\x9fE\xbc\x8c\x01f\xd5;\x8bd\x14\xb1\xd0 \xbcJ+\x90&u\x8c\xe9|k\x19\xe8R?\xbfA\x05?g\xcc\x1f}{u\xfe\xc6;\x16\xb9_u\xba\xf76\xf5\xb8v*P\xe7\x9f\xf1\xbde\xec\u007f\v\x18\xcbUz\xd5u\xbc\x91\xf7\xb7Թ\xe3\x1dJ/\xc7\xcbY\f\x0ewH\xc9^D68\xbd\b\xda\xcd\xfa\xf6[\x92\xbe\xe0~\x95\xd6\v\x856\x8e|\xa1>\xaf\x1a\xb9\x92G\xd5Ȓr\xcb \xc8C%:d\xa4C\xc3{4\xdc\xcdF\x04x\xec\x8c\xee\xf2\xc2,9\xe9\xa5D^\x9bܙ\xbe\x1d\xbeT\xaa\x898#\xfb*\x97ÌY\xc0\x9f\x99/\xf4\x97K\x1bTCͿ\xa8G\xb1\xe2D\xdfХ\xb2\xffH\xb5N1\xa2\xe3!J\xbe\xb5\xa7\v^ڦ\xc6\xda\xfe\xfd\xee\xe33\xbd\xea\xfd\xc13\xbfK\x95q\x05M\x88X\x91i\xe5\xad!sҭr\x179'\xa3\x8cӷ\xcf)Q\xb3'\x8a_\x83)\x05\xf3\f\xc4\x0f{\xc7\xd2Rѕ\xebr\xfa\xba\xcb\x01\x91\xf2SD\xab\xe9#H\xc6\x06\xa1A\x8b\x8c\rl\x9e\xca\xdd\xf0D\x8c\xfd9\ueb4f\xbd\xe2\x15\xc85Z\xb1\x99\x91\x91\xbc\xc0\xd5\xc6\xe2\n8\xa6K*\x9bM\xf38\xfa\x01\xed\xe4\xfa\x8b\x99\xdb\x1e\xd1~[aޞ\xc3\xf4\xee\xfb\xe0@\xcb\x1a[\xb6\x8e+\x95F\xf9\xf6\xf6\xfa\xfe\xcfw\xa3a\x00m\x94F\xe3Dr\xe8\xe1\x1bı\xc1(\x8cY}A\x04\xc3*\xe0\x14\xc0\xd0\x06\xab\bc\xc8#\x86 \x0eaIu\rZ\x94nȒ\xf4\xa9-0\tj\xf3\v\x96\xae\x80;4D&\t\xa6Tr\x87Ɓ\xc1RUR\xfc\xb7\xa7mI\xd7\xe8І9\x8cq\xe5\xf0y\xd7/Y\x03;\xd6t\xf8\x1a\x98\xe4в=\x18\xa4S\xa0\x93\x03z~\x89-\xe0ge\x10\x84ܪ5\xd4\xcei\xbb^\xad*\xe1R\xfc.U\xdbvR\xb8\xfdʇb\xb1\xe9\x9c2v\xc5q\x87\xcdʊj\xc9LY\v\x87\xa5\xeb\f\xae\x98\x16K\x0f]\xfa\x18^\xb4\xfc\x1b\x13#\xbe\xbd\x18a\x9d)F\xf8|x=!\x01\n\xb0 ,\xb0\xb85\xdc\xe2\xc0\xe8\xe4 \xdf\xff\xe3\xee\x03\xa4\xa3\xbd0\xa6\xdc\xf7|?l\xb4\a\x11\x10Ä\xdcbt0[\xa3ZO\x13%\xd7JH\xe7\u007f\x94\x8d@9e\xbf\xed6\xadp$\xf7\xffth\x1dɪ\x80K\x9fԐ\xc3\xec4i./\xe0Z\xc2%k\xb1\xb9d\x16\xbf\xba\x00\x88\xd3vI\x8c}\x9a\b\x86\xf9\xd8tq\xe0\xda`\"%MG\xe45Ʉ\xee4\x96$=b \xed\x14[\x11=\x14\xb9s6]^\x8c\b\xe7\r\x97\xbe\xacw\x9a.\x82\\p\x99\xecI\xd8\xe4\xc0\xa7&\x87\x19VΈ\x024S/\xdb\xef\x19F.\x1b\x1dl1\xa3pD\f\xf4I\xc5\xf1\xcc=n\x14\xc7\x1cl\xda\n\xaefA[)\xe3#\u007f\xd4I9?\x85>%\x9f\x05L+~\x06W<\x91\x81\xc1-\x1a\x94%&\xc7u*\x9d\xc9 \x1b&\x1as\x8cǕ\x02Nx\xf5,ⷷ\xd7ɓ'&F\xecn~\xee\x19\xfeз\x15\xd8p\x1f\xe8Ο}q\xbd\r\x87y\x9f\xe6\x140\xd0\x02Cb\xda\a\t\x10\xd2:d\x1c\xd46K\x91\xca' \xc37\x18w\xbc\x0e\x1e,\xba\xcaCh!\xde\x03#\xdf)8\xfc\xeb\xee\xdd\xcd\xea\x9f9\xd6\xf7\xb7\x00V\x96h}^\xee\xb0E\xe9^\xf7\xa5\x02G+\frJ\xfc\xb1h\x99\x14[\xb4\xae\x88g\xa0\xb1\x9f\xde|\xces\x0f\xe0Ge\x00\xbf\xb0V7\xf8\x1aD\xe0x\uf593\xd2\b\x1b\xd8\xd1S\x84G\xe1j1\r\xa6=\aH\xbd\xe2\xb5\x1f\xfdu\x1d{@P\xf1\xba\x1dB#\x1ep\r\xaf|Zs\x80\xf9+\xd9\xceo\xaf\x8eP\xfdS0\xedW\xb4\xe8U\x00\xd7\xc7\xe1\xa1\xd1\x1d@\x06\xcb3\xa2\xaa\xf0\x90UM?\x1fT\xc8U\u007f\v\xca\x10\a\xa4\x1a\x90\xf0\x84Iz\xc1Q\"\x9f\x81\xfe\xf4\xe6\xf3Q\xc4c~\x81\x90\x1c\xbf\xc0\x1b\x10\xb1\xd8Ҋ\u007f[\xc0\a\xaf\x1d{\xe9\xd8\x17:\xa9\xac\x95\xc5c\x9cU\xb2ه\xf6\x881\tʄyp\xcdL\ueffa*\x13C;C\x88\xf6\xcb\xd8\xf6[2\xc9\xe9\xffVXG\xe3/\xe2`'\x9ed\xbe\x1f\xaf\xaf~\x1f\x05\xefċl\xf5H\x02\x1etd\xd8\xe58\x93\x98\xbd\x1f-N\xa9c&c\xed\xd7<+3t\xacʤb\xc3\xf6䩄\xed$\aƭ\x18VY`\x06\x81A\xcb4I\xee\x01\xf7\xcb\x10\xe25\x13\x14\x9f)\x04\xf7}\x0e`Z7\"\x1b\x8ac \x8fIh\xe4\x04\x15ڬ\xb2\xc7\ue795ð\xafsF\n\x1f\aK\x93\f\xcet\x96\\\x9d\xb3\xd4Q\xbfi\x8e\x16e\xd7Ρ,\xe1Ai\xc12\xe3\x06\xad\x13ef\xe2\xd5<\xd38!\xac\xc0\xcb3<\x88-\xe8L\xf1\x12E\x112\xbd\xbe\x80\xf1]\xc7\\\x85p\xbc<8\n\x91*t\xca[\xc7\x10\x97\xf9Rr\xb2\x86J\xabɐV|1ed\xa6\xf3\x98&G\x9d\xd1!\xd2y}\xed\x1b\xdeϨ\xb0C#?\xf24\xf8S\x97\xda\xfbTL\xbc\xb4\xc6.\x15\xe5\xe9㧕\xd3⽜\xef\xf0\xed,ã\xba\x8b\x96\xacw\xd0\xf6\x8fg\xe4\x8ad\x18\x90\v;}\x04#j\xc8}\x12M9\xfe\x96\x89\x069\xa4\xb7\x9d\xe9\x9e\f\xd5!\x95\rn\xc9\xdd\a\xd3K\xa5i\x84\xd7'\xaa5\x82\xf5}\xa2\v{\x82fg\x91\xfb\x9eF\x86\t\xf3\xe4u\xabL\xcb\\\xe8k.\xb3De\xd74l\xd3\xe0\x1a\x9c\xe9\xe6\xd3',\xb1EkYu\xce\x14\u007f\x0e\xabB\xc5\x1e\xb7\x00ۨ\xce\xf5%\xfb\xc8=^بS\xcf\xeb\x1ad\x8b\xe1\xb1:3*VlLڛ\xc6\xef\x19:\x82Ã\xa0G\xb5\xc1|\xd0\u007f\x89O\x00\xf0\x0fZ\xe7\x10Қ\x9c\x81\xf5\xde뤅\xc1\t\xa7|\x83\x8f\x99\xd1\xd9C\xdcp\xf22\x99Lf\xeeGo\rϺ\u007f<\xe8\x1c\v\xe22\xa8U\x93\x8cY9ր\xec\xda\r\x1a\xe2\xc3f\xefЎ\xddy\xae?\xe3\xeb\xba\x03\x1b\a\xfb\x93\xfc\x02\xa5X\xaa\x96L\xfa>*Y\x97S\xc0\x85\xd5\r\xdbg\b\xa7\x8b\xf8܍\x8c\x8b\\\xc0A\x9f\x93Qk4~\xea\xb9}%\x8f\xe9J\xc9#\x95F\xb2g!\xdd_\xffr\"\xd3\x13\xd2a5\t\x0eq\x9e\xd8\xf9\x03\x9d\xf2uN8\x91\xc4Xɴ\xad\x95\xbb\xbe:\xa3\x05w\xfd\xc2d\r\xb3\xe79\xec\xa9EUȉ\xaa\xf7-\xcf2\xd5\xf1\x13\xf09\xa8\xa3\xc5g\xa2P||\xceŠ;\xd4̐\xa5\xfb7\x81\xcb\xe9\xa3\xd5k\xb0\xc27:)\xf3\f\xa9hhCX\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xbfg\xfc\xc8\xea\xc9l\xd0#\xe7\x03ڱY>\x1c\xe96\xfdC\xd0\x1a~\xfdmqHlXI\xc5\x13\xf2\x9b\xe9\x1fYĔ3\xfdՄ\xffY*\x19*\t\xbb\x86O\x9f\x17\xe9\xd9\xf2>\xfd1\x04\r\xfe/\x00\x00\xff\xff\xb0\xddǼ\x99\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b\xb9\x11\xbe\xf3Wti\x0f\xcaV\x99\xc3]'\x95\xa4x\xb3\xa5lJɮ\xac2e]\\>\x80\x83\xe6\f\x963\x00\x02`H3[\xfb\xdfS\x8d\a9/\x92\xa2*Z\xcf\xc5\x16\xd0h|\xf8\xd0/4'\xd3\xe9t´xBc\x85\x92s`Z\xe0W\x87\x92\xfe\xb2\xd9\xfa\xef6\x13j\xb6\xf9q\xb2\x16\x92\xcfᦱN\xd5\x1fѪ\xc6\xe4x\x8b+!\x85\x13JNjt\x8c3\xc7\xe6\x13\x00&\xa5r\x8c\x86-\xfd\t\x90+錪*4\xd3\x02e\xb6n\x96\xb8lD\xc5\xd1x\xe5i\xeb\xcd\x0f\xd9߲\x1f&\x00\xb9A\xbf\xfcQ\xd4h\x1d\xab\xf5\x1cdSU\x13\x00\xc9j\x9c\x83V|\xa3\xaa\xa6F\x83\xd6)\x836\xdb`\x85FeBM\xacƜv-\x8cj\xf4\x1c\x0e\x13aqD\x14N\xf3\xa0\xf8\x93\xd7\xf31\xe8\xf1S\x95\xb0\xeeߣ\xd3?\v뼈\xae\x1aê\x11\x1c~\xd6\nY4\x153\xc3\xf9\t\x80͕\xc69\xdc\x13\x14\xcdr\xe4\x13\x80H\x80\x876\x05ƹ\xa7\x94U\x0fFH\x87\xe6\x86T$*\xa7\xc0\xd1\xe6Fh\xe7)\xdb\xeb\x01\xb5\x02W\"m\xe9\xe9fB\nY\xf8\xa1\x00\x01\x9c\x82%BD½2\x80_\xad\x92\x0f̕sȈ\xb8L+\x9eɤ3\xca\x04\xce\xef{\xa3nG\xe7\xb0\xce\bY\x1cC\xf6\u007f\x06\xd5\xc1\xf3\xa0\xf83\x91<\x96\xe8e\x12\x9aFW\x8aq4\xb4y\xc9$\xaf\x10\xc8r\xc1\x19&\xed\n\xcd\x11\x14i\xd9\xe3Nw\x91|J\xfaZ3\x97\xb0s\t\x15A\xb6\xb3\xfdS{\xe8ܾ\x0f\x8a\xc7\x05\x10\x8d\x1a\xacc\xae\xb1`\x9b\xbc\x04f\xe1\x1e\xb7\xb3;\xf9`Ta\xd0\xda\x11\x18^<\xd3%\xb3]\x1c\v?\xf1\xba8V\xca\xd4\xcc\xcdAH\xf7\u05ff\x1c\xc7\x16\x17eN9V\xbd\xdf9\xb4\x1d\xa4\x8f\xfdဖ\x9c\xad\x88\xd7\xffM\xe0.\tҭ\x92]^\xdf\xf7F\xc7\xc0\xb6\x94\xa6@\x9c\r\x82hG뻢\xab\x8f3\x17\x06\xc2\xf4\xe6\xc7\x10\xca\xf2\x12k6\x8f\x92J\xa3|\xf7p\xf7\xf4\xe7Eg\x18@\x1b\xa5\xd18\x91\xa2k\xf8ZY\xa55\n]f\xafIa\x90\x02N\xe9\x04mp\x8a0\x86\xa3f5\xff\xce\xc4\xfck\xaf;X\aN\x17>\x9f\xebN\xdc\x00%;\x10\x16X\\\x1aNq :\x85\xec\x8f\xffX\xdc\xcf\xfe9\xc6\xfc\xfe\x14\xc0\xf2\x1c\xad\xf5\xf9\x1ak\x94\xee\xcd>gs\xb4\xc2 \xa7\xc2\x05\xb3\x9aI\xb1B벸\a\x1a\xfb\xf9\xed\x97q\xf6\x00~R\x06\xf0+\xabu\x85o@\x04\xc6\xf7\xe1/ٌ\xb0\x81\x8e\xbdF\xd8\nW\x8a~\xd2\xda3@\xd6\x15\x8f\xbd\xf5\xc7ul\x8d\xa0\xe2q\x1b\x84J\xacq\x0eW\xbe\x12<\xc0\xfc\x8d\x1c\xeb\xf7\xab#Z\xff\x14\x1c芄\xae\x02\xb8}\xbek{\xe4\x01\xa4+\x99\x03gDQ\xe0\xa1\x10\xed\u007f>xSH\xfc\x1e\x94!\x06\xa4j\xa9\xf0\x8a\xe9\xf6B\x00\xfd\xf9헣\x88\xbb|\x81\x90\x1c\xbf\xc2[\x102p\xa3\x15\xff>\x83Go\x1d;\xe9\xd8W\xda)/\x95\xc5c\xcc*Y\xedB\xb5\xbfA\xb0\xaaF\xd8bUMC\xbd\xc1a\xcbv\xc4B\xba8\xb27\x06\x9a\x19w\xd2ZS\x95\xf1\xf8\xe1\xf6\xc3< #\x83*|\xbc\xa3\xec\xb4\x12T5P\xb9\x10r\x9e\xb7\xc6A\xd2L\x9fm\x82\xf98\x05y\xc9d\x81\xe1\xbc\b\xab\x86\xb2Pv\xfd\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe83\x0f\xe7+\xd5g\x1c\xae\xfd\xd6:y\xb8u\xb3D#ѡ?\x1fW\xb9\xa5\xa3娝\x9d\xa9\r\x9a\x8d\xc0\xedl\xab\xccZ\xc8bJ\xa69\r6`g\xfe\xc9<\xfb\xce\xff\xf3\xe2\xb3\xf8\xd7\xf5s\x0f\xd4y\xf4\xbf\xe6\xa9h\x1f;{ѡR\xad\xf8\xfc2q5\xacTN0\x11\f\xe0\f\a\xb1\x994\xf22\x8a\xf6\x13*E?B\xaf\x11oE\xe3!\xf6R\xbb\xa2\x874\x95\xbd]\x84\xd3\xf1\xf7^OF+>\xe9\x93\xd6v\xc9\xde\xe4\xc1\xa1\xfa\x13][\xed\xcdv\x9a\x9c\xed\xd3\f\x9fʾ\x83v\xc9c9t\xed\"\xef!f\xbb\xd4ˣ\aˋ\x9f˹\xa2\xc7@\xf7W\x8b\xd36p3\\\xe1{S\x86G\x9f\x105\xfa7hh8n\x99M\x9b\x8c\xdd7\xb4\xf4\x85\xa5>O\x92:\xe4\xbeT\xa7\x97Ċ\x89\n9\xec\u007f7\xf1\xcdq\xeb\x9b4\xd7c\x95iR\xd4X\xe4>n\x8c\x80\x1e\xaeK}O\xce\x1cNI\xc5@B6UŖ\x15\xce\xc1\x99f8}½j\xb4\x96\x15\xe7\xfc\xeb\x97 \x15^\xf1q\t\xb0\xa5j\xdc\xfe\x19\x1f\x1d-Rqm\xa3\x15\\\xd6J(\x99=\a\xe5\x81d\xc6,n\xef\xf2\xa7M\x0eN\x84\xb2{\u070e\x8c\x0e\xfa\xd0\xedɛdB#s?y븈\x80\xb8\xd19\x0e\xa2\x18\x94\xaaJ֭\x1c%\xa5\xa6^\xa2!\"|\xf3;1\x92\x02\xc7X_Ŀ\xa7\x0eL\x1e4\xa4X\x18T\xc5\x17bΤo\x13\x92\xfd:\x05\\X]\xb1݈\xdet\x12_2\x91\xf9\x92\x1f\x1d,&y!\xb9\xbf\x9f\xbb\xb4\x9f\xb3o\xee\x8f\x17tc?\x15\x8c\xddB\xbb\xefߛ\xdf\xff\xaa\xf1:;\x9c(\xe2\xacc\xc6=7\xec-:\xc2\xe7\"\x9eW=\x1e\xefڡk\x18\xa8\xba\xdb\xfc\x911j\x94\xa8\xc1\xa0G\xce[\xbac/\xb4=\xd2,\xf7\x9d\xfe9\xfc\xf6\xfb\xe4\x90\xeeXNU;\xf2\xfb\xfeOڱVI\xbfP\xfb?s%\xc3O\xcav\x0e\x9f\xbfL 6M\x9f\xd2\xcf\xce4\xf8\xbf\x00\x00\x00\xff\xffe\xe5\xd5&\b \x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc\x99\x15\xae\fe%J\a<.\xbd\xfbi\xf9o˟.\x00\n\x89n\xfagZ\xa1Ҥ\xaao\x80\x1b\xc6.\x008\xa9\xf0\x06$*-$\xaa\xe5\x0e\x19J\xb1\xa4\xe2B\xd5X\xd8\xc56R\x98\xfa\x06\xda?\xfc\x9c\xb0\x11\x8fă\x9f\xee\xde0\xaa\xf4\xafݷ\xbfQ\xa5\xdd?53\x92\xb0v1\xf7RQ\xbe1\x8c\xc8\xe6\xf5\x05\x80*D\x8d7\xf0\xd1.S\x93\x02\xcb\v\x80\x80\x93[v\x11v\xbd{\xebA\x14[\xac\x88\xdf\x0f\x80\xa8\x91\xbf\xbb\xbf\xfb\U000af3fd\xd7\x00%\xaaB\xd2Z;ʄ\xbd\x01U@\xe0\x8b\xc3\xcdn\xc01\x01\xf4\x96h\x90XKTȵ\x02\xbdE u\xcdh\xe1\x88\xd8@\x04\x10\xebf\x96\x82\xb5\x14U\vmE\x8a'S\x83\x16@@\x13\xb9A\r\xbf\x9a\x15J\x8e\x1a\x15\x14\xcc(\x8dr\xd9\xc0\xaa\xa5\xa8Qj\x1a\t럎\x1cu\xde\x1e\xe0\xf2Ƣ\xebGAi\x05\b\xfd\x96\x03ɰ\f\x14\xb2\xbb\xd5[\xaaZ\xd4\x0e\xd1\t(\x11\x0eb\xf5'\x16z\t\x8f(-\x18P[aXi\xe5n\x87\xd2\x12\xa7\x10\x1bN\xff\xbb\x81\xad,\xa2vQF4\x06~\xb7\x0f\xe5\x1a%'\fv\x84\x19\xbc\x02\xc2K\xa8\xc8\x1e$\xdaU\xc0\xf0\x0e<7D-\xe1w\xc7\x1e\xbe\x167\xb0պV7\xd7\xd7\x1b\xaa\xe3\xf9)DU\x19N\xf5\xfe\xda\x1d\x05\xba2ZHu]\xe2\x0eٵ\xa2\x9b\x05\x91Ŗj,\xb4\x91xMj\xbap[\xe7\xee\f-\xab\xf2_\x1a\xb6\xbd\xe9\xedU\xef\xad\xe4)-)\xdft\xfepb>\xc1\x01+\xf0^\x96\xfcT\x8fEKh\xfb\xcaR\xe7\xe1\xfd\xe3箜QuH}G\xf7\x8e\xf0\xb5,\xb0\x04\xa3|\x8d\xd23\xd1I\x9b\x85\x89\xbc\xac\x05\xe5\xda\xfd(\x18E~H~eV\x15Ֆ\xef\u007f\x19TV\xa0\xc5\x12n\x9dR\x81\x15\x82\xa9K\xa2\xb1\\\xc2\x1d\x87[R!\xbb%\n\xcf\xce\x00Ki\xb5\xb0\x84\xcdcAW\x1f\x1e\x0e\xf6T\xeb\xfc\x11\x95\xd7\b\xbf\xc2\xe9\u007f\xac\xb1\xe8\x9d\x18;\x8d\xae\xc31\x87\xb5\x90=\xe5`\xa7,{@Ӈ\xd6>\xfe\xf4[\rv\xf8\xcf\xc1V\xfe\xa3\x19h\xe5\xc7n\xc2p\xfa\x97A\xa7\xe2\xfc\x89ŁJ\x19\x80\x84\xb8?'\x16\xcb\xc1\xff#4\xb5\x0f~-\x98)\xb1l\xb4\xed\x00\x97\x83\x1d\xbf\x1fLp\xe6\x88Pn\xe5ߪ\u007f\xbbm\xde\xfek\xd5ib\xc7D\"X\t\xa4\xdc\xc3\x03\xca\x1d\xb2IJۇj\xac\x12\x9b\x9b\xc4\x0e\x9c\x9d#+\x867\xa0\xa5\xc1\x11\xca\x10)\xc9~\x840\xd16\xe7ҥ\x19\x1f\x14\x02\xa3\x05v\r\x85'\x8d72D\x0ew\x04\xdf9U\xa8\xb2\xea,by/\x18-\xf6\xb3\xa4IM\x8a\xc7-\x1c\xbe(\xc1+ܒ\x1d\x15F&p\xb2GҎ}j-i\xabM\x85Uf\x01Jy\x1a\xc6Ijm\x85x\x9ac\xfe/vL\xab\xb6\xa1pn]\xc4E\x06v\a+\xbaB\xc0\xafX\x18\x9d\xd8&@i\x9c\x05\x11\x12j\xa1\xf48\xe3Ǖ\x0fx}0&\xb50%5\x03̂\xae\x8c\xac\xb3\x88\xf6\xf4\xa6\xe0h\xf7ZYֵc\xa50~졁\xebPi\x9f\f}\xdb>3\x9aw\xa0Y\xc2\xfb\xa4\xe9l\x9f\xff\x9f\x84\x8d\xa6\xe4\x04\xa1\xbd\x1bL}]\xa1uQ\x95\xf5\xf6\xefրU\xad\xf7W@u|;\a\x910\xd6Y\xff\x1f̘\xe3%\xfe\xeep\xe6\xabJ\xfc$W\xe6 Z\xae4\xcb\xff\x03\x99\xe2\x8c\xc5c\xb0\x15\xd9\f\xf9\xad;\xeb\n\xe8\xbaaHy\x05k\xca4\xca\x03μ輼\x061r\xec\x9d}*\xa2\x8b\xed\xfb\xafֳQm\x06*\x93.\x87\x93\xbdO\x1c\x83\x84\xbea\x9e\x81\v.~\xa5\x12+\x1f\x17\u007fv\xd4l߸\x80\xe2\xddǟ\xb1\x9c\"\x0f\xe4I\xde\x00\x91w\a\x9b\xed.\x1d\x1c\xfd\\4\x82\xeb\xd3\x04M>\xe3q\x05\x04\x9ep\xef=\x16\xc2\xc12\x87h\xe7\xef&ç!q\\\xeaŻǸw`B.evv\xae(\xf8\xe7\t\x13\xfe~\xea\xe9\x11\xd0\xee)D\xb8\x9e\x92\xf6\x85#\x84\x8b\xbc\xf3\x89\a./\x16u\xd1{\xc8a5J\xd5zrk\x8d2$\U0007c88d\x11\xc0\vc\xa3\xb9$]\x93\xa0#Mf\xd5\x12xF*|\xcd#g\x8b\xc7x\x8d\x96.G\xfa\xdb\xef\xbfvr\x8c\xf6\x84\xda\xdf]D^۫-DU\x91\xc3*_\xd6Vo\xfd\xcc(\xd3\x01\x90\xe7\xbe\xdc\x18w.\xf3ݽ(C\xae\xbe\xf7L\xf5\x96r \xf1\xf8\xa3\f\x02E\xa0\x16\xf3\x9a\xc8?[\xa2`\x85ț\xdc\xf8\xf7`\xaf+\xca\xef\xdc\x02\xf0\xf6\xd5\xed;\xb4\xe4:\x89\x9d\x91\xd4\rC\x9b\x17\xce\xe2\xe4\xbaF\xa2\x84\xe7-J\xecI\xc50\xe1m=\xc6L\x90\\\xe8n^\xc1\u00adE\xf9F\xc1\x9aJ\xa5\xbb\x1b\xcd\x158\xa3r\xc5\xe1H\x0e[\xec>\xd3\n\x85\xd1'\xf0\xe0};\xbbW\xa0\xad\xc8WZ\x99\nH%L\x86q\xf7\x8f\xb5/\xb4j\xaa\xa8\x81\x03τꦞ\xe42,ZX.\xd5\fu.\x8bW\xb8\xb6\xea\xa8\x10\\\xd1\x12e\xac\xf2{\xceRa\x0f\xee\x9aPfR\xe5\x9b\xd4sl\x98\xca\xdfKyR\x94\xfa\xc9\xcf\xecd\r\xb7\xe2\xb9O\xa0l\x12l\xc9\x0e\x81\xae\x81j@^X\xbe\xa0\xf4*\xdb-\x11\x88\xe1H\x93-\x96y\n\xde>\xc8M\x95G\x80\x85;ٔO&ź\xc3?\x10\xca\xce\xc16+y\xa7\x1f\x8d?\xda\xd9\xdf\xe4h4J%߄\xad\x10\x1e\x90\x94\xfbx>\x88\xd66Tu2 @\x1a\xdeՈg8\x19\xc7\xc4wa\x17\xaf\x19\xb8QN3\x18{\x90ϧ\xba\xeb\xedX\x10g\xf5v\xec\x02\x8d\xa1;%5s\xd7\x03`Met\x9c\xdd\xde\x1b\xa99\xc2\xf3Y\xa1\rP\xb1\xf4I/k>\x83\x1f\xed{\x97F\xca\xe0I\xec\x8ew]\xb28\u06dd\x90\x9f%\xfa\xbah\xdb\x15\x16.)(w\xb80\xfc\x89\x8bg\xbep1\xa5\x9a\xcd\xd67\x8b\x9f\xac8\xbe\xa5\xd2\xe8\x8bW\xbe\bD\xfb{\x06\xa5\x90\xcd\xe6\xa3\x02\xe3))\x98SC\xbe\x8du\xe4\xcf\xd9]L\xad?19\xd4\x1co}\xffino\xd3]zV\xc7\u007fxޢޢ\x8c\x8d\xad\v\xd7ÛR\xabmi\xb2u\x85\x9bf'+?ћ\xf2Mx\a\xedOi_\x99\x1bƮ\xac`\x13ôoE\x95&!DY=@+!\x18\x92ö\u061c\"\xfa\\\xe9\xbc\xdf\x0f֔\xaecC\x98\x88\x8b$0\xf4\xbc\xf4]\x9fݺl\xbf\x06\xee\xb2?q\xa7\u007f{\xabXFy{\xa6\xa8=\xdd@7E\xaf\xa1\xd8t)\xd6\xca`\x18\x17:+\xbf+\xf2\xcd\x14\xa2\xc7\xcb\xcf!ي\x9a\xec\xde.\xfb\xffh\x11\x8a\xd1.\xb3\x90@\xe5y\xdb\xe4\t\x9c\xe5\xe5%\xdd\xd1\xd2\x10֓\xc0\x0e\xcdZ҂\x90\xc0)Kա,\xcd\xe3\xfc\x1e\x8d\xe1S\xed\xf3\xd1G\x9f\xd5iw'\xaff}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xf3[\xf2\xf2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x1cOu\xa6\xc6|Be9\xb3\xab\xe8\xc5\t\xe8\x9c\xda\xf1I\x15\xe3\xd9ƛ\xcc:q\xbf\x02<\r\xf2\x88\xeap\x16q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xec\xaao\xa2\x9e;\tx\xb4\xd6;Uŝ&y\xa2\u009b_\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4Bg\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa7J:R\x05\x1d\x818Y\x1bͭ}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xffN\xea\x9a\xf2͐\xf3\xb9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x91Z\xd2\u007f\x90\x98\bAb҉r-\x96\xf0\x8e\xef\ap]3t\xd2\xe5\xee\u007f\xb1b\xb7\xf5L\x19\xeb~\x95\xe1\xc0vA\x85\x0f\x9cT:\x10\xb6\x03\xc7>fJ2EȞ\xbf;\x17q|:\x18\xdeMcM\xfb\xcf)י\xea\xed\x89\xfese\x98\xa6u\xf2\x10\xd7R\xec\xa8K\x8amq\xdf\xd0\xf3OᾇX\xed\x1d\xa4O\x0f\xcd\xf9Z\x1e\x84\x02$u*\x9e\x911 j\x88~\xe1\xbf\t,\xc4\xc2}\xe6c9\x19\xe5!|;x\xe5\xce`*@\xe5\xf1k\xb5ʂq\xdf\x15\xaaD\x02`ԺL{\xb8\xde\x19w\xef\xfe2(\xf7 v\xae\n\x1a\\\x9e\x99\x86c\xaf)\x94am\x87GP\x80\xfeK\xd4\x03Ͽ\xd5\x18\xf0\x8e{\x1b\x9c\x04{\xb0G\a\xc7*\xad6ڱ\xfa\xd9\x062#C\x93P\xb9hf\xa7\xe5a\xd2\xd4\xe4v\xeb\x9e7\xf69>\xfa\x99\xf5;\xce\x12\x01\x9d\x1e\x03M\x80\xcc\xed\xbe\xcd\xcb\xd8\xcfv۞+\x16\x9a\x8b\x86\xb2\xdd\xc0\xbcn\xdast\xd1\x1e\xd1={DTt\\\\\x94M\xa6\x9c.ٳDGg\x8c\x8f\xce\x11!\x9d\x16#̀<\xe8~\xcd\xe9k\xcd*2e\x97(r\x8aJ\xf3u\xcd\xe9~Ռ>Ռ\xe2\xc7\xdcN3\xfaQ\x8f\xebC͠ᙢ\xa73\xc5O爠\xce\x1bC\xcdFQ\xb3\x923\xf9\xf7\xc99\xf2XM\xfd(J\xbc\x17R\xcf9\xfc\xf7\x87\xe3\x13\x15\xacN\x10$X\t<\x0eM \xe5|\xf9\xe0ǟ\x86T\xba\xd8\x14ֿ\xff2\x87\xcfC3p\x1a\x11\xeb\x92\xc6\xf8,\x81\x87\x9d\xefpQ\x9c\xd4j+4\xfc\xb0\xa3$\\(\"L\x19\x82\x10\xf9\xe39\xb0|\xd4D\x9bLD\xfd\xd8\x1e\xae\xb4\xd8v\xea9\xcf\x18ˊ\xb2s'\xd1\xc1\x9e\xac'\xe4\x01\xb9\x83\xe6\x8c$\xa7\xec\xaa\xe3z\u007f\x9b2E\xe6'\xcf'\u007f\xec\xec\xc93r`],iý($-]N(P\xcc*\xe9\x8c\xef\x12\xa7-K\xe6ײ'\u007f';O\xac\x04\xa1\xc6>\x91\xcd\xf9\f\xf6o\xa5\xe7\x84>V\xc5\x16K\xc30\xe3\xf6\x9a\xc7\xce\xd0\xf9\xfbk\"\xe0ԙ\x14\xfd\x1bl,];\x9d\xae֤\xf5o\xca\tD\x0f\x90-{\x12P\xbb \xbd\xed\xf67j\x14\xd6R+S\x14\xa8\xd4ڰX\xfeu\xb7\xa4a\x19\x87'\xbb\x15#\x0eGܱ\x93v\xdb\x16\x9d;\x82.28\xa3\x12jrBE\x16\xa4\xd6F\x061/\x8c\x94\x0ee\xff\x9fX\x0f\xee5\xea\x81\x1dWZ\xa1?\xa7w\x93ܴ\x84\xdc\x0eg\xb8\xdb\xc3d\xe9\xb7\xe6:\x89\xba\u05ffx?gx/\x19\xb8\xceIմ\b\x95\xcb\x0el\x0f\xc6\xf9\xfd\x164\x96\x80;\xe4 \xb8\xeb\xbf\xc5r\xaa}\xec\xb3\v\x8c\xe5\x0e\xe5\x1b\xd5\xc01*t\xa3\x12\u007f\xfeA\xa8\xa6|\xf3A\xc8{f6\x94\u007f\x8ag\xf2\xb8\xc1\xf7DjJ\x18\xdb\xfb\xfd$\xe6\xde\xc6Ü\xf8o~\xf6\xc8\x1fSL\n8φ\t~X\x1b5Q\xee\x0f\xbak\xea_\t\xa3\xbb\xa7\xe2\x8dj\x0fL:e\xeb\xa0-\xe1\xa3\xd0\x18\xb3q\xb4\x0f\x94*X\xa1\xd2\v\\\xaf\x85\xd4>J[,\x80\xae\x83\xa2NE\x1f\x842\xe7k\xf8\v\xfc\xac\x03\xd2T\x84\x1b\xcb'$\x10\xbe\a\xe9N\x85sR*\xb2\xf7=w\xa4(\x8c\xd5\x03\xd7J\x93\x94A{\x91k뜛 \xcd#\t\x8b\xbe\xa7\xd6\x1d\xdf|\x1eh\xaa\x15J\xd7Tl\xff\xf6\xa4s\xdf\x1bx\x15\x94\xacD\x80\xfb*\xa1\xf3\xb9\x13({\x9cӁ\xf3\x94\xf2q\xff\vM\xd8ݸ\xa3\xd6\xef\xb0m\x06G\x04\xdc\xf4!\x1a\xbd\x9b\xca\xc6\xfb\x87\xa8\x8aS-ϊ-\xe1\x1b+>R\x98\xcd6\x8a\xe0\x98\xa6\x1eK\x94\x18wQZ\xedN\xaa\x8aYmm$\xef$eB\x9e\xbbl\xb7;\x05t\x9a\x84S~fϴ\xcey\x9a\xbd\xc1/s!\xdc\xc2ց\xf8~M\xff\xae\xd1\xdd\xefs\x9c\x80/\a\xc3\x0f:'\xad;\xd0B\f\x86;A\x9c\x1f\xe8:^t\xbbb\xf8\xe3`\xc47\xee\x80|&\x92S\xbe\x99C\xfe\x8f0,\xe1\x03\x05\b\t/(\x81D\xe3\x17\x1d\xe5\x05\xc5M\x8e\xdc\xe5\xd8xF/\xf0\x83\x92gh\xf0\xd2\tr\xd9!rX)\xbci\xe3\aR\x14X\xebЙܽ\xc6\xf9\xf2\xd2\xfd\x88\xf74\xbb\x9f\x85\xe0^-\xa8\x1b\xf8\xcf\xff\xba\x88\b}\x89\xd71ۗ\xff\x17\x00\x00\xff\xffF\xa1\x18'\xf3Z\x00\x00"), diff --git a/pkg/apis/velero/v1/constants.go b/pkg/apis/velero/v1/constants.go index 4d06be19f5..a7292d5684 100644 --- a/pkg/apis/velero/v1/constants.go +++ b/pkg/apis/velero/v1/constants.go @@ -46,8 +46,4 @@ const ( // APIGroupVersionsFeatureFlag is the feature flag string that defines whether or not to handle multiple API Group Versions APIGroupVersionsFeatureFlag = "EnableAPIGroupVersions" - - // UploadProgressFeatureFlag is the feature flag string that defines whether or not upload progress monitoring is enabled - // and whether or not ItemSnapshotters should be invoked - UploadProgressFeatureFlag = "EnableUploadProgress" ) diff --git a/pkg/apis/velero/v1/download_request_types.go b/pkg/apis/velero/v1/download_request_types.go index 964ba2f3df..c55f957ddc 100644 --- a/pkg/apis/velero/v1/download_request_types.go +++ b/pkg/apis/velero/v1/download_request_types.go @@ -25,17 +25,18 @@ type DownloadRequestSpec struct { } // DownloadTargetKind represents what type of file to download. -// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemSnapshots;BackupResourceList;RestoreLog;RestoreResults;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents +// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;RestoreLog;RestoreResults;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents type DownloadTargetKind string const ( DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog" DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents" DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots" - DownloadTargetKindBackupItemSnapshots DownloadTargetKind = "BackupItemSnapshots" + DownloadTargetKindBackupItemOperations DownloadTargetKind = "BackupItemOperations" DownloadTargetKindBackupResourceList DownloadTargetKind = "BackupResourceList" DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog" DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults" + DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations" DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots" DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents" ) diff --git a/pkg/itemoperation/backup_operation.go b/pkg/itemoperation/backup_operation.go new file mode 100644 index 0000000000..94979ceeb9 --- /dev/null +++ b/pkg/itemoperation/backup_operation.go @@ -0,0 +1,44 @@ +/* +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 itemoperation + +// BackupOperation stores information about an async item operation +// started by a BackupItemAction plugin (v2 or later) +type BackupOperation struct { + Spec BackupOperationSpec `json:"spec"` + + Status OperationStatus `json:"status"` +} + +type BackupOperationSpec struct { + // BackupName is the name of the Velero backup this item operation + // is associated with. + BackupName string `json:"backupName"` + + // BackupUID is the UID of the Velero backup this item operation + // is associated with. + BackupUID string `json:"backupUID"` + + // BackupItemAction is the name of the BackupItemAction plugin that started the operation + BackupItemAction string `json:"backupItemAction"` + + // Kubernetes resource identifier for the item + ResourceIdentifier string "json:resourceIdentifier" + + // OperationID returned by the BIA plugin + OperationID string "json:operationID" +} diff --git a/pkg/itemoperation/restore_operation.go b/pkg/itemoperation/restore_operation.go new file mode 100644 index 0000000000..e3fe4d1f00 --- /dev/null +++ b/pkg/itemoperation/restore_operation.go @@ -0,0 +1,44 @@ +/* +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 itemoperation + +// RestoreOperation stores information about an async item operation +// started by a RestoreItemAction plugin (v2 or later) +type RestoreOperation struct { + Spec RestoreOperationSpec `json:"spec"` + + Status OperationStatus `json:"status"` +} + +type RestoreOperationSpec struct { + // RestoreName is the name of the Velero restore this item operation + // is associated with. + RestoreName string `json:"restoreName"` + + // RestoreUID is the UID of the Velero restore this item operation + // is associated with. + RestoreUID string `json:"restoreUID"` + + // RestoreItemAction is the name of the RestoreItemAction plugin that started the operation + RestoreItemAction string `json:"restoreItemAction"` + + // Kubernetes resource identifier for the item + ResourceIdentifier string "json:resourceIdentifier" + + // OperationID returned by the RIA plugin + OperationID string "json:operationID" +} diff --git a/pkg/itemoperation/shared.go b/pkg/itemoperation/shared.go new file mode 100644 index 0000000000..b9039b3ba2 --- /dev/null +++ b/pkg/itemoperation/shared.go @@ -0,0 +1,67 @@ +/* +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 itemoperation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OperationPhase is the lifecycle phase of a Velero item operation +type OperationPhase string + +type OperationStatus struct { + // Phase is the current state of the item operation. + Phase OperationPhase `json:"phase,omitempty"` + + // Error displays the reason for a failed operation + Error string `json:"error,omitempty"` + + // Amount of operation completed (measured in OperationUnits) + // i.e. number of bytes transferred for a volume + NCompleted int64 `json:"nCompleted,omitempty"` + + // Total Amount of operation (measured in OperationUnits) + // i.e. volume size in bytes + NTotal int64 `json:"nTotal,omitempty"` + + // Units that NCompleted,NTotal are measured in + // i.e. "bytes" + OperationUnits int64 `json:"nTotal,omitempty"` + + // Started records the time the item operation was started, if known + // +optional + // +nullable + Started *metav1.Time `json:"start,omitempty"` + + // Updated records the time the item operation was updated, if known. + // +optional + // +nullable + Updated *metav1.Time `json:"updated,omitempty"` +} + +const ( + // OperationPhaseNew means the item operation has been created and started + // by the plugin + OperationPhaseInProgress OperationPhase = "New" + + // OperationPhaseCompleted means the item operation was successfully completed + // and can be used for restore. + OperationPhaseCompleted OperationPhase = "Completed" + + // OperationPhaseFailed means the item operation ended with an error. + OperationPhaseFailed OperationPhase = "Failed" +) diff --git a/pkg/persistence/mocks/backup_store.go b/pkg/persistence/mocks/backup_store.go index 449e86fa7f..2caae0c83b 100644 --- a/pkg/persistence/mocks/backup_store.go +++ b/pkg/persistence/mocks/backup_store.go @@ -24,6 +24,7 @@ import ( snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/itemoperation" persistence "github.com/vmware-tanzu/velero/pkg/persistence" volume "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -274,6 +275,34 @@ func (_m *BackupStore) PutRestoreResults(backup string, restore string, results return r0 } +// PutRestoreItemOperations provides a mock function with given fields: backup, restore, restoreItemOperations +func (_m *BackupStore) PutRestoreItemOperations(backup string, restore string, restoreItemOperations io.Reader) error { + ret := _m.Called(backup, restore, restoreItemOperations) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, io.Reader) error); ok { + r0 = rf(backup, restore, restoreItemOperations) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// PutBackupItemOperations provides a mock function with given fields: backup, backupItemOperations +func (_m *BackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error { + ret := _m.Called(backup, backupItemOperations) + + var r0 error + if rf, ok := ret.Get(0).(func(string, io.Reader) error); ok { + r0 = rf(backup, backupItemOperations) + } else { + r0 = ret.Error(0) + } + + return r0 +} + func (_m *BackupStore) GetCSIVolumeSnapshots(backup string) ([]*snapshotv1api.VolumeSnapshot, error) { panic("Not implemented") return nil, nil @@ -289,6 +318,12 @@ func (_m *BackupStore) GetCSIVolumeSnapshotClasses(backup string) ([]*snapshotv1 return nil, nil } -func (_m *BackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) { +func (_m *BackupStore) GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error) { + panic("implement me") + return nil, nil +} + +func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error) { panic("implement me") + return nil, nil } diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index acda15323a..6609a0c686 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -33,6 +33,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" + "github.com/vmware-tanzu/velero/pkg/itemoperation" "github.com/vmware-tanzu/velero/pkg/plugin/velero" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -44,7 +45,7 @@ type BackupInfo struct { Log, PodVolumeBackups, VolumeSnapshots, - ItemSnapshots, + BackupItemOperations, BackupResourceList, CSIVolumeSnapshots, CSIVolumeSnapshotContents, @@ -59,8 +60,9 @@ type BackupStore interface { ListBackups() ([]string, error) PutBackup(info BackupInfo) error + PutBackupItemOperations(backup string, backupItemOperations io.Reader) error GetBackupMetadata(name string) (*velerov1api.Backup, error) - GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) + GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error) GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error) GetPodVolumeBackups(name string) ([]*velerov1api.PodVolumeBackup, error) GetBackupContents(name string) (io.ReadCloser, error) @@ -75,6 +77,8 @@ type BackupStore interface { PutRestoreLog(backup, restore string, log io.Reader) error PutRestoreResults(backup, restore string, results io.Reader) error + PutRestoreItemOperations(backup, restore string, restoreItemOperations io.Reader) error + GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error) DeleteRestore(name string) error GetDownloadURL(target velerov1api.DownloadTarget) (string, error) @@ -256,7 +260,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error { var backupObjs = map[string]io.Reader{ s.layout.getPodVolumeBackupsKey(info.Name): info.PodVolumeBackups, s.layout.getBackupVolumeSnapshotsKey(info.Name): info.VolumeSnapshots, - s.layout.getItemSnapshotsKey(info.Name): info.ItemSnapshots, + s.layout.getBackupItemOperationsKey(info.Name): info.BackupItemOperations, s.layout.getBackupResourceListKey(info.Name): info.BackupResourceList, s.layout.getCSIVolumeSnapshotKey(info.Name): info.CSIVolumeSnapshots, s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents, @@ -329,11 +333,11 @@ func (s *objectBackupStore) GetBackupVolumeSnapshots(name string) ([]*volume.Sna return volumeSnapshots, nil } -func (s *objectBackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) { - // if the itemsnapshots file doesn't exist, we don't want to return an error, since - // a legacy backup or a backup with no snapshots would not have this file, so check for +func (s *objectBackupStore) GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error) { + // if the itemoperations file doesn't exist, we don't want to return an error, since + // a legacy backup or a backup with no async operations would not have this file, so check for // its existence before attempting to get its contents. - res, err := tryGet(s.objectStore, s.bucket, s.layout.getItemSnapshotsKey(name)) + res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(name)) if err != nil { return nil, err } @@ -342,12 +346,33 @@ func (s *objectBackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapsho } defer res.Close() - var itemSnapshots []*volume.ItemSnapshot - if err := decode(res, &itemSnapshots); err != nil { + var backupItemOperations []*itemoperation.BackupOperation + if err := decode(res, &backupItemOperations); err != nil { return nil, err } - return itemSnapshots, nil + return backupItemOperations, nil +} + +func (s *objectBackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error) { + // if the itemoperations file doesn't exist, we don't want to return an error, since + // a legacy restore or a restore with no async operations would not have this file, so check for + // its existence before attempting to get its contents. + res, err := tryGet(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(name)) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + defer res.Close() + + var restoreItemOperations []*itemoperation.RestoreOperation + if err := decode(res, &restoreItemOperations); err != nil { + return nil, err + } + + return restoreItemOperations, nil } // tryGet returns the object with the given key if it exists, nil if it does not exist, @@ -510,6 +535,14 @@ func (s *objectBackupStore) PutRestoreResults(backup string, restore string, res return s.objectStore.PutObject(s.bucket, s.layout.getRestoreResultsKey(restore), results) } +func (s *objectBackupStore) PutRestoreItemOperations(backup string, restore string, restoreItemOperations io.Reader) error { + return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(restore), restoreItemOperations) +} + +func (s *objectBackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error { + return seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(backup), backupItemOperations) +} + func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (string, error) { switch target.Kind { case velerov1api.DownloadTargetKindBackupContents: @@ -518,8 +551,10 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupLogKey(target.Name), DownloadURLTTL) case velerov1api.DownloadTargetKindBackupVolumeSnapshots: return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeSnapshotsKey(target.Name), DownloadURLTTL) - case velerov1api.DownloadTargetKindBackupItemSnapshots: - return s.objectStore.CreateSignedURL(s.bucket, s.layout.getItemSnapshotsKey(target.Name), DownloadURLTTL) + case velerov1api.DownloadTargetKindBackupItemOperations: + return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupItemOperationsKey(target.Name), DownloadURLTTL) + case velerov1api.DownloadTargetKindRestoreItemOperations: + return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreItemOperationsKey(target.Name), DownloadURLTTL) case velerov1api.DownloadTargetKindBackupResourceList: return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResourceListKey(target.Name), DownloadURLTTL) case velerov1api.DownloadTargetKindRestoreLog: diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index 7042c40b37..316c542f5f 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -89,8 +89,8 @@ func (l *ObjectStoreLayout) getBackupVolumeSnapshotsKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumesnapshots.json.gz", backup)) } -func (l *ObjectStoreLayout) getItemSnapshotsKey(backup string) string { - return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-itemsnapshots.json.gz", backup)) +func (l *ObjectStoreLayout) getBackupItemOperationsKey(backup string) string { + return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-itemoperations.json.gz", backup)) } func (l *ObjectStoreLayout) getBackupResourceListKey(backup string) string { @@ -105,6 +105,10 @@ func (l *ObjectStoreLayout) getRestoreResultsKey(restore string) string { return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-results.gz", restore)) } +func (l *ObjectStoreLayout) getRestoreItemOperationsKey(restore string) string { + return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-itemoperations.json.gz", restore)) +} + func (l *ObjectStoreLayout) getCSIVolumeSnapshotKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-csi-volumesnapshots.json.gz", backup)) } diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index f3cb554866..4636bc8460 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -36,6 +36,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" + "github.com/vmware-tanzu/velero/pkg/itemoperation" "github.com/vmware-tanzu/velero/pkg/plugin/velero" providermocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks" velerotest "github.com/vmware-tanzu/velero/pkg/test" @@ -216,98 +217,98 @@ func TestListBackups(t *testing.T) { func TestPutBackup(t *testing.T) { tests := []struct { - name string - prefix string - metadata io.Reader - contents io.Reader - log io.Reader - podVolumeBackup io.Reader - snapshots io.Reader - itemSnapshots io.Reader - resourceList io.Reader - expectedErr string - expectedKeys []string + name string + prefix string + metadata io.Reader + contents io.Reader + log io.Reader + podVolumeBackup io.Reader + snapshots io.Reader + backupItemOperations io.Reader + resourceList io.Reader + expectedErr string + expectedKeys []string }{ { - name: "normal case", - metadata: newStringReadSeeker("metadata"), - contents: newStringReadSeeker("contents"), - log: newStringReadSeeker("log"), - podVolumeBackup: newStringReadSeeker("podVolumeBackup"), - snapshots: newStringReadSeeker("snapshots"), - itemSnapshots: newStringReadSeeker("itemSnapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "", + name: "normal case", + metadata: newStringReadSeeker("metadata"), + contents: newStringReadSeeker("contents"), + log: newStringReadSeeker("log"), + podVolumeBackup: newStringReadSeeker("podVolumeBackup"), + snapshots: newStringReadSeeker("snapshots"), + backupItemOperations: newStringReadSeeker("backupItemOperations"), + resourceList: newStringReadSeeker("resourceList"), + expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", "backups/backup-1/backup-1.tar.gz", "backups/backup-1/backup-1-logs.gz", "backups/backup-1/backup-1-podvolumebackups.json.gz", "backups/backup-1/backup-1-volumesnapshots.json.gz", - "backups/backup-1/backup-1-itemsnapshots.json.gz", + "backups/backup-1/backup-1-itemoperations.json.gz", "backups/backup-1/backup-1-resource-list.json.gz", }, }, { - name: "normal case with backup store prefix", - prefix: "prefix-1/", - metadata: newStringReadSeeker("metadata"), - contents: newStringReadSeeker("contents"), - log: newStringReadSeeker("log"), - podVolumeBackup: newStringReadSeeker("podVolumeBackup"), - snapshots: newStringReadSeeker("snapshots"), - itemSnapshots: newStringReadSeeker("itemSnapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "", + name: "normal case with backup store prefix", + prefix: "prefix-1/", + metadata: newStringReadSeeker("metadata"), + contents: newStringReadSeeker("contents"), + log: newStringReadSeeker("log"), + podVolumeBackup: newStringReadSeeker("podVolumeBackup"), + snapshots: newStringReadSeeker("snapshots"), + backupItemOperations: newStringReadSeeker("backupItemOperations"), + resourceList: newStringReadSeeker("resourceList"), + expectedErr: "", expectedKeys: []string{ "prefix-1/backups/backup-1/velero-backup.json", "prefix-1/backups/backup-1/backup-1.tar.gz", "prefix-1/backups/backup-1/backup-1-logs.gz", "prefix-1/backups/backup-1/backup-1-podvolumebackups.json.gz", "prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz", - "prefix-1/backups/backup-1/backup-1-itemsnapshots.json.gz", + "prefix-1/backups/backup-1/backup-1-itemoperations.json.gz", "prefix-1/backups/backup-1/backup-1-resource-list.json.gz", }, }, { - name: "error on metadata upload does not upload data", - metadata: new(errorReader), - contents: newStringReadSeeker("contents"), - log: newStringReadSeeker("log"), - podVolumeBackup: newStringReadSeeker("podVolumeBackup"), - snapshots: newStringReadSeeker("snapshots"), - itemSnapshots: newStringReadSeeker("itemSnapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "error readers return errors", - expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, - }, - { - name: "error on data upload deletes metadata", - metadata: newStringReadSeeker("metadata"), - contents: new(errorReader), - log: newStringReadSeeker("log"), - snapshots: newStringReadSeeker("snapshots"), - itemSnapshots: newStringReadSeeker("itemSnapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "error readers return errors", - expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, - }, - { - name: "error on log upload is ok", - metadata: newStringReadSeeker("foo"), - contents: newStringReadSeeker("bar"), - log: new(errorReader), - podVolumeBackup: newStringReadSeeker("podVolumeBackup"), - snapshots: newStringReadSeeker("snapshots"), - itemSnapshots: newStringReadSeeker("itemSnapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "", + name: "error on metadata upload does not upload data", + metadata: new(errorReader), + contents: newStringReadSeeker("contents"), + log: newStringReadSeeker("log"), + podVolumeBackup: newStringReadSeeker("podVolumeBackup"), + snapshots: newStringReadSeeker("snapshots"), + backupItemOperations: newStringReadSeeker("backupItemOperations"), + resourceList: newStringReadSeeker("resourceList"), + expectedErr: "error readers return errors", + expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, + }, + { + name: "error on data upload deletes metadata", + metadata: newStringReadSeeker("metadata"), + contents: new(errorReader), + log: newStringReadSeeker("log"), + snapshots: newStringReadSeeker("snapshots"), + backupItemOperations: newStringReadSeeker("backupItemOperations"), + resourceList: newStringReadSeeker("resourceList"), + expectedErr: "error readers return errors", + expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, + }, + { + name: "error on log upload is ok", + metadata: newStringReadSeeker("foo"), + contents: newStringReadSeeker("bar"), + log: new(errorReader), + podVolumeBackup: newStringReadSeeker("podVolumeBackup"), + snapshots: newStringReadSeeker("snapshots"), + backupItemOperations: newStringReadSeeker("backupItemOperations"), + resourceList: newStringReadSeeker("resourceList"), + expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", "backups/backup-1/backup-1.tar.gz", "backups/backup-1/backup-1-podvolumebackups.json.gz", "backups/backup-1/backup-1-volumesnapshots.json.gz", - "backups/backup-1/backup-1-itemsnapshots.json.gz", + "backups/backup-1/backup-1-itemoperations.json.gz", "backups/backup-1/backup-1-resource-list.json.gz", }, }, @@ -335,14 +336,14 @@ func TestPutBackup(t *testing.T) { harness := newObjectBackupStoreTestHarness("foo", tc.prefix) backupInfo := BackupInfo{ - Name: "backup-1", - Metadata: tc.metadata, - Contents: tc.contents, - Log: tc.log, - PodVolumeBackups: tc.podVolumeBackup, - VolumeSnapshots: tc.snapshots, - ItemSnapshots: tc.itemSnapshots, - BackupResourceList: tc.resourceList, + Name: "backup-1", + Metadata: tc.metadata, + Contents: tc.contents, + Log: tc.log, + PodVolumeBackups: tc.podVolumeBackup, + VolumeSnapshots: tc.snapshots, + BackupItemOperations: tc.backupItemOperations, + BackupResourceList: tc.resourceList, } err := harness.PutBackup(backupInfo) @@ -442,30 +443,30 @@ func TestGetBackupVolumeSnapshots(t *testing.T) { assert.EqualValues(t, snapshots, res) } -func TestGetItemSnapshots(t *testing.T) { +func TestGetBackupItemOperations(t *testing.T) { harness := newObjectBackupStoreTestHarness("test-bucket", "") - // volumesnapshots file not found should not error + // itemoperations file not found should not error harness.objectStore.PutObject(harness.bucket, "backups/test-backup/velero-backup.json", newStringReadSeeker("foo")) - res, err := harness.GetItemSnapshots("test-backup") + res, err := harness.GetBackupItemOperations("test-backup") assert.NoError(t, err) assert.Nil(t, res) - // volumesnapshots file containing invalid data should error - harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", newStringReadSeeker("foo")) - res, err = harness.GetItemSnapshots("test-backup") + // itemoperations file containing invalid data should error + harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemoperations.json.gz", newStringReadSeeker("foo")) + res, err = harness.GetBackupItemOperations("test-backup") assert.NotNil(t, err) - // volumesnapshots file containing gzipped json data should return correctly - snapshots := []*volume.ItemSnapshot{ + // itemoperations file containing gzipped json data should return correctly + operations := []*itemoperation.BackupOperation{ { - Spec: volume.ItemSnapshotSpec{ + Spec: itemoperation.BackupOperationSpec{ BackupName: "test-backup", ResourceIdentifier: "item-1", }, }, { - Spec: volume.ItemSnapshotSpec{ + Spec: itemoperation.BackupOperationSpec{ BackupName: "test-backup", ResourceIdentifier: "item-2", }, @@ -475,13 +476,13 @@ func TestGetItemSnapshots(t *testing.T) { obj := new(bytes.Buffer) gzw := gzip.NewWriter(obj) - require.NoError(t, json.NewEncoder(gzw).Encode(snapshots)) + require.NoError(t, json.NewEncoder(gzw).Encode(operations)) require.NoError(t, gzw.Close()) - require.NoError(t, harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", obj)) + require.NoError(t, harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemoperations.json.gz", obj)) - res, err = harness.GetItemSnapshots("test-backup") + res, err = harness.GetBackupItemOperations("test-backup") assert.NoError(t, err) - assert.EqualValues(t, snapshots, res) + assert.EqualValues(t, operations, res) } func TestGetBackupContents(t *testing.T) { @@ -564,7 +565,7 @@ func TestGetDownloadURL(t *testing.T) { velerov1api.DownloadTargetKindBackupContents: "backups/my-backup/my-backup.tar.gz", velerov1api.DownloadTargetKindBackupLog: "backups/my-backup/my-backup-logs.gz", velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup/my-backup-volumesnapshots.json.gz", - velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup/my-backup-itemsnapshots.json.gz", + velerov1api.DownloadTargetKindBackupItemOperations: "backups/my-backup/my-backup-itemoperations.json.gz", velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup/my-backup-resource-list.json.gz", }, }, @@ -576,7 +577,7 @@ func TestGetDownloadURL(t *testing.T) { velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup/my-backup.tar.gz", velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup/my-backup-logs.gz", velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup/my-backup-volumesnapshots.json.gz", - velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup/my-backup-itemsnapshots.json.gz", + velerov1api.DownloadTargetKindBackupItemOperations: "velero-backups/backups/my-backup/my-backup-itemoperations.json.gz", velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup/my-backup-resource-list.json.gz", }, }, @@ -587,7 +588,7 @@ func TestGetDownloadURL(t *testing.T) { velerov1api.DownloadTargetKindBackupContents: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902.tar.gz", velerov1api.DownloadTargetKindBackupLog: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-logs.gz", velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-volumesnapshots.json.gz", - velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-itemsnapshots.json.gz", + velerov1api.DownloadTargetKindBackupItemOperations: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-itemoperations.json.gz", velerov1api.DownloadTargetKindBackupResourceList: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-resource-list.json.gz", }, }, @@ -598,7 +599,7 @@ func TestGetDownloadURL(t *testing.T) { velerov1api.DownloadTargetKindBackupContents: "backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz", velerov1api.DownloadTargetKindBackupLog: "backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz", velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz", - velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz", + velerov1api.DownloadTargetKindBackupItemOperations: "backups/my-backup-20170913154901/my-backup-20170913154901-itemoperations.json.gz", velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz", }, }, @@ -610,7 +611,7 @@ func TestGetDownloadURL(t *testing.T) { velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz", velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz", velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz", - velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz", + velerov1api.DownloadTargetKindBackupItemOperations: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-itemoperations.json.gz", velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz", }, }, @@ -618,8 +619,9 @@ func TestGetDownloadURL(t *testing.T) { name: "restore", targetName: "my-backup", expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{ - velerov1api.DownloadTargetKindRestoreLog: "restores/my-backup/restore-my-backup-logs.gz", - velerov1api.DownloadTargetKindRestoreResults: "restores/my-backup/restore-my-backup-results.gz", + velerov1api.DownloadTargetKindRestoreLog: "restores/my-backup/restore-my-backup-logs.gz", + velerov1api.DownloadTargetKindRestoreResults: "restores/my-backup/restore-my-backup-results.gz", + velerov1api.DownloadTargetKindRestoreItemOperations: "restores/my-backup/restore-my-backup-itemoperations.json.gz", }, }, { @@ -627,16 +629,18 @@ func TestGetDownloadURL(t *testing.T) { targetName: "my-backup", prefix: "velero-backups/", expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{ - velerov1api.DownloadTargetKindRestoreLog: "velero-backups/restores/my-backup/restore-my-backup-logs.gz", - velerov1api.DownloadTargetKindRestoreResults: "velero-backups/restores/my-backup/restore-my-backup-results.gz", + velerov1api.DownloadTargetKindRestoreLog: "velero-backups/restores/my-backup/restore-my-backup-logs.gz", + velerov1api.DownloadTargetKindRestoreResults: "velero-backups/restores/my-backup/restore-my-backup-results.gz", + velerov1api.DownloadTargetKindRestoreItemOperations: "velero-backups/restores/my-backup/restore-my-backup-itemoperations.json.gz", }, }, { name: "restore with multiple dashes", targetName: "b-cool-20170913154901-20170913154902", expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{ - velerov1api.DownloadTargetKindRestoreLog: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-logs.gz", - velerov1api.DownloadTargetKindRestoreResults: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-results.gz", + velerov1api.DownloadTargetKindRestoreLog: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-logs.gz", + velerov1api.DownloadTargetKindRestoreResults: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-results.gz", + velerov1api.DownloadTargetKindRestoreItemOperations: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-itemoperations.json.gz", }, }, } diff --git a/pkg/volume/item_snapshot.go b/pkg/volume/item_snapshot.go deleted file mode 100644 index 909c219944..0000000000 --- a/pkg/volume/item_snapshot.go +++ /dev/null @@ -1,57 +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 volume - -import isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1" - -// ItemSnapshot stores information about an item snapshot (includes volumes and other Astrolabe objects) taken as -// part of a Velero backup. -type ItemSnapshot struct { - Spec ItemSnapshotSpec `json:"spec"` - - Status ItemSnapshotStatus `json:"status"` -} - -type ItemSnapshotSpec struct { - // ItemSnapshotter is the name of the ItemSnapshotter plugin that took the snapshot - ItemSnapshotter string `json:"itemSnapshotter"` - - // BackupName is the name of the Velero backup this snapshot - // is associated with. - BackupName string `json:"backupName"` - - // BackupUID is the UID of the Velero backup this snapshot - // is associated with. - BackupUID string `json:"backupUID"` - - // Location is the name of the location where this snapshot is stored. - Location string `json:"location"` - - // Kubernetes resource identifier for the item - ResourceIdentifier string "json:resourceIdentifier" -} - -type ItemSnapshotStatus struct { - // ProviderSnapshotID is the ID of the snapshot taken by the ItemSnapshotter - ProviderSnapshotID string `json:"providerSnapshotID,omitempty"` - - // Metadata is the metadata returned with the snapshot to be returned to the ItemSnapshotter at restore time - Metadata map[string]string `json:"metadata,omitempty"` - - // Phase is the current state of the ItemSnapshot. - Phase isv1.SnapshotPhase `json:"phase,omitempty"` -}