From 785e1aa5d330fe0343d4876f79bb778561534f23 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Fri, 19 May 2023 11:46:44 -0400 Subject: [PATCH 01/26] Speedup update-1fmt.sh by removing xargs, printf. Signed-off-by: Tiger Kaovilai --- hack/update-1fmt.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/update-1fmt.sh b/hack/update-1fmt.sh index fef575451..204801bdf 100755 --- a/hack/update-1fmt.sh +++ b/hack/update-1fmt.sh @@ -35,7 +35,7 @@ fi files="$(find . -type f -name '*.go' -not -path './.go/*' -not -path './vendor/*' -not -path './site/*' -not -path '*/generated/*' -not -name 'zz_generated*' -not -path '*/mocks/*')" echo "${ACTION} gofmt" -output=$(printf '%s\n' "${files}" | xargs gofmt "${MODE}" -s) +output=$(gofmt "${MODE}" -s ${files}) if [[ -n "${output}" ]]; then VERIFY_FMT_FAILED=1 echo "${output}" @@ -47,7 +47,7 @@ else fi echo "${ACTION} goimports" -output=$(printf '%s\n' "${files}" | xargs goimports "${MODE}" -local github.com/vmware-tanzu/velero) +output=$(goimports "${MODE}" -local github.com/vmware-tanzu/velero ${files}) if [[ -n "${output}" ]]; then VERIFY_IMPORTS_FAILED=1 echo "${output}" From a845ea4d57d4b31a9090afe60387bbbddab3e34e Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Fri, 19 May 2023 12:37:23 -0400 Subject: [PATCH 02/26] lint: pkg/backup/backup_pv_action.go:79: unnecessary trailing newline (whitespace) Signed-off-by: Tiger Kaovilai --- pkg/backup/backup_pv_action.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/backup/backup_pv_action.go b/pkg/backup/backup_pv_action.go index e11598809..56c4fff8a 100644 --- a/pkg/backup/backup_pv_action.go +++ b/pkg/backup/backup_pv_action.go @@ -76,5 +76,4 @@ func (a *PVCAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runti } return &unstructured.Unstructured{Object: pvcMap}, []velero.ResourceIdentifier{pv}, nil - } From ceccd5a92c59dbf15be23528bad595cb7a530bb8 Mon Sep 17 00:00:00 2001 From: yanggang Date: Tue, 25 Jul 2023 23:11:49 +0800 Subject: [PATCH 03/26] Fix some typos for DataMovers type. Signed-off-by: yanggang --- config/crd/v1/crds/crds.go | 4 ++-- config/crd/v2alpha1/bases/velero.io_datauploads.yaml | 2 +- config/crd/v2alpha1/crds/crds.go | 2 +- .../volume-snapshot-data-movement.md | 2 +- pkg/apis/velero/v2alpha1/data_upload_types.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index cdcf68520..a375900ac 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -33,10 +33,10 @@ var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=M\x93\xdb:rw\xff\n\x94r\xf0\xee\xd6H^'\x97\xd4ܼc;Q\xbd\xf7\xfc\xa6<\xb3\xdeK.\x10ْ\xf0\x06\x04\xb8\x00\xa8\xb16\x95\xff\x9eB\x03\xe0'H\x82\xb2\xe6ś2/\xf6P@\xa3\xd1\xdd\xe8\x0ft\x03\\\xafׯhɾ\x80\xd2L\x8a[BK\x06_\r\b\xfb\x97\xde<\xfd\xbb\xde0\xf9\xe6\xf4\xf6\xd5\x13\x13\xf9-\xb9\xab\xb4\x91\xc5gвR\x19\xbc\x87=\x13\xcc0)^\x15`hN\r\xbd}E\b\x15B\x1aj_k\xfb'!\x99\x14FI\xceA\xad\x0f 6O\xd5\x0ev\x15\xe39(\x04\x1e\x86>\xfdy\xf3\xf6_7\x7f~E\x88\xa0\x05ܒ\x1d͞\xaaRoN\xc0A\xc9\r\x93\xaft\t\x99\x05yP\xb2*oI\xf3\x83\xeb\xe2\x87s\xa8\xfe\x05{\xe3\vδ\xf9\xa9\xf5\xf2g\xa6\r\xfeP\xf2JQ^\x8f\x84\xef4\x13\x87\x8aS\x15\u07be\"Dg\xb2\x84[\xf2\xc9\x0eQ\xd2\f\xf2W\x84x\xacqȵG\xf8\xf4\xd6AȎPP\x87\v!\xb2\x04\xf1\xee~\xfb\xe5\xdf\x1e:\xaf\t\xc9Ag\x8a\x95\x06\xe7\xee\x10#L\x13J\xbeഈ\xf2T&\xe6H\rQP*\xd0 \x8c&\xe6\b$\xa3\xa5\xa9\x14\x10\xb9'?U;P\x02\f\xe8\x1a4!\x19\xaf\xb4\x01E\xb4\xa1\x06\b5\x84\x92R2a\b\x13İ\x02\xc8\x1f\xde\xddo\x89\xdc\xfd\x06\x99ф\x8a\x9cP\xadeƨ\x81\x9c\x9c$\xaf\np}\xff\xb8\xa9\xa1\x96J\x96\xa0\f\vtvOKxZo{\xd3{m)\xe0Z\x91\xdcJ\r\xb8ix*B\xee\x89f\xe7c\x8eL7\xd3E9\xea\x00&\xb6\x11\x15\x1e\xf9\ry\x00e\xc1\x10}\x94\x15ϭ\xb0\x9d@Y\x82e\xf2 \xd8?jؚ\x18\x89\x83rj\xc0\v@\xf30a@\t\xcaɉ\xf2\nn\x90$\x05=\x13\x05v\x14R\x89\x16\x93\x96r\x88\xfe؛G\xc3H+\xa98\xf31ԭ#зh᱈Z\x0f\x17-g\xceN,\xaf(G#JE\xe6\xe6Ck\xbcb\xc6x\x82\xc9\x03\x9c\x9d\x89\x0e\x98[Nt\xc2\x11)\xc0\xba\xa0\x85\x8d\xc1\x86McF\xc6=c\xd3\xdeQ\xebgH'\xa2\xaa\xe2\xa0\xfdPαkt\xc0\xcd(\xe8\x9a#.~\xe7t\a\x9ch\xe0\x90\x19\xa9\xe2\xe4\x98c\xb2{R\xf4\xda\b\x15#\x1a\xae\x1b\x104\x13\x9b\x00I0\xa8:\xb2\xec\xe8\xdc4+A\b\x87\xe4\x124\xaerZ\x96\x055\xd9\xf1\xc3W\xeby\xe9&\x97\x93H\x97~g\xe7\xbf\x06\x7f\xbek\x98g\xe0\x12\xdc\x14f\n\n\xb7\xd9\xfc\x88\xd4lޠG\xf5\xee\xd3\xfb\xd8nV\xf7I\x90\xbc\xc1D\xde\xf5\x90m\x0f\xed\x9d\xf2\xd4ixק\x8eo\\\x1a\xe1\x86P\xf2\x04g\xe7\xb1PA,s\xa8\x1dh$\xd2\x19\x12\a\xf3\x19(dOpF0>A1\xdb;U\x14\xdc\xf3\x04\xe7\x94f=\x02Z\x9c\x98\xf6\x89\x17KI\xfb\x02\t\x81\xfb\xd9\xe9\xc4#\x98l\n\xbah~r$]\x91\x84'\xd0\xfe\x82i\xd6lk%ꐱ\xaf\xb5c\x91]\x05GV&NԚ9\xdcJ\x90\xfb:\xdd\xf4\x85r\x96\xd7\x039\xb9ߊqo\xb8\xfb|\x92f+nȇ\xafL\xfb\x8c\xdf{\t\xfa\x934\xf8\xe6E\xc8\xe9\x10\xbf\x80\x98\xae#./\xe1Զ\xa5C;o\x95 \xdc\xeeٺ\b\xaff\x0f\xd3d+l\xdc\xe2\xe9\x81YH7ܴ}\xe8>E\xa511%\xa4X\xbb\xad\x97\xd8H\x8e؉ \xa5\xeapd\x88Z=\xe8\xc8^O\xfcy\xb4\x96\xc4\xf5wyUN3\xc8C^\x05\xb3\x81\xd4\xc0\x81e\xa4\x00u\x982\x1c\xed\xa7\xb4\xfa=\r\x85D\xad랅\x12\x96f\xda\xc3\xe3Uwt\xf3\xbb\xfb\xac\xed\xcaMh\x15\x98=\xdbt$\t8\xdet~Fhb\xd1\xff\x98\xa5.\xcds,Ӡ\xfc~\x81\xc6_\xc0\x8b\xa1\xedw\x889\vYPLN\xfc\xb75s(\xd0\xffCJ\xcaT\xc2\x1a~\x87\xe5\x18\x1c:}\xfd.V{\x18;\x02\xd3\xc4\xf2\xf7D\xf90\xbd\x1c\x99\x9c\xb4\xba\x05\xb83\xe4r?\xf0Xn\xc8\xf3QjgS1)2\v\x92i\xb2z\x82\xb3OƵ\xf5\xc0j+V\xce\xc0/V7\xb5\xb7 \x05?\x93\x15\xf6]}\x8b\x13\x94(\x89\x89;\xae\x9f\xea\xf2\x93uA˵\x97^#\v\x96\x8d\xf6\xc3r\x99T\x17\xdbƠ\xc1\x83\xb0\x1d\xeb\x1a\x11\xeb\x1eO\xcd6I~K\xa9#\x99\xef\x11T\xee\xa56nG\xb2\xe3\xce.\xd9\xfd\"N\xf6\xfc\xae\x17\xa1{W\xa5#U\xa8\xbf\xb0겷Qk\xb9\xad\xa75\xb3\xcb\x19\xf8\x9d4\a\xd4\x06d\xabf\xe5;=\xbcr9\v\x1c\x84f\xe8\x94\xcc\xc2-\x95\xcc@G\xb3\xc5͓\xa0\xe5g6\x17\xeb\x8dE\xea\x02\x1fW\xdc0\xbd\x99\x19\x9etG\xd6\x12ia\b\xf0\xe1kk\xd7\xd3*\r\xfb\xf7\x9c\xf0-ŋ\xe0Z/\nگ\xe2IB\xf1\xce\xf5\f\xcb\xc4\x03r!\x85:T\xa8\"\xd2=O/H߃y/\x98\xd8\xe2\x00\xe4\xed\xd5݁Z\xb9\xc6j9bO\x8f\xe4\xbeoC\xf4\xfa\x85\x18)\xe6\x88=\xa5\xc4\x1d\x7f\x05\x1d\xce\r\xf7ǭ\x83\x99\bRH\xd3ކ\xb0pK\x99\xbf\xd6dϔ6mDS\x85\"^+\x12{\x96F\\\xe2\x83R\x17\x05\\\xbf\xba\x9e\xad\r\xb0\xa3|\x0e\xb5P\xa3\xc5\x13\xb1\a\x93I@؞0C@d\xb2\x12\xb8mc\x97:\x0e\xe1X\xe0\x14t2\xc9\xd2\x14\x84}@TE\x1a\x01\xd6(uLL\xeeﴛ\x7f\xa4,\x96\x81\x1e>\v\xd9f\xc6J\xc6bO\x87m\xa1v\xac]\xd4VЯ\xac\xa8\nB\vK\xfa\xd4pi\xef*\xce:\x1c\xaf\xeb\xce\x10.\x9a\x11#\xed\xa2*9\x98\xd4\x15\xe9*\xcc\xec2\xd1,\x87\xda0{)\x90\x82P\xb2\xa7\x8c\x8f\x94\xbb\f\x9fE\xb4]\x12\xa3xeq\xbd\xe0#m\xf05\x92\"a\x037\xd1ɜ\xd6֥Jw\x15\xef\x15\xa4\xb9gs\x9b\xd9\xc1=+\x15\x93X\xa7we\x0f͋\x18\x15\xe7\x1f.\xda\xe0\xf9\xe1\xa2\xcd3ś\xdfP/\x1f\x06\xbd\xa8^~;\xd9\xf9J\xf5\xf2\x1eþ\xd7}\xa5j\xf90\xffe\xd5\xf27\xbeT\xa3\x00\x1a\xb6\xe7].>\x1f\x1b\xb27\xda\x00\xf0\xef\\\x7f;\xa8\x0f\xbb\x8c\xf1/^m?\xc2\xfc\xc4\xc2\xf8՟V\xdf\x1f\xa5\x17\xd3v\x94\x9a\x032E&\x15\x8e\xf9ڸ\xb2]\xdc\xd5-\xa4\xfb>\x85s\xa94\xa6V\xccO\xd1k\xa8eZ\x04\xfb^\x17\xb3\x81\xe2\xd7\xd2ۊ\xb4\x13\x9d\xdbH\x97\xb93\x9d\x91\xf9\xe0&\x80>\x8b쨤\x90\x95\xf6\xfb\x06\x16\xfa;ܾ\xf0\xa9P,\x03KT\xb0o\xc9QV\x91\x8a\xed\t\xda\xcd\xd4\xef\x8dW\xed\xf9\x1c5\x18zz\xbb\xe9\xfeb\xa4\xaf\xe1#\xcf\xcc\x1c#x>\x1fA`v]\x1c\xda\x05\xf9a\xc1\xf9\x03\xe7}A\"R\x11\xc1\xf8\x98\xc1\xaaO\xe5wLӯ\xa5\xdb$Zl\xf9\xa778Ҫ\xfc.\xae\xed\xeb\xd6\xee\x8d8\x81K\x93\xd9\xe9G\x18ҫ\xf7\xa6\xcb\xed\x96\xd4\xec\xf5+\xf2F\x81\xceW\xea\xa5\xecM\xcdT\xe5]P\x8b\x97X\x87\xfdͩ\xf7\x94j\xbb\x8bj\xecfK\x95\x13+\xeb\xba5s\xd3 \x17\xd4\xd3%\x11g\xbevnqŜ\xafP\x9b\x9cGr\x9d\\\xa4\x02n\x12\xf0hu\xdcT\xdd\xdb̾\xf7\xb0&.\xbd\xdam\x124V\xc2\xcd\u05f8]\xaf\x92\xfd\x1aQ\xf6\xb8\xaa\x99\xadS\x9b\x8d§\xf1\x9b\xadD[R\x7f6K\xb1\vk\xcd\xeaZ\xb2\x91q\x97V\x98u+\xc8F\x80\xa6ԕ\x8dԍ\x8d@\x9c\xac&K\xad\x16\x1b\x81=cv'\xa5d\xf2\xc7%Ub\xf1KTȬ5俗\xfc]J\x06\xa9:\xce\xe5\\@\xf3k\xaf\xb9\xe5|\U0003199d\u0558\x9f\xca\xccq\xb9\xb3ZTܰ\x92cz\xf1\xc4\xf2h\xccn\x8ep\xae/\x86\xf8M\xe2qMw\x99\t\xf9\xf5s-̛\x9e\xcbM5y\x06\xce\t\x8d\x89\xe2`晻\a(\x93k\xb0\x16\xc2.O\x7f兿.\xe8\xc6\xc9;\x9eH\x8de`\xcc\x11\n\ve\xfcړQU>\xedN:\xcf\x17\xdf\xfd\xbd\x02u&x=K\xed_̜\x87r\xcbR\xdbX((\n\xafm\xdc\xedS=7\xbbY\x9e\xe4\x9dp\x06/\n\xb6\x87#±\x1a\x82\u05fc\xb6\xca\xd0F\r#M\xe3\x1b\xb1\xb2\xee\x1d\xf9}\xceSM=L\xf4\xb2\x81\xc6\xf2Pc\xd6ȿH\xb8qy\xc01\x012\xf5pPZB|\xf60\xd0K\x05\x1es\xa1G\xb2ϕv\xd8\xe7%\x0e\xf9,8ܳ \x04Y\x16\x84$\x93)\xe5\x10ϋ\x84\"/\x18\x8c\xbcD8rY@2\x03\xb2w8'\xe5\xd8MR\xb1Gr\xbe3\xa5Xc>%9}\x9c&\xe1\x18MB\xb2r\x0eӄ\xe32ˎ\xc9$\xd0\xf0\x85B\x95\x17\nV^\"\\yـe6d\x99\x95\x9c\x99\x9f\x97\x1do\xb9x\xf3^\xaa\x1c\xd4d\xae#U4'\x85\xb2\x17_t\xc7\xec\xed\xfc\x87;\xe5l\xab\x8e+\x1b۰\xaeO\xbdg\xe4'&|\x1e\xd5\na\xcb\xeew\x120\x8d#\x12\xdf\xffo\xbc<\x7fۨ\xcb\xdah(\xa9\xc2\f\xeb\xee\xecJ+\xf4\x86|\xa0ٱ\a\xfd\x18\x8d+\xf6R\x15ԐU\x9d\xf2z\xe3\x80ۿW\x1bB>\xca:i߾IF\xb3\xa2\xe4g\x1b7D`\xae\xda .\x13\x88\xa8\xf0\x85\xf1\xef%gY\xc4ӊ^.\xe4\x1a\x0f\xae\x84\xc0+\x8f\xb2v껴\r\xe3\x8e\x16:e\xdd\xeb\x15\xf7\x92s\xf9\xbc0\x1c\xa7%\xfb\x0f\xbc\xa5y~\x0f\xe7\xdd\xfd\x16\x9b\x06I\xc1\u06dd\xeb\n\xa1\x1a\xe9\x1dX\x8b\xd9Lgl\xc5o\xf7\x1d\x88\x91J\xbb\xfaO\x94\xd6\xdab\xb3\xb1[\x97\\՟\xd54\xf7[\x87\xdd\x06\x85\x85\x8a3\x91X\xeba\x8eL\xe5\xeb\x92*svu\x0575\x0e\xe3\xfb8\xc1nN\xed\xb6\x8c\x9a\x97\xe1u\xbfQچ[\x7f1\x99w.\xbb\xa9\xd0>E/\xc1c\xfc(\xdf\xec!\xbe+\xe21\ue0ac\x91R\x91\xd7Ѣ\xa4\xab\xedbi\x7f\xb5\xed/\xf2\x04\uf8fbY\x1d\xf2<\xf4\x9aGʉ\x02Dw\xb9\xeb\xd4\xed\xa0x9\xe7e\xba(^\x1f\x14\x86\xf6\xd7w&\xceŷ\x8eL%\xdc\\\x1a\xe0\xea\xf8\xae\x8d]^\xf7_0\xb4\xaaU\x98wv|\xf0\x14\xb6\xae\xfa\x17\xdc\xfd\xe5\xfa5R\xdaHE\x0f\xf0\xb3tW/\xcfѠۺs\xef\xb6wyB\xcdbX\r\xb1P\xc0_\x02\xdd\x03֔\"\x0f\xae\xc1\xb5X.\xbc\xd5\xd7\x18>3\x99\xc7ǟ\xdd\x04\f+`\xf3\xber\xb9|\xab\xed4Xj\x86\x89\xb9N;\xfb\xdfc\xc4^\x10\xbcO\xb6ş\x16\xde\n\xb0\xdc\x19\xcb\xde\x16a\x7f\xea\\$\x1dH4'\xa2_\xe2\xbdZ\xfbK-&9\xd7#*\xa1cpZw\xe9\xe3\xce+\x1eW\xbe\ueb4bc\xce\xe4\xd8m\xe3x\xc3\xf6\xfc}\xe3\xee\"n\xffu\x01_4_)\xbc2\xd1_ҍW\f^t\xe5\xf8\xae\xae\v\xa9\xabN\xf4;cl\xa0\x1c\xd3\xdc\x11\xf4F\xfa\xd6\x06N\x1aʉ\xa8\x8a\x1d\xfa\xac1\x95Rw\xc1\x8a\x95\xc9R\x15\xe7\x80L0Α\x9a\t\x03\x87\xc1\xa6{l\xaew\xbe\xc6\xf9\x92\xb9\xd6}\xd3窫,\x03\xad\xf7\x15\xe7纾z\xc9\xc4c\xd6\xe5J\xa4\xf8H\x19\xbf\x88\x0e\xae\xe3\b\x11\xdc\xdcF\xf5h\x12\x9b}Q'\x88<,ށ)\xb0\x0f\x9e:XF\a\xcf\x02_k\xa5\r-\xe6nN\xbf\x1b\xf6\xc0\xcfZ\xa8\xbcU\x9dU_\xff\xfdLu\xc3\xe6\x98Oـs=\xd1\x05\xb5\xd0 'p\x02A\xa4\xc0\xaay\xbc\x8b\xd3}z\xa5\xdf'\x02\xb5\rŗ\xe5W%\x974\x0f\x06.D\x92\xfes\x1d\x8fh\xbe\xd5\t\xd4k=\x01\xb3\xbe\xd0=B\x84\xa1d\xba\xd0\xee\xd6\xfaF\xb0\x8e\x02M2\xfdQ]\x9bi\xd6\xd5\xf3\xc9J\xeb\xeea;\xd6sT\x82C\x83\x18\xff\x06\x1fN\xf8F%5\x9cY\xaa\x8a\x1a\xcelNAu\xd4Qdr\x8d\x82\xba\xfa4q\xad\xce\u07b8\x8c\x8d\x9c\a\x80Nj\xc2u\xfa\xee|Q\x01Z\xd3C\xb8j\xf9\xd9:`\a\x10\x80\x9b\x11\x91\xd9\xf8\xed\xdd\xe6\x027\xaf,R\xa4D\r\xa1C\x99\x86\xa9\x94h\xa5r\xea\xf3b\r\xba4{\x1a\xc5ԧ\xa2\xc3G\x0f\xdf\xf8\x8b\xd3\xd7{%\x8b\xb5\xe7\x05VW\xdc\xf8\xf4\x8ab\xd2\x06\xec\xe6\x18%9q\xdfT\xf27\x14\xa3\x18\x94%\bB\xb5\xc7'\xe1b\x89i\xb6N\xec\xa6jC\x95I\x8d\x83\x1e:\x8dgB \x84\x1c\xc7\xf7\xc1\xa7\x8f\xdc\x05\x1bw\xfe\x93b5\xe0\x1b\xa2\x99\b\xdf[t\xc9)'\n\xdaFF\nps-Z83\x88i:\x11L\x17\xfd\xdf7x9\xd56\xf1C\x8a\x17\xfc\xa5\u05fcw\x88\n?\xaeU7\xf1\x9ek\x84\x1e\x7f`{W˓Y\xac\xff\xf8\x7f~8\xea\x94\xe4e\xbd\x9et\xb0\xd0w\xaa=\xa5\x99Oi\xdds\xb0\x9e\x8f\x06\xe8\xfan\xaf\x179\xe9\xa7\xcb\xc2\xcekƜ\xe1S\xa0\u05c9\xc4N\x97E\x9b/\x16j^wv\xcf\x14??8\xb7\xc6\xfe\xe6\x9bEbM\x0f!\x12mF\xa6Qǟ\xb3\xd1f+\xd8\f8\x8e|-\xa8\x17\x80^)܌ځ\xc1KT\xa0ykm\xfb\x91\xfc\x9b\xff\r\x00\x00\xff\xff\x9a\xfbL\xe1\xa9x\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\xe3\xb8\x11\xbe\xebWt\xed\x1e|YQ3\x9bKJ\x97\x94FN\xaa\xa6\xe2\x89]#ǹ\xe4\xb0\x10Д\xb0\x06\x01\x06\x0fi\x94T\xfe{\xaa\x01\xf0!\x92\xb2\xe4\xa9$\x8b\x8bM\xb2\xd1\xe8\xfe\xfa\r\xcd\xe7\xf3\x19\xab\xe5\vZ'\x8d^\x02\xab%~\xf3\xa8\xe9\xc9\x15\xaf\xbfw\x854\x8b\xc3\xc7٫\xd4b\t\xeb༩\xbe\xa23\xc1r\xbc\xc7Rj\xe9\xa5ѳ\n=\x13̳\xe5\f\x80im<\xa3\u05ce\x1e\x01\xb8\xd1\xde\x1a\xa5\xd0\xcew\xa8\x8bװ\xc5m\x90J\xa0\x8d̛\xa3\x0f\x1f\x8a\x8f?\x17\x1ff\x00\x9aU\xb8\x84-㯡v\xdeX\xb6CexbY\x1cP\xa15\x8543W#\xa7\x13vքz\t݇\xc4!\x9f\x9e$\xff\x14\x99m\x12\xb3\x87\xcc,~W\xd2\xf9?_\xa6y\x90\xceG\xbaZ\x05\xcb\xd4%\xb1\"\x89\xdb\x1b\xeb\xff\xd2\x1d=\x87\xadS\xe9\x8bԻ\xa0\x98\xbd\xb0}\x06ษq\tqw\xcd8\x8a\x19@\x86&r\x9b\x03\x13\"\x82\xcdԓ\x95ڣ]\x1b\x15*ݞ%\xd0q+k\x1f\xc1L\xba@V\x06\x1am\xc0y\xe6\x83\x03\x17\xf8\x1e\x98\x83ՁIŶ\n\x17\x7fլ\xf9?\xf2\x03\xf8\xd5\x19\xfd\xc4\xfc~\tE\xdaU\xd4{暯\xc9FO\xbd7\xfeD\n8o\xa5\xdeM\x89\xf4\xc0\x9c\x7faJ\x8a(ɳ\xac\x10\xa4\x03\xbfGP\xccy\xf0\xf4\x82\x9e\x12B@\x10!4\b\xc1\x91\xb9|\x0e\xc0!q\x89\x18MK\xaaFg\x9d\x89M\xa2\xc0ˀK\x92\x9f\xded\xe9{l\x1b\xff.\xb8Ŗ\xa5\xf3\xac\xaa\xcf\xf8\xaevx\x89\xd9\x19\x14\xf7X\xb2\xa0|_U\xb2\x92\xea\xfb\xe5\xb9Z5\xf2B\xa4]g'ޟ\xbdK\xa7n\x8dQ\xc8\x12\x97Du\xf8\x98\xbc\x90\xef\xb1b\xcbLljԫ\xa7\xcf/\xbfۜ\xbd\x86)G\x1a\x04\x05\x19\x8e\xf5l\xb3G\x8b\xf0\x12\xe3/\xd9\xcde\xd5Z\x9e\x00f\xfb+r\xdf\x19\xb1\xb6\xa6F\xebe\x13,i\xf5rQ\xef\xed@\xa6;\x12;Q\x81\xa0$\x84ɏr\xbc\xa0Ț\x82)\xc1\xef\xa5\x03\x8b\xb5E\x87\xda\xf7\xe1m\x05+\x81\xe9,^\x01\x1b\xb4Ćb9(A\xb9\xeb\x80փEnvZ\xfe\xb3\xe5\xed\xc0\x9b\xec\xbc\x1e\x9d\x1f\xf0\x8c\xf1\xa9\x99\"W\r\xf8\x130-\xa0b'\xb0H\xa7@\xd0=~\x91\xc4\x15\xf0\x85\xfc]\xea\xd2,a\xef}햋\xc5N\xfa&\asSUAK\x7fZ\xc4t*\xb7\xc1\x1b\xeb\x16\x02\x0f\xa8\x16N\xee\xe6\xcc\xf2\xbd\xf4\xc8}\xb0\xb8`\xb5\x9cG\xd1uJ\x9a\x95\xf8\xd1\xe6\xac\xed\xee\xced\x1dEmZ1k\xbea\x01ʘ\xc9\v\xd2֤E\a4\xbd\"t\xbe\xfeq\xf3\f\xcd\xd1\xd1\x18C\xf4#\xee\xddFי\x80\x00\x93\xbaD\x9b\x8cXZSE\x9e\xa8Em\xa4\xf6\xf1\x81+\x89z\b\xbf\v\xdbJz\xb2\xfb?\x02:O\xb6*`\x1d\v\x13l\x11B\x1d㾀\xcf\x1a֬B\xb5f\x0e\xff\xe7\x06 \xa4ݜ\x80\xbd\xcd\x04\xfd\x9a:$N\xa8\xf5>4\xb5\xf0\x82\xbd&\xa3xS#?\x8b\x1f\x81NZ\xf2p\xcf<Ƹ\x18\xe0\x9aC\xfcr1m\xd6tp\xd3b\x9c\xa3s_\x8c\xc0ᗁȫ\x96\xf0L\xc6\x1am%],\x8bP\x1a;\xac\x18\xac\xcd\xc0\xfd\xd5d\xaab\xf4\ru\xa8Ƃ\xcc\xe1+2\xf1\xa8\xd5\xe9§\xbfY\xe9\xc7\a]0$\xad$\xe2\xe6\xa4\xf9\x13Zi\xc4\x15\xe5?\r\xc8[\b\xf6\xe6\betk\xedՉr\x90;i>ζ\xcdZ=}n2o\n\xa0\x1co\x19\xab\x02V9rM\t\x1f@HG\r\x80\x8bL\xc7`\xe9\xa0b\x83\xb0\x04oû\xd4\xe7F\x97r7V\xba\xdf\xd3\\\xf2\x98+\xac\aȭ\xe3I\x94\x9a\xc8;jk\x0eR\xa0\x9dS|\xc8R\xf2,I\xb0\xa9r\x95\x12\x95pcM/DYTŢ\xa0\xa8f\xea\x8a\r\xd7-a쀙\xd4Ƀ;\x061\xd9\xd8*\x97T\xedQ\x8b\xb6\x1b9\x93\xc6Ĭ\xe5P\xc0Q\xfa}J\x87j*\xee\xe0\xcdأ\xf5\x8a\xa7\xa9\xd7\x03ٟ\xf7H\x94\xa9\x80\"8\xe4\x16}\xf46T\xe4>\xe4J\x05\xc0\x97\xe0bB\x1d\xe6\x89f\xc5F\xad\xd9\xfd\x8a\xa71\xd0p\u0378\xb9\x85\xb9.\xf2\x1d\xb5\u038d\xc0\x16K\xb4\xa8\xfddR\xa7\x01\xc4j\xf4\x18\xf3\xba0\xdcQJ\xe7X{\xb70\a\xb4\a\x89\xc7\xc5\xd1\xd8W\xa9ws\x02|\x9e#h\x11NJŏ\xf1\xcf\x05\x95\x9f\x1f\xef\x1f\x97\xb0\x12\x02\x8cߣ%\xab\x95A5\x8e\xd6\xebo~\x8a5\xf6'\bR\xfc\xe1\xee{p1u\x8a\x9c\x1b\xb0\xd9D\xef?Q\xa3\x16\x85\"\x886\xc9*\xc6\x02UJ2v\x95\xad\x99r͔#Nu\x98\xfdE\x89\x89*\xc8TF}\xc5q2}#\xcc\x00\xbe\xcd;C\xcd+V\xcf\x135\xf3\xa6\x92|6\xd46\xb6\xc1W\"\xb2i\xbb\xa5\x16\x92S\xdbv\x1eI\xcd8\"κ\xf3\t\x18\x86\xfd\xfa\xa5\xfc1\rSR7W\xcf+\x12?\xf6i\xbb!.%\xb3\\\x11\x1dzj\xb7\x1ch\xa4\x8a\xc9\xec\x18\xe7\x98B\xb8њb\xd7\x1b`mb\xbcsÊ\xf0\xce|\xb2\r\xfc\x15'\x80\x1f\xa9\xf2)\x126\x18\xa7m$Kp\x18S\xf551\xe0zDp\xb6F{\x8b,\xeb\x15\x11\xb6E\x95\xc1z\x05۠\x85\xc2F\xa2\xe3\x1e5\xcd\x13\xb2\x14?\xfcf3\x93b\xce\xd3\b\x84\xe2+\x1e\xe4\xf8Nh\x8c\xee\xc3hG\x13\xf8m8\xd0\xc3/\xcdh\xbd\xb0\x99\xec\x97\t0J\xa9\xa8s\x9c\xc8\x13]\xc70\xbe\xbd\xfc\xb4y\xb8s\xb1\xe1G\xed\xa7\x9a\xc4#Z\x8c\xf3\x15\n\xea\xf9M\xbe\xc5\bΣ\x9dp\x80\xd6z\xd1栌\xde\r\x02'\xad|\xa7A\xfd\\r(cA\xa0\xa7Ҥw\xc0\xf7Lﰻ\xb3\xca\xf2\xbf-)\xb9\xcf\xc0g:\x0f\x91\xfa\x92{\xdcd\xd1g9\xd5ԏ\xee\x8b;\xe2\xe9\xbb\xe2F\xfaƲ\x17\x87\xa2+\xb8\x8f\xe8\x9b*M\xa0\xce}w\x7fܭ\xef\x1f\x86Ǘ\xd37 \xf1ޛ\xf37nA\xe0\xc8\\w\x87\xfe\xdb\xe1PQ\xb7z\xb5\x05\xfe\x92\xa8\xd2ec\xde\x02lk\x82\x7f+2\xef\xa6\x1c:\xff8\xf0\x1e\x19\xe3O\x1eך\f\xa2i,\u0083\xa5\xc1\xb3\xbbC\x8bIa\xaa\xb6\xdc~\x19\xb5\x1a\xfc2\xd3\xff6\xfe\xdd\xe6\x06\xbd&k\xed\xe8e\xaa\x97=\xbbf\x90\xfbo¶\xbdW^¿\xfe=\xfbO\x00\x00\x00\xff\xff\x80.\x12\xd3P\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96M\x93\xdb6\x0f\xc7\xef\xfe\x14\x98y\x0e\xb9<\x92\xb3\xed\xa5\xa3[\xb3\xc9a\xa7mƳ\x9bɝ&a\x8bY\x8ad\x01\xd0[\xb7\xd3\xef\xde!)\xf9E\xb67\xdbCy\x13\t\x02\x7f\xfe@\x80j\x9af\xa1\xa2\xfd\x8a\xc46\xf8\x0eT\xb4\xf8\x87\xa0\xcf_\xdc>\xffĭ\r\xcb\xdd\xdd\xe2\xd9z\xd3\xc1}b\t\xc3#rH\xa4\xf1#n\xac\xb7b\x83_\f(\xca(Q\xdd\x02@y\x1fD\xe5iΟ\x00:x\xa1\xe0\x1cR\xb3E\xdf>\xa75\xae\x93u\x06\xa98\x9fB\xef\u07b7w?\xb4\xef\x17\x00^\r\u0601A\x87\x82k\xa5\x9fS$\xfc=!\v\xb7;tH\xa1\xb5a\xc1\x11u\xf6\xbf\xa5\x90b\aDž\xba\x7f\x8c]u\x7f,\xae>\x14W\x8f\xd5UYu\x96\xe5\x97[\x16\xbf\xda\xd1*\xbaD\xca]\x17T\f\xd8\xfamr\x8a\xae\x9a,\x00X\x87\x88\x1d|β\xa2\xd2h\x16\x00㱋\xcc\x06\x941\x05\xa4r+\xb2^\x90\xee\x83K\xc3\x04\xb0\x01\x83\xac\xc9F)\xa0\xbe\xf4X\x8e\ba\x03\xd2#\xd4p \x01\xd68*0e\x1f\xc07\x0e~\xa5\xa4\xef\xa0ͼ\xdaj\x9a\x85\x8c\x06\x15\xf5\x87\xf9\xb4\xec\xb3`\x16\xb2~{K\x02\x8b\x92ē\x88\x12\xd7\x06\x0ft\xc2\xf7\\@\xb1oc\xaf\xf8<\xfaSY\xb8\x15\xb9\xda\xec\xee*i\xdd㠺\xd16D\xf4?\xaf\x1e\xbe\xfe\xf8t6\r\xe7Z\xaf\xa4\x16,\x83\x9a\x94fp\x95\x1a\x04\x8f\x10\b\x86@\x13Un\x0fN#\x85\x88$v\xbaZu\x9c\x14\xcf\xc9\xecL»\xac\xb2Z\x81\xc9U\x83\\\xa0\x8d\x97\x00\xcdx\xb0\n\xd32\x10FBF_\xeb\xe8\xcc1d#\xe5!\xac\xbf\xa1\x96\x16\x9e\x90\xb2\x1b\xe0>$gr\xb1\xed\x90\x04\bu\xd8z\xfb\xe7\xc17\xe7s\xe6\xa0N\xc91?\xd3(\x97\xce+\a;\xe5\x12\xfe\x1f\x9470\xa8=\x10\xe6(\x90\xfc\x89\xbfb\xc2-\xfc\x961Y\xbf\t\x1d\xf4\"\x91\xbb\xe5rkej\x1a:\fC\xf2V\xf6\xcbR\xffv\x9d$\x10/\r\xee\xd0-\xd9n\x1bE\xba\xb7\x82Z\x12\xe1RE\xdb\x14\xe9\xbe4\x8ev0\xff\xa3\xb1\xcd\xf0\xbb3\xad\x17\x17\xa4\x8eR\xe8\xafd \x97yM{\xddZOq\x04\x9d\xa72\x9d\xc7OO_`\n]\x921\xa7_\xb8\x1f7\xf21\x05\x19\x98\xf5\x1b\xa4\x9a\xc4\r\x85\xa1\xf8Dob\xb0^ʇv\x16\xfd\x1c?\xa7\xf5`\x85\xa7+\x99s\xd5\xc2}餹\xa8S4Jд\xf0\xe0\xe1^\r\xe8\xee\x15\xe3\x7f\x9e\x80L\x9a\x9b\f\xf6m)8}\x04\xe6ƕ\xda\xc9\xc2Ծo\xe4\xebJ\xd1>E\xd49\x83\x19b\xdem7V\x97\xf2\x80M x\xe9\xad\ue9e2\x9d\xd1=\x14x{\xb6p\xbd\xa0\xf38\xb6\xc9\xf9\xca\xcd\xc3Cɝ%\x9c\xdd\xc2\x06.z\xee\xeb\\J3\xfc\x97dj'\x1e\xd9\xe8D\x84^N\xfa\xb3\xba\xb6\xe9\xad,\x90(\xd0\xc5\xecLԧbT^ze=\x83\xf2\xfbq#H\xaf\x04^\x90r\x19\xe8\x90r\x9fA\x03&]\xf0\x1b\xb1\x9c\xbe%\x91\x82F\xe6\xf6\xc2\xce\n\x0eW4\xbd\x92\x9d<|rN\xad\x1dv \x94\xf0Ff\x15\x91\xda\xcf\xd6ʛ\xf5\x1d\x04\xabls-\a\x87w\xfa\xbbI(\xb8}\x1a.#5\xf0\x19_\xae\xcc>\xf8\x15\x85-!ϯ|^\\Uz\x87\x9f\x817P\xbaz)/&9\xf7;sB\x91%\x90ڞr\xe5\xb4>\xf4\xef\x0e\xfe\xfa{\xf1O\x00\x00\x00\xff\xff\x045\f\xc6i\n\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VOs۶\x13\xbd\xebS\xec\xcc\xef\x90_gB*i/\x1d\xddZ%\aO\xd3\xd4c\xa5\xbeC\xe0\x8aD\r\x02(v!\xc7\xfd\xf4\x9d\x05H\xfd\xa5d\xf9P\xde\b,\x16\x0fo\xdf>\xa0\xaa\xaa\x99\n\xe6\x11#\x19\xef\x16\xa0\x82\xc1\xef\x8cN\xfe\xa8~\xfa\x99j\xe3\xe7ۏ\xb3'\xe3\x9a\x05,\x13\xb1\xef\x1f\x90|\x8a\x1a?\xe1\xc68\xc3ƻY\x8f\xac\x1a\xc5j1\x03P\xceyV2L\xf2\v\xa0\xbd\xe3\xe8\xad\xc5X\xb5\xe8꧴\xc6u2\xb6\xc1\x98\x93\x8f[o?\xd4\x1f\x7f\xac?\xcc\x00\x9c\xeaq\x01\x8d\x7fv֫&\xe2\xdf\t\x89\xa9ޢ\xc5\xe8k\xe3g\x14PK\xee6\xfa\x14\x16\xb0\x9f(k\x87}\v\xe6OC\x9a\x87\x92&\xcfXC\xfc\xdb\xd4\xec\x173D\x04\x9b\xa2\xb2\xe7 \xf2$\x19\xd7&\xab\xe2\xd9\xf4\f\x80\xb4\x0f\xb8\x80\xaf\x02#(\x8d\xcd\f`8b\x86U\r\xa7\xdb~,\xa9t\x87\xbd*x\x01|@\xf7\xcb\xfd\xdd\xe3O\xab\xa3a\x80\x06IG\x138\x13u\x82\x19\f\x81\x82\x01\x01\xb0߁\x02\xe5@E6\x1b\xa5\x196\xd1\xf7\xb0V\xfa)\x85]V\x00\xbf\xfe\v5\x03\xb1\x8f\xaa\xc5\xf7@Iw\xa0$_\t\x05\xeb[\xd8\x18\x8b\xf5nQ\x88>`d3\xb2\\\xbe\x03\r\x1d\x8c\x9e\x00\x7f'g+QЈx\x90\x80;\x1c\xf9\xc1f\xa0\x03\xfc\x06\xb83\x04\x11CDBW\xe4t\x94\x18$H\xb9\xe1\x045\xac0J\x1a\xa0\xce'ۈ\xe6\xb6\x18\x19\"j\xdf:\xf3\xcf.7\tC\xb2\xa9U<\xcaa\xff\x19\xc7\x18\x9d\xb2\xb0U6\xe1{P\xae\x81^\xbd@\xc4\xccSr\a\xf9r\b\xd5\xf0\xbb\x8f\b\xc6m\xfc\x02:\xe6@\x8b\xf9\xbc5<\xf6\x8e\xf6}\x9f\x9c\xe1\x97yn\x03\xb3N\xec#\xcd\x1bܢ\x9d\x93i+\x15ug\x185\xa7\x88s\x15L\x95\xa1\xbb\xdc?u\xdf\xfc/\x0e\xddF\uf3b0\xf2\x8bȌ8\x1a\xd7\x1eLd\xcd_\xa9\x80\xa8\xbe\b\xa6,-\xa7\xd8\x13-C\xc2\xce\xc3\xe7\xd57\x18\xb7\xce\xc58e\xbf(g\xb7\x90\xf6%\x10\u008c\xdb`,E\xccʓ\x9c\xe8\x9a\xe0\x8d\xe3\xfc\xa3\xadAwJ?\xa5uo\x98F1K\xadjXfC\x815B\n\x8dblj\xb8s\xb0T=ڥ\"\xfc\xcf\v LS%\xc4\xdeV\x82C/<\r.\xac\x1dL\x8cNv\xa1^'\xad\xbe\n\xa8\xa5zB\xa0\xac4\x1b\xa3sk\xc0\xc6GP\xfb\xce\x1f\b\xac\x8f2Own\x06\xa7b\x8b|:z\x82\xe5[\x0e\x92\xed\x9f;ul4\xffǺ\xad\xc5+h\x00R\xdc\xe3\x87\xfa,\xe3e\f0\xa9\xdeI$\xa3\x88\x85\x06\xe1U\xac@L\xea\x10\xd3\xf9\xd6\xf2\xa1K\xfd\xf4\x06\x15\xfc\x9a1\x7f\xf1\xed\xd5\xf9\xa5w,r\xbf\x1a\xf4\xe8m\xeaq\xe5T\xa0ο\x12{\xc7\xd8\xff\x110\x96\x1b\xf3j\xe8x\xf1\xeen\xa9+\x81\xc9^\xdc\xf7\x01\xc5\xef\xf1\xf2I\x87\x80\x9b\xb2܀i\x88\xbc\xe9\xa0\xcb\xd5\xdd[(\xbc\x10~\xb5H\x17\xdav\xfc\xf2\xf5\xfc\xba\x06\xe5\x82\x1f5(Kʝ\x85 \xaf\x9b萑\xf6\xf6\xf9l\xb8\x9b\xcc\b\xf0\xdc\x19\xdd\xe5\x85Y\xc0\xe2\xccD^\x9b\xecso\x87/}o\"N4Q\x95\x9bkbX\xc0\x9f\r_p\xabK\x1bT\x83\x83\xdc\xe4x\xac8\xd1\x1bz\x8dDx\xdeF\x17O2\xd9\x04g\x83$/\xa7急\xe1A>\x8c\xfc\x1b\x00\x00\xff\xff\x02\x83F4\xa5\r\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VM\x93\xdb6\f\xbd\xfbW`\xa6\x87\xb43\x91\x9c\xb4\x97\x8eo\xad\x93\xc3N\xd24\xb3N\xf7NS\xb0\xc4.E\xb2\x04\xe8\xcd\xf6\xd7w@J\xfe\x94\xbd\xdeCu\x13\t\x82\x8f\x0f\x0f\x8f\xac\xaaj\xa6\x82y\xc0Hƻ\x05\xa8`\xf0;\xa3\x93?\xaa\x1f\x7f\xa5\xda\xf8\xf9\xf6\xfd\xecѸf\x01\xcbD\xec\xfb{$\x9f\xa2\xc6\x0f\xb81ΰ\xf1n\xd6#\xabF\xb1Z\xcc\x00\x94s\x9e\x95\f\x93\xfc\x02h\xef8zk1V-\xba\xfa1\xadq\x9d\x8cm0\xe6\xe4\xe3\xd6\xdbw\xf5\xfb\x9f\xebw3\x00\xa7z\\@㟜\xf5\xaa\x89\xf8OBb\xaa\xb7h1\xfa\xda\xf8\x19\x05Ԓ\xbb\x8d>\x85\x05\xec'\xca\xdaa߂\xf9Ð澤\xc93\xd6\x10\x7f\x9a\x9a\xfdl\x86\x88`ST\xf6\x1cD\x9e$\xe3\xdadU<\x9b\x9e\x01\x90\xf6\x01\x17\xf0E`\x04\xa5\xb1\x99\x01\fG̰\xaa\xe1t\xdb\xf7%\x95\xee\xb0W\x05/\x80\x0f\xe8~\xfbz\xf7\xf0\xcb\xeah\x18\xa0A\xd2\xd1\x04\xceD\x9d`\x06C\xa0`@\x00\xecw\xa0@9P\x91\xcdFi\x86M\xf4=\xac\x95~La\x97\x15\xc0\xaf\xffF\xcd@\xec\xa3j\xf1-P\xd2\x1d(\xc9WB\xc1\xfa\x166\xc6b\xbd[\x14\xa2\x0f\x18ٌ,\x97\xef@C\a\xa3'\xc0\xdf\xc8\xd9J\x144\"\x1e$\xe0\x0eG~\xb0\x19\xe8\x00\xbf\x01\xee\fA\xc4\x10\x91\xd0\x159\x1d%\x06\tRn8A\r+\x8c\x92\x06\xa8\xf3\xc96\xa2\xb9-F\x86\x88ڷ\xce\xfc\xbb\xcbM\u0090lj\x15\x8fr\xd8\x7f\xc61F\xa7,l\x95M\xf8\x16\x94k\xa0W\xcf\x101\xf3\x94\xdcA\xbe\x1cB5\xfc\xe1#\x82q\x1b\xbf\x80\x8e9\xd0b>o\r\x8f\xbd\xa3}\xdf'g\xf8y\x9e\xdb\xc0\xac\x13\xfbH\xf3\x06\xb7h\xe7d\xdaJE\xdd\x19F\xcd)\xe2\\\x05Se\xe8.\xf7O\xdd7?ġ\xdb\xe8\xcd\x11V~\x16\x99\x11G\xe3ڃ\x89\xac\xf9+\x15\x10\xd5\x17\xc1\x94\xa5\xe5\x14{\xa2eHع\xff\xb8\xfa\x06\xe3ֹ\x18\xa7\xec\x17\xe5\xec\x16Ҿ\x04B\x98q\x1b\x8c\xa5\x88Yy\x92\x13]\x13\xbcq\x9c\x7f\xb45\xe8N駴\xee\r\xd3(f\xa9U\r\xcbl(\xb0FH\xa1Q\x8cM\rw\x0e\x96\xaaG\xbbT\x84\xff{\x01\x84i\xaa\x84\xd8\xdbJp腧\xc1\x85\xb5\x83\x89\xd1\xc9.\xd4\xeb\xa4\xd5W\x01\xb5TO\b\x94\x95fctn\r\xd8\xf8\bj\xdf\xf9\x03\x81\xf5Q\xe6\xe9\xce\xcd\xe0Tl\x91OGO\xb0|\xcbA\xb2\xfdS\xa7\x8e\x8d\xe6G\xac\xdbZ\xbc\x82\x06 \xc5=~\xaa\xcf2^\xc6\x00\x93\xea\x9dD2\x8aXh\x10^\xc5\nĤ\x0e1\x9do-\x1f\xba\xd4OoP\xc1\xef\x19\xf3g\xdf^\x9d_z\xc7\"\xf7\xabA\x0fަ\x1eWN\x05\xea\xfc\v\xb1w\x8c\xfd\x9f\x01c\xb91\xaf\x86\x8e\x17\xef\ue5ba\x12\x98\xec\xc5}\xefQ\xfc\x1e/\x9ft\b\xb8)\xcb\r\x98\x86ț\x0e\xba\\ݽ\x86\xc2\v\xe1W\x8bt\xa1m\xc7/_\xcf/kP.\xf8Q\x83\xb2\xa4\xdcY\b\x9f\xd2\x1a\xa3CF\xda\xdb\xe7\x93\xe1n2#\xc0Sgt\x97\x17f\x01\x8b3\x13ym\xb2Ͻ\x1e\xbe\xf4\xbd\x898\xd1DUn\xae\x89a\x01\x7f6|\xc1\xad.mP\r\x0er\x93\xe3\xb1\xe2D\xaf\xf0\xbc\x1c?R\xadS\x8c\xe8xȒ\xdf\x00\xa7\vn5\xbd\xd1)\xfe\xba\xff\xfc\x82\xf3}\xd8G\xe6Ǭ2\xae\xa0\t\x11+2\xad\xbc\\dN\xbc/{\xd29\x19\xe5;~I\x1d\x135YQ\xfc\x1eLi\x98\x17 ~\xdc\x05\x16\x83FW.\xdfӷbN\x88\x94\x1f6Z\x9d>\xa9\xe4[#4h\x91\xb1\x81\xf5s\xb9i\x9e\x89\xb1?ǽ\xf1\xb1W\xbc\x00\xb9\x94+6\x132r\xc9Z\xb5\xb6\xb8\x00\x8e\xe9\x92\xca&\x0f\x1e:E\x13mxt\xe6\xaf\x123%\x8c]3^U\x06\\\xbc\x0f*\xf8\x82O\x13\xa3_\xa3\xd7H\x84\xe7mt\xf1$\x93Mp6H\xf2rj\x0eX\x1a\x1e\xe4\xc3\xc8\x7f\x01\x00\x00\xff\xff\xa7\x94\xfb\xf9\xa5\r\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1c\xb7\x11\xbe\xf3Wt\xc9\a\xc6U\x9aYKI\xa5R{\x93\xc98\xc5ĦX\xa2\xa4\x8b\xcb\a\xec\xa0w\x06\xe6\f\x80\x00\x98\xa56.\xff\xf7T\xe31;\x0f\xec.Ɋ\x9c\xb9\x90\x8bG\xe3C\xbf\xbbQ\x14\xc5\x05\xd3\xe23\x1a+\x94\\\x03\xd3\x02\xbf8\x94\xf4˖\x0f\x7f\xb3\xa5P\xabݛ\x8b\a!\xf9\x1a\xaez\xebT\xf7\x01\xad\xeaM\x85\u05f8\x15R8\xa1\xe4E\x87\x8eq\xe6\xd8\xfa\x02\x80I\xa9\x1c\xa3aK?\x01*%\x9dQm\x8b\xa6\xa8Q\x96\x0f\xfd\x067\xbdh9\x1aO<\x1d\xbd\xfb\xae|\xf3\xb6\xfc\xee\x02@\xb2\x0eנ\x15ߩ\xb6\xefpê\x87^\xdbr\x87-\x1aU\nua5VD\xbb6\xaa\xd7k8L\x84\xbd\xf1܀\xf9N\xf1Ϟ\xcc\xf7\x9e\x8c\x9fi\x85u\xff\xca\xcd\xfe(\xac\xf3+t\xdb\x1b\xd6.A\xf8I+dݷ\xcc,\xa6/\x00l\xa54\xae\xe1\x96`hV!\xbf\x00\x88W\xf4\xb0\n`\x9c{\xa6\xb1\xf6\xce\b\xe9\xd0\\\x11\x85Ĭ\x028\xda\xca\b\xed\x8a\x0e\xe1\xb1A\t\xae\x11\x16\xc2m\xe1\x91Y\x82c\x9c\xbfe\xfe`?Oۭc\x9d\x9e \xb82\xc8\x0e[\x03\x04\xce\x1c\xe6\x00\f\xfc\x04\xb5\x05\xd7 q\xde+\x16\x13R\xc8\xda\x0f\x05I\x80S\xb0A\x0f\x119\xf4:\x83LcUj\xc5K\x99\x88N`\xdd\xceF\xcf\xf1\x86\xd6\xff\xafQM\x00\xdd)\xfe\x02(\xcf:7,\x9e\x9c\xfay\xbc\x9e\x90\x00\x05X\x10\x16X\xdc\x1anq`tr\x90\x1f\xfe~\xff\x11\xd2\xd1^\x18s\xee{\xbe\x1f6ڃ\b\x88aBn1:\x98\xadQ\x9d\xa7\x89\x92k%\xa4\xf3?\xaaV\xa0\x9c\xb3\xdf\xf6\x9bN8\x92\xfb\xbf{\xb4\x8edU\u0095\xcf]\xc8a\xf6\x9a4\x97\x97p#\xe1\x8au\xd8^1\x8b_]\x00\xc4i[\x10c\x9f&\x82q\xda5_\x1c\xb86\x9aHI\xd3\x11y\xcd2\xa1{\x8d\x15I\x8f\x18H;\xc5VD\x0fE\xee\x9c͗\x97\x13\xc2yå/\xeb\x9d\xe6\x8b \x17\\f{\x1269\xf2\xa9\xc9a\x86\x95\v\xa2\x00\xed\xdc\xcb\x0e{Ƒ\xcbF\a[.(\x1c\x11\x03}Rq\xdb\a\xf5q\n\xaa\x86\xc9\x1a\xc3}\x11\xb6=E\xc7\xf2\xf2%v\xbcLIҗIM\xe6\x8e\xe3\xff\x16ܟx9\x9fA?\xe1r\xe3*\xe3\xe4\xe5\x1e\xfa\r\x1a\x89\x0e\xfd\xfd\xb8\xaa,]\xadB\xed\xecJ\xed\xd0\xec\x04>\xae\x1e\x95y\x10\xb2.H5\x8b\xa0\x03v\xe5\v\xe7\xd57\xfeϋ\xef\xe2k\xec\xa7^hR\xfb\x7f\xcd[\xd19v\xf5\xa2K\xa5\x1c\xf6\xe9q\xec\xf2>fV\xf3\xbdd\x16\x8f\x8d\xa8\x9aT\x9cD\x1f{Ę\x04e\xc2<\xb8f&\xf7_]\x95\x89\xa1\xbd!D\xfb\"v\xf7\n&9\xfdo\x85u4\xfe\"\x0e\xf6\xe2I\xe6\xfb\xe9\xe6\xfa\x8fQ\xf0^\xbc\xc8V\x8f$\xe0\xe1\xfbR\x1c`\x15\x1d\xd3EX͜\xeaD5[=퉜I\xe3>L\x16\xa7D3\x93\xdf\x0ek\x9e\x95G:Vg\x12\xb7q3\xf3Tzw\x92_\xd3\xc6\r\xab-0\x83\xc0\xa0c\x9a\xe4\xfc\x80\xfb\"$\x04\x9a\t\x8a\xe6\x14\xb0\x87\xae\b0\xad[\x91\r\xdc1\xecǔ5r\x82\xcarV\xdbcw\xcfJm\xdc\x05:#\x85O\xa3\xa5I\x06g\xfaP\xae\xc9\xd9\xf5\xa4;\xb5D\x8b\xb2\xef\x96P\nxPZ\xb0̸A\xeb\x16\xfaE\x13\xaf\x96y\xc9\ta\x05^\x9e\xe1AlXgJ\x9d(\x8a\x90\x17\x0e\xe5\x8e\xefQ\xe6\xea\x89\xe3\xc5\xc4Q\x88T\xcfS\x96;\x85X\xe4\v\xcf\xd9\x1a*\xc4fCZ\xf1\x8b9#3}\xca49风\x91.\xabq\xdf\x1e\x7fF=\x1e\xda\xfe\x91\xa7\xc1\xfb\xba\xf4\x18@\xa5\xc7K+\xf2JQV?\xe9\xe8\x9d\x11\xef\xd5r\x87o~\x19\x1e\xd5]td\xbd\xa3G\x82xF\xae\xa4\x86\x11\xb9\xb0\xd3\xc7;\xa2\x86ܧ\xdcT\x11l\x99h\x91Cz\t\x9a\xef\xc9P\x1dS\xd9\xe0\x96\x82C0\xbdT\xc8FxCZ\xdb X\xdfU\xba\xb4'h\xf6\x16\xb9\xef\x80d\x98\xb0Lu\xb7\xcat̅.h\x91%*\xfb\xb6e\x9b\x16\xd7\xe0L\xbf\x9c>a\x89\x1dZ\xcb\xeas\xa6\xf8SX\x15\xea\xfb\xb8\x05\xd8F\xf5n(\xf0'\xee\xf1\xd2F\x9dz^\x8f![:OՙQicc\x8a߶~\xcf\xd8\x11\x1c^\t=\xaa\r\xe6S\x84\x97\xf8\x04\x00\xff\xfcu\x0e!\xad\xc9\x19\xd8\xe0\xbdNZ\x18\x9cpʷ\xf8\x98\x19]<ۍ'\xaf\x92\xc9d\xe6~\xf0\xd6\xf0\xac\xfbǃα .\x83F\xb5ɘ\x95c-Ⱦ۠!>l\xf6\x0e\xedԝ\xe7\xba9\xbe\n<\xb0q\xb4?\xc9/P\x8a\x85mŤﺒu9\x05\\Xݲ}\x86p\xba\x88\xcf\xf4ȸ\xc8\x05\x1c\xf49\x19\xb5F㧞ۅ\U00098b95\x18\x9f\x83:Y|&\nŧ\xea\\\f\xbaG\xcd\fY\xba\x7fA\xb8\x9a?q\xbd\x06+|[\x942ϐ\x8a\x86\xa6\x85\xa5\xe0D\xa9\x952\x98q\x99\xb0\f+\x93 2\x85\xffGƏ\xac\x9e,\x06=r>\xa2\x1d[\xeb\xe3\x91~3<\x1b\xad\xe1\xb7\xdf/\xfe\x1b\x00\x00\xff\xffÊ\xc5\x01R\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y_\x93۶\x11\x7fק\xd8q\x1e\xae\x991\xa9\xd8\xedt:z\xb3\xef\x9aε\xc9Yc\x9d\xfd\x92\xc9\x03D\xacHD$\x80\x02\xa0tj&߽\xb3\x00A\xf1\x9f\xa4\xd3M.\xe1\x8b}\xc0b\xf1\xc3\x0f\xfb\x0f\xab$IfL\x8b\xafh\xacPr\x01L\v|r(\xe9/\x9bn\xffaS\xa1\xe6\xbbw\xb3\xad\x90|\x01\xb7\xb5u\xaa\xfa\x8cV\xd5&\xc3;\xdc\b)\x9cPrV\xa1c\x9c9\xb6\x98\x010)\x95c4l\xe9O\x80LIgTY\xa2Ir\x94\xe9\xb6^\xe3\xba\x16%G\xe3\x95ǭwߥ\xefާ\xdf\xcd\x00$\xabp\x01Z\xf1\x9d*\xeb\n\rZ\xa7\f\xdat\x87%\x1a\x95\n5\xb3\x1a3R\x9e\x1bU\xeb\x05\x1c'\xc2\xe2f\xe3\x00z\xa9\xf8W\xaf\xe7s\xd0\xe3\xa7Ja\xdd\x7f&\xa7\x7f\x10\xd6y\x11]ֆ\x95\x138\xfc\xac\x152\xafKf\xc6\xf33\x00\x9b)\x8d\vx (\x9ae\xc8g\x00\xcd9=\xb4\x04\x18\xe7\x9e9V.\x8d\x90\x0e\xcd-\xa9\x88\x8c%\xc0\xd1fFh\xe7\x99i\xf5\x80ڀ+\x90\xb6\xf4\xac2!\x85\xcc\xfdP\x80\x00N\xc1\x1a\xa1A½2\x80_\xac\x92K\xe6\x8a\x05\xa4D\\\xaa\x15Oe\xd4\xd9\xc8\x04\xce\x1f\x06\xa3\xee@\xe7\xb0\xce\b\x99\x9fB\xf6;\x83\xea\xe1Y*\xfeL$\x8f\x05z\x99\x88\xa6֥b\x1c\rm^0\xc9K\x042Pp\x86I\xbbAs\x02E\\\xf6x\xd0}$_\xa2\xbe\xce\xcc5\xec\\CE\x90\xedm\xff\xb5;tiߥ\xe2\xcd\x02h\x8c\x1a\xacc\xae\xb6`\xeb\xac\x00f\xe1\x01\xf7\xf3{\xb94*7h\xed\x04\f/\x9e\xea\x82\xd9>\x8e\x95\x9fx]\x1c\x1be*\xe6\x16 \xa4\xfb\xfb\xdfNck\x16\xa5N9V~<8\xb4=\xa4\x8f\xc3ဖ\x9c-o\xae\xffO\x81\xbb&HwJ\xf6y\xfd8\x18\x9d\x02\xdbQ\x1a\xe3m\x9a\x19\xf4\xa1\xf6QTh\x1d\xabtO뇼\xaf\x8f3\x17\x06\xc2\xf4\xee]\beY\x81\x15[4\x92J\xa3\xfc\xb0\xbc\xff\xfa\xd7Uo\x18@\x1b\xa5\xd18\x11\xa3k\xf8:ɣ3\n}foHa\x90\x02NY\x03mp\x8a0\x86\xbc\xc1\x10\x9cEX0\xa8\rZ\x94!\x8f\xf4\x14\x03\t1\tj\xfd\vf.\x85\x15\x1aR\x03\xb6Pu\xe9#\xd0\x0e\x8d\x03\x83\x99ʥ\xf8_\xabے\xefѦ%s\u0604\xf8\xe3\xe7c\xb0d%\xecXY\xe3[`\x92C\xc5\x0e`\x90v\x81Zv\xf4y\x11\x9b\u008fd!Bn\xd4\x02\n\xe7\xb4]\xcc\xe7\xb9p1if\xaa\xaaj)\xdca\xee\xf3\x9fX\xd7N\x19;\xe7\xb8\xc3rnE\x9e0\x93\x15\xc2a\xe6j\x83s\xa6E\xe2\xa1K\x9f8ӊ\x7fc\x9a4kozXGN\x17>\x9f\xeb\xce\xdc\x00%;\x10\x16X\xb34\x9c\xe2Ht\fٟ\xff\xb9z\x84\xb8\xb5\xbf\x8c!\xfb\x9e\xf7\xe3B{\xbc\x02\"L\xc8\r\x05]\xbačQ\x95\u05c9\x92k%\xa4\xf3\x7fd\xa5@9\xa4\xdf\xd6\xebJ8\xba\xf7\xff\xd6h\x1d\xddU\n\xb7\xbe\x92\xa0xYk\xb2\\\x9e½\x84[Vay\xcb,\xbe\xfa\x05\x10\xd36!b\x9fw\x05\xdd\"h(\x1cX\xebL\xc4\n\xe6\xc4}\r\xab\x92\x95ƌ\xae\x8f\x18\xa4\xa5b#2\xef\x1b\x14~\x80\x8d\xe4Ӟ\xeaiץoͲm\xadWN\x19\x96\xe3\x0f*\xe8\x1c\n\r\xb0}\x9cZ\x13\xc1\xc9N\xce\v\xca\xc1\x06ɑR\x802.\xde\x17h\xb0\xbbƠVV8e\x0e\xa48d\xcbt\xa4\xe1\xc4E\xf8#+~\xe1\x18\x14\xee\xbdC\x18ܠA\x99a\x8c\x10\xe7*\x99\x89St\x12\xfa\x18\xe2i\xea\xe1L\xf4\x9c\x04\xfcay\x1f#fd\xb8\x81\xee\xc6\xfb^\xa0\x87\xbe\x8d\xc0\x92\xfb\x84ry\xef\x9b\xfbM\xd8\xcc\xc7\x0e\xa7\x80\x81\x16\x18*\xd26\x18\x83\x90\xd6!\xe3\xa06\x93\x1a\xe9m\x00\xe4`\x06\x9b\x15oC\xa4hB\xd21\x84\x13\xf5\xc0(F\t\x0e\xff^}z\x98\xffk\x8a\xf9\xf6\x14\xc0\xb2\f\xad\xf5\xf9\x1a+\x94\xeem\x9b\xb39Za\x90S\xe1\x82iŤؠui\xb3\a\x1a\xfb\xd3\xfb\x9f\xa7\xd9\x03\xf8^\x19\xc0'V\xe9\x12߂\b\x8c\xb7\xe1/ڌ\xb0\x81\x8eV#\xec\x85+\xc40i\xb5\f\x90u5\xc7\xde\xfb\xe3:\xb6EP\xcdqk\x84Rlq\x01o|%x\x84\xf9+9\xd6ooNh\xfdKp\xa07$\xf4&\x80k\xf3]\xd7#\x8f ]\xc1\x1c8#\xf2\x1c\x8f\x85\xe8\xf0\xf3\xc1\x9bBⷠ\f1 UG\x85WL\xb7\x17\xe2\x11\xf2\x11\xe8\x9f\xde\xff|\x12q\x9f/\x10\x92\xe3\x13\xbc\a!\x037Z\xf1oSx\xf4\xd6q\x90\x8e=\xd1NY\xa1,\x9ebV\xc9\xf2\x10\xaa\xfd\x1d\x82U\x15\xc2\x1e\xcb2\t\xf5\x06\x87=;\x10\v\xf1\xe2\xc8\xde\x18hf\xdcYk\x8dU\xc6㧻O\x8b\x80\x8c\f*\xf7\xf1\x8e\xb2\xd3FP\xd5@\xe5B\xc8y\xde\x1aGI3~\xb6\x0e\xe6\xe3\x14d\x05\x939\x86\xf3\"lj\xcaB\xe9\xcdK\xfcx\x9c\xfa\xe37Q\x02\f\x03ǟ\x96D\x9fy8_\xa9>\xe3pݷ\xd6\xd9\xc3m\xeb5\x1a\x89\x0e\xfd\xf9\xb8\xca,\x1d-C\xed\xec\\\xed\xd0\xec\x04\xee\xe7{e\xb6B\xe6\t\x99f\x12l\xc0\xce\xfd\x93y\xfe\x8d\xff\xe7\xc5g\xf1\xaf\xeb\xe7\x1e\xa8\xf7\xe8\x7f\xcdS\xd1>v\xfe\xa2C\xc5Z\xf1\xf9y\xecf\xd5\x140õ\xe4\x16\xfbBdE|\x0441\xf6\x843\t\xaa8y\b\xcdL\x1e^ݔ\x89\xd0\xda\x10\xa2C\xd2\xf4\xb4\x12&9\xfd\xdf\n\xebh\xfcE\f\xd6\xe2Y\xee\xfb\xe5\xfe\xee\x8f1\xf0Z\xbc\xc8WO\x14\xba\xe1{J\x8e\xb0\x92\x8a\xe9$H3\xa7*\x91\r\xa4\xa9\xf6\xbb\xe7D\xfcF\xa0\xb9P\xc5}\xee\t\xc7*t\xa2\x8ale\xae*#\xadd\xda\x16\xca\xdd\xdf]\xc0\xb1j\x05#\x86\xe3u5\xc5c\xd45h\x02]\x87\xc7\xfb\xcb\xc3\xe9@\xd2\a\u0557\x8eȔ\x11\xb9O[\xad\xef\xfbW\x84d\x15\xeb6\xff\xba_Ŵ\x162\xbf\nk\xb7\x97v\x01藎hDy\xa1\x9b\xe7\x8a)\x9c\xbd\x1e\xdf\x18-ʺ\x1aCI`\xab\xb4`\x13\xe3tG#\xfb\xa4\x897\xe3\xba\xe6\f\x13\xc1\x00.pд\x9e&\xdeQ\x8d\xfd\x84\xbaҏ\xd0\xdb\xc5[\xd1t@\xbe֮\xe8\xd9MEr\x1fa2\xfd:\x1c\xc8h\xc5gCҺ.9\x98<:\xd4p\xa2o\xab\x83\xd9^K\xb4{\x9a\xf1\xc3\xda\xf7ۮyZ\x87\x1e_\xc3{\x88\xf0.v\xfe\xe8y\xf3\xe2\xc7u\xa6\xe8\xe9\xd0k\xcf]\xb0\x81\xdb\xf1\n\xdf\xc92\xbc\xf1\tQ\xa1\x7f\xb1\x86\xf6\xe4\x9eٸ\xc9\xd4}CG_X\xea\xb3*\xa9C\xee\v{zwl\x98(\x91C\xfb+\x8bo\xa5[\xdfҹ\x99\xaac\xa3\xa2\xda\"\xf7qc\x02\xf4x]\xec\x92r\xe60!\x15#\tY\x97%[\x97\xb8\x00g\xea\xf1\xf4\x19\xf7\xaa\xd0Z\x96_\xf2\xaf\x1f\x83Tx\xf37K\x80\xadU\xed\xdaG\x7f\xe3h\r\x157\xb6\xb1\x82\xeb\x1a\x0f\x05\xb3\x97\xa0,If\xca\xe2Z\x97?orp&\x94=\xe0~btԵ\xeeN\xdeF\x13\x9a\x98\xfb\xde[\xc7U\x044\x1b]\xe2\xa0\x11\x83B\x95Ѻ\x95\xa3\xa4TWk4D\x84o\x95GFb\xe0\x98\xea\xa2\xf8\xd7בɣ\x86\x18\v\x83\xaa\xe6=\x991雊d\xbfN\x01\x17V\x97\xec0\xa17\x9e\xc4\x17Xd\xbe\xe4GG\x8b\x89^H\xee\xef\xe7\xae\xed\xfe\xb4?\x05L\x97\x7fS?,L\xddB\xf7W\x82\xc1|\xfb\x1b\xc8\xeb\xecp\xa6䳎\x19\xf7ܰ\xb7\xea\t_\x8ax^\xf5t\xbc놮q\xa0\xeao\xf3GƨI\xa2F\x83\x1e9\xef\xe8n:\xa7ݑz\xdd\xfe.\xb0\x80_\x7f\x9b\xfd?\x00\x00\xff\xffg\b\x17r\xc1\x1f\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc{j\xec\xf2\x9e!\xb2%b\x87\x04\xf8\x00Pc%\x95\xff\x9ej|\xf1\v$A\xcd\xe8\xad_p\x13\x054\x80\xeeF\x7f\x03\x9b\xcd\xe6\x86\xd6\xec;H\xc5\x04\xbf#\xb4f\xf0C\x03\xc7_j\xfb\xf4\xefj\xcbĻ\xd3\xfb\x9b'\xc6\xf3;\xf2\xa1QZT\x8f\xa0D#3\xf8\x05\x0e\x8c3\xcd\x04\xbf\xa9@ӜjzwC\b\xe5\\h\x8a\x9f\x15\xfe$$\x13\\KQ\x96 7G\xe0ۧf\x0f\xfb\x86\x959H\x03\xdcO}\xfa\xd3\xf6\xfd\xbfn\xfftC\b\xa7\x15\xdc\x11\tJ\v\tj{\x82\x12\xa4\xd82q\xa3j\xc8\x10\xe6Q\x8a\xa6\xbe#\xed\x1fv\x8c\x9bϮ\xf5\xd1\x0e7_J\xa6\xf4_\xba_\xffʔ6\xff\xd4e#i\xd9Nf>*ƏMIe\xf8|C\x88\xcaD\rw\xe43NS\xd3\f\xf2\x1bB\xdc\xd2ʹ\x1b\xb7\xea\xd3{\v\"+\xa0\xa2v=\x84\x88\x1a\xf8\xfd\xc3\xee\xfb\xbf}\xed}&$\a\x95IVk\x83\x00\xb76\xc2\x14\xa1\xe4\xbb\xd9\x1b.\xc0\xe0\x9a\xe8\x82j\"\xa1\x96\xa0\x80kEt\x01\x84\xd6u\xc92\x83\xea\x00\x91\x10q\b\xa3\x149HQ\xb5\xd0\xf64{jj\xa2\x05\xa1DSy\x04M\xfe\xd2\xecArРHV6J\x83\xdc\x06X\xb5\x145H\xcd\xb6\xa3{y݊\xfe`US\x11Z\x89&\xc1(\xb0\r\xf5\v\xabB\xf2\xd5Q\xe0\x992\x1d\xf2P&2\xa3\x05R\xa9.A\xa7\x92x\x0f\a\x14G\x99\xe0\x8a\xe5 }q\x80\xa5,\x13xp\x0f\x94\x95M,\xed\x13kk\xdd[\xfeQʋ\xbc\xdb/vd'\xdaX\x88\xe7>\x82\x92QP\xd0\x13\x10v L\x13\xe0\x19\xd2\x05\xa4\x15\xd9f\n\x87\f\x83\x9ad\xb6L\x13\xf0\u06007U\x1a\x026\xe6d3>\x1bL\xebv\xffDYy\r\xb2!\xe7]~4\xfe֎\xfe]\x8eF\x10*\xe9*l\x0f\xe4\x11h~\xf6\xe7\x83j\x8d.\xae\xe1\x01Adû\x12\xf1\n'c\x8d_\xe8V\xf1\x9a\x0e\x1f\xe3,\x81\xb0\x83<\x00\xd3]k\aA\\\xd5\xda\xc1\t\x82\xa2\xbb$\xa4\xb3\xeb\x01@U\xe9\rg\xb3\xf6\xc05+,\x9f=\xa0c\v\xb9\r\x96\xa1\xfatv\xb4-y\x9aH\x9fGw\xb7\xdetI\xa2\xaco=/Ʉ\a\xe5\t6\r\x7f\xe2\xe2\x99o\x8cw\xa9\x16\xe3\xf6\xc3\x15\xa4\x87\xab^uz}\xb1$\xfa=\xa5P\x9f_\xd3y\xca+\xf4+H\x99d\xbeY\xe5\xa1\xcfq\xc1\x92\\\xb3\xe5\xb4\x13\x7f.\xaebn\xfe\x99\xc1.\xf9\xf9\xc1\xd6\xc1\xa6\xd6X\xed\xe2\xa3:\x06\xc9s\x01\xba\x00\xe9\vl7\xa6\x968&\xa7\xdb\x1cik[\x87\xa2+\xe4\x1fo\x9e\xd9b\xc0A\x19V\xdc\xf8\xe6MY\xde\"cӦԶ$V6\x11&J*F\xda\vQ\x02\x1d\x96\xe7\xa6d\xf3\x97r\xf8\xfd\xba\xb4\x90C\xf7\x85i\xc2O\x12١\xa5\xa5\xad>\xed&\x88\xfb\xc9x\x13\x86\xf2+\xfd\x87\x97\xac%\xe4\xd9\x17\xb2\xeb\xf3\x85|s\xf8\x1a\xb3M\x17c-\x0f\xba~\xae\xc2\xf3\xe7B\x9f\x86\xeaK\xed\xce\xc1\xa4\x01\xda\xc7`dȠD\xc1Hnt#M\xb2\x9b\xb2\x98p\xc1S\xe8BT\b\xf1>3'Qx\xc0ʄ?\xddis\x15\xd7L\x91\xf7\xa4\x10M\xa4\xcck\x06;\vI\xff\xe9T\xbf\vl\x83\xa6\xa7\xf7\xdb\xfe?Z\xb8Ŀ\x89\xc6Dv\xf7\\\x84؊\xb1Vx\xceN,oh\xd9;d\x1d\xb6h\xb9\x87\bI8+c9?d+?\xbe\xc7F\xe4Kmc\xff\xab\xc5Ѽ\x89\x98V\x1fpqU@?\xeb?\xa1\xa4ֆ\xc1\xd3\xcb\x1f\xd3\xf3\xfe\xf3\x89\xfa5\xd9\xfea.\x7f\x12\xe8r\x8e?ź_\xc8\xe7_\x90\xc5O\xac\xe0zq\xd0>%O\x7fQv~\xb1\xc8)1'\xdf϶σ\\\x91\x89OB\xcer\xd6}u\xae\xdd\xe5\xb6g\xf7\x91\x9ca\x8f\xe4\xceg\x01O\xe6\xd5\xe72\xe6\xf3(\x8fd\xd3\xd3\xf3䳠M\x0e}9;\xfez5p\xaf\xe1\x05L\x8b\x9a\xc5\f\xf7\x8b\xbc\x84\x84\x1c\xf6\x9a\xcc\xf5\"\xc6.\xccR\x87,\xf4ļks\xd3\xfd\xdc\xf3\x04Д\x8c\xf4D\xc6y\x02\xe2l\x1e:5\xcf<\x01{A\xed\xcer\xc9\xec\x9fk\xf2\xcb\xc1\r\xf9\x95\xd65\xe3\xc71\x9f\xa4r\xd3,'\x8d\x92\xd3\xdd9{\xac\xd4\xf5\x16z~VlJ{S4\xe2\x93\xf9\xb0\x1e\xe3Zl\xc9=?\x8f\xe0\x9a2\xf5\xa8\x0fҿJ\x84\xcbzfeٽ/c\xc0vA\xb9\x9bg*\x1e\x19\xc0\x8eS\x16v\x94\x84B\xf6\xac\xe3%\x17\xecˠ{7P8om\xc7\fm\xa6\x8b\v\xad\xed\xaa)5\xab\xa3G\xbe\x96\xe2\xc4Lر\x80s\xc0\xe7߅\xb9\xa9\xb2?\x1bH_\x1e\xc3i\xdc\x0e\x1c\a\x1a;C\xcfP\x96\x84\xaa\xf1\xf63{Y3\x13\x1bs\xff\n)\xe9\xf9\xc1]\xea\xbc5'6\xe6\xb1s\x7f\x8d\xb0B0\xe6§\x8aDD&uѼ=lMw\xf3\xed\xb7\x06䙈\x93\xc93;\x03i\xa1\x14\xdc\xca\x15\x85\xfe\x9b\x97tN\\\xda+\xc2\x03?\xa1\x95/\xe4\x9e[\x8d\x1d\x05;X\xa3\x81\x83\"\xae\xf5\x8dP\x9a\xa3\xdb3\xd15\n\x95\x8b0:\xce\x0f\xb3\x8a)\xb5\x8e\xfa\xba\x9e\xd2z_i\xd1J\xb9\x8a\xbft\xb9\xc74\x032\xb5.:-'\xb2X\a}-\xcfi\xc9wJ6\x1a\xd3ꜯQ\u07fc\xa2\xaey\x85\x0f\xb5\u038bJFSJ\xfd\xf2U|\xa9+zS\xd7\xf0\xa7.\xf3\xa8\x16@\x0e\xea\x92S*\x8e\x93\xd2x\xc99\x9b\x94,\xdbr\xe6x\xbe\x928\xa1\x828!\x1b\xb4\xb4҄J\xe1u\x15\xc2\t8\xbc\x92\xafu%o\xeb\x1a\xfe\xd6u=\xaeE\x9fk\x91s\x16\xfe^W\xd9{q\x92\xc1\xa7\xa3?\x8b\x1c\x1e\x84\xd4K\x0e\xc2ð\x7f$\x05\xd8q\x9aD\x99\x13\xee\xbb\xc62\rh\xfb;\xbb\xff\xb2Mųu\xde\xfc\xfdU丶\xa5\xdc\xc2\xe3\xa0\xfb\xe8Z\xe7\x01$p\xfb\xd8\xc1\x7f}\xfd\xf29\xc0\x8f٣\xce\xe8\x1dܳ\xb7\x06F\xee\x90\xe3\xb2O\xae\xe0\xc6b\xcb\xe8\xf0WN\x12К\xfd\xa7yGj9 s\xff\xb03]\xbd\xb5dޟ\n\t\xfd\x90{\xdb\x03j\x8f\x80\x91I\xee\xdf\x1dz\x10#\xa5\x90\xe1'1\xaf\xf8x\xedņ\xb4\xf4\xcd\x16!\xe1\xa9{\xd8\xd9\xd5m\xc9'4\xdd\xf8\x99\b\xcbx\x05\x93\xf9\xa6\xa6R\x9f\rw\xa8۰\x86頌\xd7!s\xa1\x93IQ;~\x9f(\x8a[\xffL\x91I\xc0\x9d\xeb~6s\x88\xd1K\xd61]ѿX\xcb\xff\x8a\xeb\x98V\xc7\x1b\x83\xa9\xc8\xe7h\x05ī\x85\xa4\x9c\x18z\xf8\xbe$\xd6\x1eC\xc7yy\x86\x9e\xac\x0f\xebD\xf0\x83\xe3\x8dHS\x9c֪\x88D\x85^&\xd3\xcc\xe3I\x9a\xea&q?\xb6ooK,+:\x02\xe8\x19\xbc\x88\x92\x9d\xa7\xe4\x06k³j\x01\x195lLh\xce\xcaێc\xfe\xfb\xa4<\x13\x9f\xaa\xb8\xf8\x91\n\x8b\x9e\tQa\"M(\xc6\x93Am\x91\x98y\xe8R\xb3\xe0\xa5LH#\x9cyU\xe9W]P\xb5$.\x1f\xb0O\xb8\xd6\xd59\x94AR>N\xac)~\xcddC>\xc3s\xe4\xeb'\xc3\xf4&\x0e\x1a?J\x1b\xb2\xe3\x0fR\x1c%\xa81Ko\xcc\x1d\x0fƏ\x9f\x84|(\x9b#\xe3\xa1\x04o]\xe7\a*5\xa3ey\xb6뉌\xfd\xe0\x0fs\xe4\xbf\xe5\xd1\x13\x7f\xcc\x11\xc9\xedy1(`\xbb\xb51\x15\xc6\xedA7\x97\xaa\xf6\xa2\xd1\xddS\xf1V\xb5\a&\xe6@;h[\xf2Yh\xf0\xb1z\xd6\a\xca\xd0yVz\x03\x87\x83\x90\xda\xc6p6\x1b\xc2\x0eNP\xc7b\r\x94\x95\xc6ְﮢ\x01\x12\xaaK\x82\xe6s\xfe\xa44\xa7\xc2\x18)\x15=[\xb7\x94fY\x83r\xe0\x9d\xd24\xa6\xd0^d\xda\x1a\xe3\xc6qs\x8aG\xb9\xeb\xf6\x0f.]S\xedA\x9aK\x1d\xf8\xb7E\x9d\xb9\xefeEP4OI̭\xb0\xceuS\xa2\xf08\xc7\xc3js\xc2\xc7\xfc/4-wӆZ\xffBB\xe8\x1c|c\xfc2\xdeF\xef\x85\xc9\xe9ZD\xa6\xfcP\xa4YVP~D\xf6\x91\xa29\x16\x9e\x05\xa7$\xf5T\x18\xb511\x9fڜT\xe5s^\xba\x91\xbc\x13\xb2uY\xb0\xbc]\xee\x1c\xd0y\x14\xceؙ\xb2\xad\xc8meƽ\xbd\xab\x15㙘\xb531x\x02\xff\xb1\x80R\x18Bՙg\xf3e\xc26x\xc4fn\x03\xcd!#\xba\xdf \x01/\xd9o\x18\x9c\xbe\xdf\xd6\xea-ϭ-\xb5f\xf3\xd3v\xf6+\xa0Ê\xf4KpaGN\x1d<\xb3\xbf\xc8\xcaW\x91\xdbE\x1b\x80\xa3\x81i\x8aAF1\r\xe2Ͷu\xb8P=+s\xc9\xe9\xeau~\x995m&F[\xfa終O\xc1\x8c\xf9\x98b\x0f\x7f\x1ft\x1fܹ@˸\x85\xe8l\xd8\br\xfe\x99\x1d\xfcS\xfd\xfb\x12\xfee\xd4\xe3w\xbe;\xf1L%g\xfc\xb8\xb4\xf9\xbf\xb9n\x11w\xc0A\x888\x04\x91M\x04\x17a\x95C\xe0\x179\xf1\x1aup\x12^\xe0\x12D\xd5\xc9\xe8\xa3a伃d7\x93\xfb\xf2\x7f\x01\x00\x00\xff\xffp}\xdc\x0f@c\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc{j\xec\xf2\x9e!\xb2%b\x87\x04\xf8\x00Pc%\x95\xff\x9ej|\xf1\v$A\xcd\xe8\xad_p\x13\x054\x80\xeeF\x7f\x03\x9b\xcd\xe6\x86\xd6\xec;H\xc5\x04\xbf#\xb4f\xf0C\x03\xc7_j\xfb\xf4\xefj\xcbĻ\xd3\xfb\x9b'\xc6\xf3;\xf2\xa1QZT\x8f\xa0D#3\xf8\x05\x0e\x8c3\xcd\x04\xbf\xa9@ӜjzwC\b\xe5\\h\x8a\x9f\x15\xfe$$\x13\\KQ\x96 7G\xe0ۧf\x0f\xfb\x86\x959H\x03\xdcO}\xfa\xd3\xf6\xfd\xbfn\xfftC\b\xa7\x15\xdc\x11\tJ\v\tj{\x82\x12\xa4\xd82q\xa3j\xc8\x10\xe6Q\x8a\xa6\xbe#\xed\x1fv\x8c\x9bϮ\xf5\xd1\x0e7_J\xa6\xf4_\xba_\xffʔ6\xff\xd4e#i\xd9Nf>*ƏMIe\xf8|C\x88\xcaD\rw\xe43NS\xd3\f\xf2\x1bB\xdc\xd2ʹ\x1b\xb7\xea\xd3{\v\"+\xa0\xa2v=\x84\x88\x1a\xf8\xfd\xc3\xee\xfb\xbf}\xed}&$\a\x95IVk\x83\x00\xb76\xc2\x14\xa1\xe4\xbb\xd9\x1b.\xc0\xe0\x9a\xe8\x82j\"\xa1\x96\xa0\x80kEt\x01\x84\xd6u\xc92\x83\xea\x00\x91\x10q\b\xa3\x149HQ\xb5\xd0\xf64{jj\xa2\x05\xa1DSy\x04M\xfe\xd2\xecArРHV6J\x83\xdc\x06X\xb5\x145H\xcd\xdf\x12\xa6\xfd\xd7%\x88\xb4,;\xf3\xff\x81\t\xb3\x9e\xe3wÑ\xaf\xca\xf1\xb3TY\x82\x88T\t\xd3\xff\x01\x89b\x94\xc5W\xa7+\x92\t\xf2\xd7\xee\xa8[\xc2\x0e\x81 \xf9-9\xb0R\x83\x1cP\xe6E\xe7\xe55\x90\x91\xa2\xef\xb0UTg\xc5\xc7\x1fh٨6Δ\x88\x97\xe1`k\x12{\x1f\xa1\xaf\x98\x17\xe0\x12\xe3\xbe2\t\x95u\x8b\xbf\x19l\xb6_\x8c?q\xff\xf9\x17\xc8\xe7\xd0C\xd28o\xb4\x91\xfb\xc1b\xbbS;;?u\x1b\xce\xf4\t>\x93\rx\xdc\x12J\x9e\xe0l-\x16\xca\t\x12\x87jc\xefF\xbd\xa71rL\xe4\xc50\xd9\x13\x9c\r\x18\x17JY\x1c\x9d\xca\n\xb6=A\xc4\u070f\xb5\x1e\x02qM\xce\xc1\xb5\x98\xc4\x0f\x06\x11\xc6\xf1NG\x1e1a1/\x8b\x967G\xd2\x05\x89o\x1e\xf7\x17l3\x90\xad\x13>4\x84}\xab,\x89\xf0\x14\x14\xacNܨ\x89\x1e*0\xa7\xc5\aƾӒ\xe5a\"\xcb\xf7;>m\r\xf7\xdbg\xa1w\xfc\xd6zd\xcap\xc9/\x02\xd4g\xa1͗\xab\xa0\xd3.\xfc\x02dځ\xe6xq+\xb6\x11\x0f\xdd\b[\x02s۶\xb3\x81\x94@\x1e\xa6Ȏ\xa3\xe3\xe2\xf0a\xe2\xa5v\xbay\xfd\xd0oU\xa3L\b\x8d\v\xbe1\xaar\x1b\x9b\xc9\";\x11\xa4\x90=\x8a\x8c\x97\x16&\xb5\x13&\x82\xfd\x86\x9aĎ\xb7\x11\xe0\x92f\x90{o\xd3\xc4-\xa9\x86#\xcbH\x05\xf28\xa78\xba\xadF\xf9\x9e\xb6\x84D\xa9k\xdbJ\x0eKS\xed\xbe9ѝ//f\x83'7\xa1\x97'\xf6b\u05c9p\xe5t\xd7\xe5\x1d\x19\x15k\xec\x8fE\xec\xd2<7)$Z>\xac\x90\xf8+h1\xd6\xfdvaVCV\xb4\xc6\xf3\xfb?\xa8\xe6\fC\xff/\xa9)\x93\tg\xf8ޤ\x89J\xe8\x8du\x81\xb1\xee48\x03S\x04\xe9{\xa2\xe58\x10\x1eٜ@\xd9\x02\xa5U\xe4\xe20\xb2Xn\xc9s!\x94թ\a\x06e,d\xd3oL\x917Op~s;\x92\x03ov\xfc\x8dU\xf0\xab\xc5M\xb0\x16\x04/\xcf\xe4\x8d\x19\xfb\xe6%FP\"'&v\xfb\xb1y\n!\xb9ME\xeb\x8d\xe3^-*\x96M\x8e\xe3\xd1\xf0x\xdbz\xec\xd4\r\x91\xb7\xb1qg\x1e\xcf\xed6\x89\x7fk\xa1\xf4\x9fま\x89\xf5<\xf8\x11}\x9b6\x12/[\xb4\xf5]\xec+\bc\xb4\x00\x0f\x1a\xa4\v\xfeY\x01\xed=\x87\x17\xfaTK\xc1\xbd\x10أ! \x8b\b^\xe0&\x9b*IY\xe2\x1ak\x13\xf1\xb2\xd2N\xff\xf8\xa3\x13\x9bē\x8d\xbf\xbb\x1bymk8\x13UE\x87\xc9\xc1\xa4\xa5~\xb0#=O;@\x96\xfa\xf2ؘ\xf3\x9cn&z\x1e2i\xc1g\xa6\v\xc6\t\xf5b\x03\xa4c(Jj\xb1,\xc1l+\xa8\"{\x00\x1eb\xea?\x83\x9e\xaf\x18ߙ\t\xc8\xfbW\xb7\vH\x8b\xae\x8b\xc8\xe9Q\x1d\b\x1a>\x18M\x95jR\x89\x9c<\x17 \xa1\xc7\x15\xe3@9Z\x9a\x89 \xb9\xd0\xddx\x04\u00adE\xfeV\x91\x03\x93Jw\x17\x9a\xcap\x8dJe\x87\x95\x14\xc6\xdd}c\x15\x88F_@\x83\x8f\xed\xe8^^\xb7\xa2?X\xd5T\x84V\xa2I0\nlC\xfdª\x90|u\x14x\xa6L\x87<\x94\x89\xcch\x81T\xaaKЩ$\xde\xc3\x01\xc5Q&\xb8b9H_\x1c`)\xcb\x04\x1e\xdc\x03ee\x13K\xfb\xc4\xdaZ\xf7\x96\x7f\x94\xf2\"\xef\xf6\x8b\x1dى6\x16⹏\xa0d\x14\x14\xf4\x04\x84\x1d\b\xd3\x04x\x86t\x01iE\xb6\x99\xc2!à&\x99-\xd3\x04<6\xe0M\x95\x86\x80\x8d9ٌ\xcf\x06Ӻ\xdd?QV^\x83l\xc8y\x97\x1f\x8d\xbf\xb5\xa3\x7f\x97\xa3\x11\x84J\xba\n\xdb\x03y\x04\x9a\x9f\xfd\xf9\xa0Z\xa3\x8bkx@\x10\xd9\xf0\xaeD\xbc\xc2\xc9X\xe3\x17\xbaU\xbc\xa6\xc3\xc78K \xec \x0f\xc0t\xd7\xdaA\x10W\xb5vp\x82\xa0\xe8.\t\xe9\xecz\x00PUz\xc3٬=p\xcd\n\xcbg\x0f\xe8\xd8Bn\x83e\xa8>\x9d\x1dmK\x9e&\xd2\xe7\xd1ݭ7]\x92(\xeb[\xcfK2\xe1Ay\x82Mß\xb8x\xe6\x1b\xe3]\xaaŸ\xfdp\x05\xe9\xe1\xaaW\x9d^_,\x89~O)\xd4\xe7\xd7t\x9e\xf2\n\xfd\nR&\x99oVy\xe8s\\\xb0$\xd7l9\xedğ\x8b\xab\x98\x9b\x7ff\xb0K~~\xb0u\xb0\xa95V\xbb\xf8\xa8\x8eA\xf2\\\x80.@\xfa\x02ۍ\xa9%\x8e\xc9\xe96G\xda\xda֡\xe8\n\xf9Ǜg\xb6\x18pP\x86\x157\xbeyS\x96\xb7\xc8ش)\xb5-\x89\x95M\x84\x89\x92\x8a\x91\xf6B\x94@\x87\xe5\xb9)\xd9\xfc\xa5\x1c~\xbf.-\xe4\xd0}a\x9a\xf0\x93Dvhii\xabO\xbb\t\xe2~2ބ\xa1\xfcJ\xff\xe1%k\ty\xf6\x85\xec\xfa|!\xdf\x1c\xbe\xc6l\xd3\xc5X˃\xae\x9f\xab\xf0\xfc\xb9Ч\xa1\xfaR\xbbs0i\x80\xf61\x18\x192(Q0\x92\x1b\xddH\x93\xec\xa6,&\\\xf0\x14\xba\x10\x15B\xbc\xcf\xccI\x14\x1e\xb02\xe1Ow\xda\\\xc55S\xe4=)D\x13)\xf3\x9a\xc1\xceB\xd2\x7f:\xd5\xef\x02۠\xe9\xe9\xfd\xb6\xff\x8f\x16.\xf1o\xa21\x91\xdd=\x17!\xb6b\xac\x15\x9e\xb3\x13\xcb\x1bZ\xf6\x0eY\x87-Z\xee!B\x12\xce\xcaX\xce\x0f\xd9ʏ\xef\xb1\x11\xf9R\xdb\xd8\xffjq4o\"\xa6\xd5\a\\\\\x15\xd0\xcf\xfaO(\xa9\xb5a\xf0\xf4\xf2\xc7\xf4\xbc\xff|\xa2~M\xb6\x7f\x98˟\x04\xba\x9c\xe3O\xb1\xee\x17\xf2\xf9\x17d\xf1\x13+\xb8^\x1c\xb4O\xc9\xd3_\x94\x9d_,rJ\xcc\xc9\xf7\xb3\xed\xf3 Wdⓐ\xb3\x9cu_\x9dkw\xb9\xed\xd9}$g\xd8#\xb9\xf3Y\xc0\x93y\xf5\xb9\x8c\xf9<\xca#\xd9\xf4\xf4<\xf9,h\x93C_Ύ\xbf^\r\xdckx\x01Ӣf1\xc3\xfd\"/!!\x87\xbd&s\xbd\x88\xb1\v\xb3\xd4!\v=1\xef\xda\xdct?\xf7<\x014%#=\x91q\x9e\x808\x9b\x87N\xcd3O\xc0^P\xbb\xb3\\2\xfb\xe7\x9a\xfcrpC~\xa5u\xcd\xf8q\xcc'\xa9\xdc4\xcbI\xa3\xe4tw\xce\x1e+u\xbd\x85\x9e\x9f\x15\x9b\xd2\xde\x14\x8d\xf8d>\xacǸ\x16[r\xcf\xcf#\xb8\xa6L=\xea\x83\xf4\xaf\x12ᲞYYv\xef\xcb\x18\xb0]P\xee晊G\x06\xb0㔅\x1d%\xa1\x90=\xebx\xc9\x05\xfb2\xe8\xde\r\x14\xce[\xdb1C\x9b\xe9\xe2Bk\xbbjJ\xcd\xea葯\xa581\x13v,\xe0\x1c\xf0\xf9wan\xaa\xec\xcf\x06җ\xc7p\x1a\xb7\x03ǁ\xc6\xce\xd03\x94%\xa1j\xbc\xfd\xcc^\xd6\xcc\xc4\xc6ܿBJz~p\x97:o͉\x8dy\xec\xdc_#\xac\x10\x8c\xb9\xf0\xa9\"\x11\x91I]4o\x0f[\xd3\xdd|\xfb\xad\x01y&\xe2d\xf2\xcc\xce@Z(\x05\xb7rE\xa1\xff\xe6%\x9d\x13\x97\xf6\x8a\xf0\xc0Oh\xe5\v\xb9\xe7VcG\xc1\x0e\xd6hࠈk}#\x94\xe6\xe8\xf6Lt\x8dB\xe5\"\x8c\x8e\xf3ìbJ\xad\xa3\xbe\xae\xa7\xb4\xdeWZ\xb4R\xae\xe2/]\xee1̀L\xad\x8bNˉ,\xd6A_\xcbsZ\U0009d48dƴ:\xe7k\xd47\xaf\xa8k^\xe1C\xad\xf3\xa2\x92єR\xbf|\x15_\xea\x8a\xde\xd45\xfc\xa9\xcb<\xaa\x05\x90\x83\xba䔊\xe3\xa44^r\xce&%˶\x9c9\x9e\xaf$N\xa8 N\xc8\x06-\xad4\xa1Rx]\x85p\x02\x0e\xaf\xe4k]\xc9ۺ\x86\xbfu]\x8fk\xd1\xe7Z䜅\xbf\xd7U\xf6^\x9cd\xf0\xe9\xe8\xcf\"\x87\a!\xf5\x92\x83\xf00\xec\x1fI\x01v\x9c&Q\xe6\x84\xfb\xae\xb1L\x03\xda\xfe\xce\xee\xbflS\xf1l\x9d7\x7f\x7f\x159\xaem)\xb7\xf08\xe8>\xba\xd6y\x00\t\xdc>v\xf0__\xbf|\x0e\xf0c\xf6\xa83z\a\xf7쭁\x91;\xe4\xb8\xec\x93+\xb8\xb1\xd82:\xfc\x95\x93\x04\xb4f\xffiޑZ\x0e\xc8\xdc?\xecLWo-\x99\xf7\xa7BB?\xe4\xde\xf6\x80\xda#`d\x92\xfbw\x87\x1e\xc4H)d\xf8I\xcc+>^{\xb1!-}\xb3EHx\xea\x1evvu[\xf2\tM7~&\xc22^\xc1d\xbe\xa9\xa9\xd4g\xc3\x1d\xea6\xaca:(\xe3u\xc8\\\xe8dRԎ\xdf'\x8a\xe2\xd6?Sd\x12p纟\xcd\x1cb\xf4\x92uLW\xf4/\xd6\xf2\xbf\xe2:\xa6\xd5\xf1\xc6`*\xf29Z\x01\xf1j!)'\x86\x1e\xbe/\x89\xb5\xc7\xd0q^\x9e\xa1'\xeb\xc3:\x11\xfc\xe0x#\xd2\x14\xa7\xb5*\"Q\xa1\x97\xc94\xf3x\x92\xa6\xbaI\u070f\xed\xdb\xdb\x12ˊ\x8e\x00z\x06/\xa2d\xe7)\xb9\xc1\x9a\xf0\xacZ@F\r\x1b\x13\x9a\xb3\xf2\xb6\xe3\x98\xff>)\xcfħ*.~\xa4¢gBT\x98H\x13\x8a1\xcf\v-^.Hv.\x9ap\t\xf7\xc9\xe7\xed\xce\xc4W\x0e.~\xdf`\x19Y\x11DM=m\x90\xf2|\xc1?\x14\x9f3\"Ie\x05\xe4M\t\t\x8f\x8e}\xedt]~v\xcc\x03\x8e\x9dI\xd1\x7fx\f\xf1\xdaQ\xafh\xf0\xf6\x1f8sHw\x90\x91<\x11\xa8]\x90ֲ\xb7/!ehǫ&\xcb@\xa9CS\xfaj\x99L\x02Ր\xfb\xee\xd1jq\xbf\x87\x15e!q-\xb2\xe9<\xedv\x93@\x19\x15\x11\x933\"2\xa3\xb5n\xa4c\xf3\xac\x91\xd2l\xd9\xfe'\x0e\xa3\xe7\xe8z`\xa7\x85\x96+gt\xc58J\xd3*b\x89\r/'\rG\x98G\x1fe\xde)\xdf\xe9\xbe\xda\x15\xaarbam\xaaBEe\xbe\xed\xc0\xb6`\x8c\xf1\x83\xa0!'p\x02N\x047\xf7\x1f \x9f+\xdf\xfdf\xc2f\xf2\x04\xf2\xad\npLA\x11ڊ_5\x95:,}\xcc\x11\a!+\xaa\xefHN5lp\xf4e\x1a2\xfe\xa2\x9e\x94\xcb\x19\x0es\x91¹\xc1\xe6\xf6\x83!oY\xba\xeb\x0f\x15(E\x8f\xde|\x7f\x06\t\xe4\b\x1cQ<\xf7hX{\x83ĝ\xe0Pw\x86آ\x99n\xa8\x9b\xc0jʐ\xfb\x89\xe5\r\xecK\x94\xc6#8N\x9e\x1b\xc65\x1cGY\x17w{\xe5\x11\xa8\x1a\xbe\\:Bħn_\x173\xb38\xb0\xcfdP[$f\x1e\xba\xd4,x)\x13\xd2\bg^U\xfaU\x17T-\x89\xcb\a\xec\x13\xaeuu\x0ee\x90\x94\x8f\x13k\x8a_3ِ\xcf\xf0\x1c\xf9\xfa\xc90\xbd\x89\x83Ə҆\xec\xf8\x83\x14G\tj\xcc\xd2\x1bsǃ\xf1\xe3'!\x1f\xca\xe6\xc8x(\xc1[\xd7\xf9\x81J\xcdhY\x9e\xedz\"c?\xf8\xc3\x1c\xf9oy\xf4\xc4\x1fsDr{^\f\n\xd8nmL\x85q{\xd0ͥ\xaa\xbdht\xf7T\xbcU큉9\xd0\x0eږ|\x16\x1a|\xac\x9e\xf5\x812t\x9e\x95\xde\xc0\xe1 \xa4\xb61\x9c͆\xb0\x83\x13ԱX\x03e\xa5\xb15컫h\x80\x84꒠\xf9\x9c?)ͩ0FJE\xcf\xd6-\xa5Y֠\x1cx\xa74\x8d)\xb4\x17\x99\xb6Ƹqܜ\xe2Q\xee\xba\xfd\x83K\xd7T{\x90\xe6R\a\xfemQg\xee{Y\x11\x14\xcdS\x12s+\xacsݔ(<\xce\xf1\xb0ڜ\xf01\xff\vM\xcbݴ\xa1ֿ\x90\x10:\a\xdf\x18\xbf\x8c\xb7\xd1{ar\xba\x16\x91)?\x14i\x96\x15\x94\x1f\x91}\xa4h\x8e\x85g\xc1)I=\x15FmĻ6'U\xf9\x9c\x97n$\xef\x84l]\x16,o\x97;\at\x1e\x853v\xa6l+r[\x99qo\xefj\xc5x&f\xedL\f\x9e\xc0\x7f,\xa0\x14\x86Pu\xe6\xd9|\x99\xb0\r\x1e\xb1\x99\xdb@sȈ\xee7H\xc0K\xf6\x1b\x06\xa7ﷵz\xcbskK\xad\xd9\xfc\xb4\x9d\xfd\n\xe8\xb0\"\xfd\x12\\ؑS\a\xcf\xec/\xb2\xf2U\xe4v\xd1\x06\xe0h`\x9ab\x90QL\x83x\xb3m\x1d.T\xcf\xca\\r\xbaz\x9d_fM\x9b\x89і\xfey\xad\xe0S0c>\xa6\xd8\xc3\xdf\a\xdd\aw.\xd02n!:\x1b6\x82\x9c\x7ff\a\xffT\xff\xbe\x84\x7f\x19\xf5\xf8\x9d\xefN\xcc\x0f\x0f\x90\x00\x8cHT\xf1ڐp\xb4\xado\xbb\x9f\x1c\x80\xadR\x05py\xd1V:\xbcw\xb2\x97\xed\xa1\xe4\u05fe\xb2\xaa@~\xb8\xdd|\xfb\xf7\xbb\xdeg6\x90%O)&\f\xe3\xec\x1bM\f\xa6\xfdLev\xcf-Ӏ\x9c\ai\xb1F\xa5a\x15\xa8\x9b7 \x19S\x9aU\xa0\x85\xcaE\x16\xb8B\x8d\xcd^\xd5Eζ\x80\fZ7\r*\xad*\xd0V\x84\xa9\xe7JG\xa3t\xbe\x0e0\xfe\x01\a\xe5j9I\x04C\xc2\xe7'\x14\xe4\x9e\x0en~\b\xd3\xe2OL\xea\x01fX\x89K\xa6\xb6\xbfBf\xd7\xec\x0e4\x82\tXgJ\x1e@#\x052\xf5 \xc5\xff6\xb0\rJ\xbd%a\xb4\xe0\xf5A[h\x02K^\xb0\x03/j\xb8b\\\xe6\xac\xe4G\xa6\x01{a\xb5\xec\xc0\xa3*f\xcd~Q\x1a\x98\x90;u\xcd\xf6\xd6V\xe6\xfaݻ\aa\x83&\xcdTY\xd6R\xd8\xe3;R\x8ab[[\xa5ͻ\x1c\x0eP\xbc3\xe2a\xc5u\xb6\x17\x162[kx\xc7+\xb1\"\xd4%i\xd3u\x99\xffS\xe0\xa8\xf9\xa1\x87\xeb\xc9|s\x85\x14\xe1\x04\aP#:\x81qM\xdd(ZB\xe3'\xa4\xce\xd7Ow\xf7]a\x12fH}\xa2{G\xc2Z\x16 \xc1\x84܁\x9f\xd1;\xadJ\x82\t2\xaf\x94\x90\x96\xfe\xc8\n\x01rH~SoKa\x91\xef\x7f\xa9\xc1X\xe4՚ݐyA9\xac+\x9c\x81\xf9\x9am$\xbb\xe1%\x147\xdc\xc0\xab3\x00)mVH\xd84\x16t-㰲\xa3Z\xe7\x87`\xde\"\xfc\ns\xfc\xae\x82\xac7e\xb0\x9d؉\x8c&\x06i\xcfF\x05\f4\xa8+㳖~!55\xfc:\xc0\xc3\xe9\xb2\xd0+\x18\xb4\x1fvO\x1cn\xcd\x18ʕ\x83\x86:E\xaa!wǴ`\x87\x12\x1e\xca\f&}\xad\x97j\xdfN`2\xaf\xea\xd6\x11\x1cO\xb8J?AY\xa1ژA\xf1\xdeWC\x14\x91>y\xe35\x05\xc3\x1fԬ\xf2ڕ\x9d(7\xean\x0fȷ\x83Ƚ\xf6:\xe1*\x9b\xe4,\x96̈;\xc9+\xb3W\x16m\x9c\xaa\xedX\xad\xc1\x00n\xee6\x83F\x1d\xce#VdÉ\xd1V\xb1'.N9\xed\n\xca\xe5\xcd݆}C\x97\b\x02L\xe6,9\xb3\xb5\x96\xa4\x8e\xbf\x02Ϗ\xf7\xea\xcf\x06X^\x93V\nv\xf9*\x02x\v;\x9c\xf4\x1a\x10\x066\x00\xadq\x0e\x18BM\xd5vM\x0eG\x0e;^\x17\xd6+9a\xd8\xfb\x1fY)dm\xe1\x94\xefl\x9a\xf7D$ny\xa9\x0e\xa0\x13h\xf8\x91[\xfe\v\xd6\x1d\x90\x0ea0\x02\xe2\xd9Od\xdc\x1e#\x03\xc5&['\xa9l\xb3\xeb@\x15\x86]^\xe2<\xbbt.\xf1啫[\x8b®\x84\xa4~\"0]\xefO\xa2(B\xff\xe7Q\xc3\x11\xd7\xf1\xd6ܫ\x9f\x8c\x13\xeb\x14\xe2D\x9a\x8e(\x98J\xe5\xec@\xf5b2&\n`\xe6h,\x94\x9eR\x1dυ\x88Kڱ(<\x18öǀ\xfb\xf8\xb8e]\x14|[\xc05\xb3\xba\x1e\xefvJ\x91\x8d\xd1\xe6+\x18+\xb2\x04\xca\\\x0eI\xe3Z\x8e\x10F\xd3\x0f\x11\xa2\f(\x80.\x0f\x7fD\xb7\xdbS\b}\xa7\xa2\xe8\x10w\x9e*\x8c\xfd\x8fd\x1f\xd1\xdcgh\x84\xaf\xbdq\x17P\x90C!\x15+\x94|\x00\xedzD\xc7)H\x98\x06\x94\xb8<\x02\x15-\xad\x86\x02]\x06\xb6\xab\xd1\b\xaf\x19j\x82\xa8\x8c\bi,\xf0|}\xf9Z̃\xefYQ\xe7\x90\xdf\x14\xb5\xb1\xa0\xef0\x04\xccC\b<\xaae\aL\xfc4\t\xc0\xbb_\x85\xc8\x00\xf9\x90\xb9J+\x8a4cDj=\xb1c\x05.\xf0E\xa6zL[\x17\xab\xa3*\fX\xacr\xf9\xaf\x971%\x8a\x12\xd0\xef\xbdߏa\\CC\x8d\x9eF\x8d@l\xf4,\x94\x95=\x8eˑ\xb0PF\x888\xabr\x16\xb0\x97k\xcdǔj\x18N\x13џ\xcf\xde\x18\x88\x01\x83e\xa8\xf6\x1b\xb1x\xd8\xff?\"\x93\xcfb\xab\xa1u,.$\xb2\xb3\x10\xc6\xf6\xb89\f\x88\x1a\xcc0vF\x9ab\xd0\"\xa4\x83\x89ʭü\xdf3\xcdΙ\t1\xd1o$͋\xf3\x9eDŽ\xea\x0fH\xb0\xbdR\x8f)D\xfao\xac\xd7\x06\xca,\xa3%U\xb6\x85=?\b\xa5\xcdp\xb5\x05\xbeCVۨ\x9e\xe0\x96\xe5b\xb7\x03\x8d\xb0h\x81\xb0YO\x9c\"\xd6t\x98\xc0:\n(Za0\xae\x96\xe9\xc8<\xa2Fl(\x14\x8eE\xa12B\x1c\xbdx\xb2\xee\xb98\x88\xbc\xe6\x05\x19z.37>\xde\xe0\x17sOf\x04\xe2\x04\x7f\xe7N\x84Q \x97zQ\xb6\x92\x80\xeeu\xa9t\xcc\xf3t\xe5\x14L\x9c\f[N\xc1q,$m\x8b\xae\v0\x1e\x15\xe7\xc0\xb6z\xe7\xaa\xe5\x94[\xa0*\xf8\x16\nf\xa0\x80\xcc*\x1d'O\x8a\x10\xb8\x92\xaa?#\x94\x1dѤ\xfd hV\x89\xb6\x05\x03̽\xc8\xf6\xce\xddD)#X,W`Hc\xf0\xaa*\"V\xa8-\xb3\x92\xe1;\x9bS\x1amIP\x1fC\xb81EҖD\x1dܖ\x19mܧz#6oD\xef\xa1)\x9f%웓\xe6//\xecHn\x01\x86\x9c>\U000bab98\xb0\xe1k\nԞ\x1fh\xfe\xce\x18w\xdel\xd9\f[\xbf\xf8ly\x11\xae5h\xfc\x9d0\x8d\x8c՝\xb7U\x8b\x18\xf6s\xb7\xe5\x15\x13\xbb\x86a\xf9\x15ۉ\xc2\x02\xf9Rs\x88v\x1c\x9dYν$\x81Rm/\x96\x92\xdbl\xff\xa9Y\xd6Nh1\xa0\xd5\x10\x80\xf3\xcbC\fC\f<\x9d.\n4\xc0$\x90\x9dA\x91\x9b\xd6\xc4xn?\xef\x8aq\xf6\bG\xe7Y\x8d.\x0f\x8d\x15d-o@j\xa0\xcdER#\x8fp$P~\xb70\t\xde\x12Qq\xe5\x11\x8e\xa9U\aDE\xfc\xfc>\x85\xa3.~\xa0Q\xa4L\xa5\xb64D\xf5s\x87Y\x956X\xb6L)\x85\x12(~\xe6\xb0\x1b\x86\xf5\xb6\xc8\x1f\xe1\xf8\x83q\xec\xc3Y\xb3\x17\xd5\x02\n\xa0¦%\x19\xb5k\xf6\x86\xbf\xf1B\xe4Mg4O\x16@\xdc\xc8+\xf6YY\xfc\xe7\xd3wa\x10E\x99\xb3\x8f\n\xccge\xe9˫\x92\xd8\r\xe2L\x02\xbb\xc64-\xa53\vH\x97E\xfd\xb78\x90\tE\x11m\xd8&\f\xdbH\x8c\xcf\x1c}\x96\xb0i\x0f\x019\x87VY\x1b\xda]\x96J\xaeܒ\x96\xefm\x01\xd0.^\x9eUJ\xf78u\xb5\x10\xe2(\x8a\x1e\xbd{\xb4V\ue5d3}\xf9\xa9\xa2\xa1*x\x06y\xd8e\xa3$\x00n\xe1Ad\xac\x04\xfd\x00\xacB\xbb\x91.T\v4\xb9+gHa\xbak\x11\x8a7\v#{\xdace\x85\xb3>\xb1f`sR\xf5Ȏ\xfft\xf5\xb4Q\x92y'\x7f(\x89\xfa\xdd\x14\xb5e\x96e!\xbfN}\x10\x87\xa4s?JN\x1bO\x7fE\xf3J\xe2\xfd\xb74kȅ6k\xf6\x81\x12\xf4\n\xe8\xb6\x0f\xab\x84\x9d\xae\x92@\"&\xc20\x94\x93\x03/\xd0}@\xe5-\x19\x14ΙP\xbb\x13\x0f*M\xc5<\xed\x95q6\xbf\xd9\x18\xbb|\x84\xa3ߜ\xedj\x89ˍ\x8c\xae\xda\xf7\v\xea\xfc\x13\xa5\xd5x-J\x16GvI\xbf]\x92c\xb6d\x8a\x9c\xe1\xbc-\x90\xea\x05U\xbf\xaf\x1e\xeb-h\t\x16̪\xe4\xd5\xca\xcf\x06\xab\xca\xe8\x1e\xa7+\x94F\xb7$\x8c\xc08=x<ظI6C\xf7\x7f\x8e\x02\xc9\xf3\xa1R&\x92i\x11A\xebV\x19\xeb\x16\x0f{\xae\xfa\xc8\xeabJ\xe4\xe8W\x1c\x19\xdfY\xd0\xccX\xa5Cb\x17\xaa\xec\xc1\xe2:J\x8d\x99\x97\x1b\xb7O\xe4W2\x1d`\fP/[\xed\xe2\xec\xc1\xa5۫\xc2\xff\xcf\xc3\xcc\xc8\xd1\"ؕV\x19\x98h6B[\x12\xad\xce\xccbo\xb3\xd0\xcb]\xe0\xb7KR\xeb)\xcbС,s㑴g\x04E\x9f\xbew֬Q\x85\xe1\xdf)\xa2|\x0e\x8e\x8cr\xbb˒\x0f\x93\f\x93ѽq\xad\xc3\x04\xf4\xc0\\\xb0\xa5\x1fjRH\xcb|n/\x92\xbf7\xa7\xa5\x14rC\x1d\xb1\xf7\xaf\xe6\xe8\xb0`\x06b\x19Ice\xc0\x0e߾eH\xf3!5\xf6e!UM\xd1>\x8f\x86\x1egOwA\xd29\xc5\xd0\x11\x97\xcav\x17z|O?\x18\xb6\x13\xda\xd8\x16\xe1\x05P\x85\x99\xc8z\x1a\x1d\xde\x19\xf1\xa9\xfc\xa4\xf5\xd9\xe1\xe9\x17\u05fa\xb3$\xb9WO>\xc1sIP\x1e\x88\xbf\xe7\a`bDŽe 3UKZ,Cu\x81\xdd,\x80\xe8\x98\xe8\x8cI\xa2\xcd\xec4\x96u\x99N\x90\x15I\xa7\x90\xb3+k\xdd&?q\x91\xb6\xb2\xc5\xcec\xab\x9dJ\xa2\x1c+\xfd\xccP\x9fM\xd9\xcd\xe4-\xf9wQ\xd6%\xe3%\xb2eI̹sy\x98!\xed\xd7\xf1\xfa\x89\v\xebOS\xb8M\xd9e\xda4SeU\x80\x85\x90a\x99)iD\x0e\x8d\xfb\xe0\xf9?\x9a\xaf\x1a+\x9c\xed\xb8(j\xbd@G/\xe6\xccҘϫ\xa7\x97\x0f\xe4\xd2\x11Y\x111\x13\x17\xec\x178\xdc\xf3\xf6\xa3\xd2\xcb\\\xe6[\r/\xef\x9aVZ(ʁ\x9d\xf1Nga\x92\xf7\xda\xf7N\xbd\xf0ry\x8c\xb9\xa7\xb3P\t\x937\xf7\xb4)o\xee\xe9\x9b{\xfa\xe6\x9e\x0eʛ{\xfa枾\xb9\xa7\xe3\xe5\xcd=\xed\x947\xf74\xd9~\xa4`\xb8\xa2\x95ۉ\nIX%\xa6o̡=ӗ\xcfR\xf2gA\x96dWo\xc6[\x8e\x9c\x05Zt\x86\xc4t\x8c^\x93n\x8dS2L&w\xa64\xc1\v\x7f\x81\xb36\x01\x81\xb3\xcf\xdal&\x01\xbc\xe0Y\x1b\x8f\xe9p\xed\xfc\x05O\xda\x04Z,?\x84q\xe5ӘJ\xe0aK\xc8\xe5\xa0\xe4\xb1nc^l\x0f\x8f\xd1:\xbfq\xd6\xfdI\xb6\xe6\xf9\"\xf3\xffr~'\"6'\xa7S#P\x85A\xb9\xfacp\xe2,\xdaG\xa9\xed\xfe\x17\x1b]KX\xa7x\xdd5\x03\xddT\xcb~\xca\xeb\x1fG\xb0ϑ\xe4\xd4\xf371\xe7<\xae\xdb:\xc4Խ\xf3\x1e\xbfoZZ(\xbfTޒ\xa5\x9f{ߌ4{\xc6\xc9wn\x8e2\xdbk%Um\xfc\n\x0f\xf6\xf0!sW\x01\x84\x8e\xcc\x12e\xf0\x9e\xedU\x1d9\xe31Cׄ\xcc\xdbx\xbe\xad\xcf\xe0\x00\xcb\x0f\xef\xd7\xfd_\xac\xf2ٷ\x11\xac\x9f\x84ݻ\xfb\x18x\x9e\xa3\xa3\xde9\xe2\x13&\xaf\xbf\x93e(x\x11\x88J3)\n'\x95\x01B߀~\xa9ܒ\xdf\xd9~\xcb\xfc\xc2Sz\x8e\xee\xd2\xcc\xdc&\x97r\xdeK~F>\xee\xb2\xc3R\xb3\xb9\xb7)H\xb3\x94\x8c\xdb\xf1\\\xda\x19\xa8K\xf2lS\xd7\x14\x13rj\xd33i\xd3\xc8\xc3\xe8&\xa5\xd4\xfc\xd9\xe4(45W\xf6u2d\x13\xf3b;ٮ\xb3 \xcf̆M&XZ\xe6kr\xbek'\x8bu\x9eZ\x13Y\xae㹫\xb3 \xc7r[S2V\x93pM\xceSm\xb2O\xe7wF\x9e\x95\x9d\xfa\xf2\xe7`^r\xddb:\xd74)\xc34imc\x1e\xe7\xa4\x1cҥ\x99\xa3IT]\x9a%\xdad\x80Nt\x9c\x94\x1bz\x9a\xf795\x94ٌ\xd0x\xb6\xe7\x14ر<Є\x1c\xcf\t\x90\xdd\xec\xcf\xc5n\xc0\xac4\xcdVX\x9a\xbb9~?Z(\xf3ֹ\xf8-d\xf6\xb9dR\xba\xe74\xa7\x04w_\x06MPZ\x82\x9f8\xe6\x88\xc7Ce瞟\xe1\x88G@nv\xac\xac\v+\xaa\xa2sA\x99\xddñ\xb9\xf2\xe7WE\a\u05f7G\x82\xf6\xe5k#\xf21\x90\xfd\x90\x82\x1b\xf6\x04E\x81\xff\x9eP!s\xd7\x01fj\x05h\xa5\xe2\x1b\x81\xfe\xaa#\x7f\x97\xe0\x95[\x16\xa3S\xfdd\x01K\x844}\x01֤)\x99v\x8f\x9dWO\xdf\xfeR\x83>2\xbas+\xf8AQ1kO{\xfa\xc9l0&\f\xca\xc7k1w)e_\x19\xc5gC\xa3\x02\xd8\a\xe9\f\xf3\x10W\x82\x85Z\xa7\r\xa7\xa6\x94-FO1\x10R5\x10\"\xedS\xbc\xef%\xc7\x1f_#\xb8z\x89\xf0*\xc9\x11y\x8d\x10뵂\xac\xa5a֒䍤㋯\x11l-\t\xb7\x16\xf9\x8c\xe9\xc7\x13_\xebX\xe2+\x84]g\a^\x8bH\x97z\xecpq\xf8\x950\xbe\x99c\x86'>Z\x02\xc8\xe8\xf1\xc2\xf1\x10,\x01\xe2ɱ\xc2\xd9 ,e\x1e\fôg\x1f\x12LNdZ\xb4\x9b\x9e\x9a\x84\x94\xb6\xd1=\x7f\xf8/\xf1\xd0_\xe26x\n\xf6\x89\x87\xfb\x96\x1f\xeaK\xa4\xf3\x99\xe1\xd9d\u05c9\x87\xf7\x16\x05hg\x86h\x93\x10\xa7\x0e\xebM\ai\xd3\vp\xc3Czg\xb8\x13\t\x12\x96Pe\xf9A\xbbgo\xc6(\x9d\x83\x9e\xdd\xd7Z\"γ\x82<\x88\xa3\xfa\xfd\x0fvt\u008d\xa8X\xab\xbbg\x16\xe3\xa8j\xee\x1d\xc9؟\x84\xf4\xbb\xf5(\xb8\x1d\x9f\xa4\xb7\xf1\xd6:L\xf1}\x9d\xd6K\xf5\x17\xab\xbb\x1d;\x03\x15״\x8f\xbf=\xba\xa4 \xb3f\x9fx\xb6oz\x88\x80\xa4~\xf7ܰ\x9d\xd2%\xb7\xec\xb2\xd9\n}\xe7:\xc0\xbf/\u05cc\xfd\xa4\x9a\xf4\x91νb\x11\xa8F\x94UqĈ\x89]v\xc1ti^\xd6ɂ\x88\xc5(\xd8\\\xf8\v\v\a\x97\n\xefTQ\xa8\xa73\x97.x%\xfe\x8b\xde1I[\x1b\xfbp\xbb\xa1\xeaA\xaa\xe8\r\x94&{\xae\x91\xb1-L+\xf4v\xe0\xe4zt\xa1\x8ed\xaf6\x7fN@\xa4W\x04\x82\x9f\xe1\xd5x\xa6P\x8b\xddn\x1c\x96k\x12,.\x8fL\xf9{\xe2\x85\xceW\x15\xd7\xd1M=\xe6\xe5\xc1\\\xf50\fv|n\x05kҬ\x9d\xbe\x8a\xd0-=\x9a\x87\a\x12h\xb3\xf7X\xf5\xb7щ\xd2\x1dz>\a\xa7\xe9\x83˳G\x96_\x01\xa7i\x97iET\x8c\xfc\x14M\xc7{\xf1\xd5C\xe3/\x91\xffE\x1d\xe0ct\x15\xb1\xffd\xc0\xa0\xc9H\x02]\x80:umz\x9b5\x17\xbf\xce\xfa\x052\xe2\x02*\xfe\xe2\xeb\x05\xe3\xf3-\xc6^i\xf0\xf7\x7f\a\xd8\x13\xb6\r\xa7\xec\xed7\n7\x1bu\xe9g\xb8\x0f&\xc3R\xe1\xe0\xaa\xd6\b\xc8\xd83\v/E-\xab4\x7f\x80\x9f\x95{\t#\x85Z\xfd\x16\xbd\xc7P\xbc3\x17\xb2\x89\xfd\\\x8b)s?\xb6!\xc0\xf6\x90\xc1\xc9u\xf4\x88홷\xed[[$\f\xee\xfe\xfeg7 +JX\x7f\xac]\x86\t\xea]\x03H\xe90P\xd7h\x1b\xd7N{\xf5D\xf7\xb9w\x9f\xab\xe8<\b\x04t\xa8\x81\xd2F\xcf\x1a͡\xf7 D ]\x8a\xb0\x7f\x1bo\xd9\xf1\xa1:L\x9cJ!S\xbb(,n\x8c\xca\x04\xb9]\xb4fNg\t^\xef&\xe2)\xf7yB\x7f\xd6\x06\xbe{\xd6.\x87\xce>\xb26¿\xe6Y\x9d\xe8;3.\x90p\x8f\xa0\xad\x10\xfey\xec\x1c\x9d\at\x8d\xf5\xdc\x13DX\xa79\a\xe6\tM\r\xc3\xf5\xd7w1\xd4\xc7\x0f\xf6\xac\xd8g8u\xe4W\xec\x93\xc4A\x9c\xdawwz\arZD\x1d{\x90lr\x88\x87\xa6\x15\x1d\x9d\x1a\xd1\x16}57\xa8>H\xec\xa4\xe7L\x9a*\xee\x98\xd4\x18[\xffY\xec\xdc\nw\x86c\xfa\x97\x93\x1aQ\xc55\xa9\xb4b\nktJ\x9d|4\xa0\x0f\xf4~H\x10\x12oû_\xeam{\x1b9\xfb\xeb\xdf.\xfe/\x00\x00\xff\xff\x80\xea<õr\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4V\xcfo\xeb6\f\xbe\xe7\xaf \xb0û\xcc\xce\xebv\x19r\x1b\xba\x1d\x8am\x0fE\xf3л\"\xd3\tWY\xf2H*]\xf6\xd7\x0f\x92\xec&\xb1\x9d\xb5\x1b0\xdd\"\xf1\xc7Ǐ\xe4\xe7TU\xb52==#\v\x05\xbf\x01\xd3\x13\xfe\xa9\xe8\xd3/\xa9_~\x90\x9a\xc2\xfax\xb7z!\xdfl\xe0>\x8a\x86\xee\t%D\xb6\xf8\x13\xb6\xe4I)\xf8U\x87j\x1a\xa3f\xb3\x020\xde\a5\xe9Z\xd2O\x00\x1b\xbcrp\x0e\xb9ڣ\xaf_\xe2\x0ew\x91\\\x83\x9c\x83\x8f\xa9\x8f\x9f\xeb\xbb\xef\xea\xcf+\x00o:܀ \xa775\x1a\x85\U0004f222R\x1f\xd1!\x87\x9a\xc2Jz\xb4)\xfe\x9eC\xec7p~(\xfeC\xee\x82{\x9bCms\xa8\xa7\x12*\xbf:\x12\xfd\xe5\x96ů4X\xf5.\xb2qˀ\xb2\x81\x1c\x02\xeb\x97s\xd2\nD\xb8\xbc\x90\xdfGgx\xd1y\x05 6\xf4\xb8\x81\xec\xdb\x1b\x8b\xcd\n` $Ǫ\x06.\x8ew%\x9c=`gJ\x12\x80У\xff\xf1\xf1\xe1\xf9\xfb\xed\xd55@\x83b\x99zʹ.T\x06$``@\x01\x1a\xc0X\x8b\"`#3z\x85\x82\x12ȷ\x81\xbb\xdcɷ\xd0\x00f\x17\xa2\x82\x1e\x10\x9e3\xe5Ce\xf5\x9bIϡGV\x1a\xd9\x18\xdc\xceCvq;\xc1\xfa)\x95S\xac\xa0IӅ\x923\r\x94`30\x00\xa1\x05=\x90\x00c\xcf(\xe8u\x8a2\xf3ӂ\xf1\x10v\xbf\xa3\xd5z\xe0AR\xb3\xa2k\xd2P\x1e\x91\x15\x18m\xd8{\xfa\xeb-\xb6$BRRgt\x9c\x93\xf3!\xaf\xc8\xde88\x1a\x17\xf1[0\xbe\x81Μ\x801e\x81\xe8/\xe2e\x13\xa9\xe1\xb7\xc0\x98\xc9\xdc\xc0A\xb5\x97\xcdz\xbd'\x1d\x97ˆ\xae\x8b\x9e\xf4\xb4\xce{B\xbb\xa8\x81e\xdd\xe0\x11\xddZh_\x19\xb6\aR\xb4\x1a\x19צ\xa7*C\xf7y\xc1\xea\xae\xf9\x86\x87u\x94OWX\xf5\x94&K\x94\xc9\xef/\x1e\xf2B\xfcC\a\xd2:\x94\xf9(\xae\xa5\x8a3\xd1\xe9*\xb1\xf3\xf4\xf3\xf6+\x8c\xa9s3\xa6\xecg\xdeώrnA\"\x8c|\x8b\\\x9a\xd8r\xe8rL\xf4M\x1fȗ鲎\xd0O闸\xebHe\x9c\xddԫ\x1a\xee\xb3\xe2\xc0\x0e!\xf6\x8dQljx\xf0po:t\xf7F\xf0\x7fo@bZ\xaaD\xec\xc7Zp)\x96S\xe3\xc2\xda\xc5\xc3(s7\xfa\xb5\xb0\xdd\xdb\x1em\xea`\"1ySK6\xaf\a\xb4\x81\xc1,\xb9\xd4\x1fB\x92=\xfe%\x96AI\n\x9a\x89\xbe\xa4\xfd|\x1fͲ\x9c䗃\x11\x9c^N0=&\x9bi~G-ړuXB\x145\xc1\xf7\xa1\xa4\x83>v\xf3\x9c\x15|\xc1ׅ\xdbG\x0eIY\xb3\xae_\x9f\x1b\xb3\x01\xe5{\xb3'?+wZY\xb1\xca߰K\xa9\xbe\x10\xe8!\x10p\xf4>\xed\xedL!3\x90\xa9\x92\xcflH\xb1[@\xb3\x88\xe7\xc1\xb7!\x7f\xf0MJl\xb4\xec\x13\x0e\xcd\x1e\xf2\x14\\\v\x01o\xf7\xba\x9c\xb9x}\x88\xd0r\xf2\x97\xf4\xbf9'\xb9!\xc6\xc5\xdcUF\xb5\xf8\x902.1\xbe\xbc_\x03\xca\xe8\x9c\xd99܀r\x9c{\x17_\xc3lNө\x19G\xed+u(j\xba\xfe\xbd\x01\x9a9\xa4=y=\xa0\xbf\xb5\r\xf0j\xa6*\x7f\x95\x19v\xa7[\xae\xf7o\xff\x01\xe7+UFw\x03I\xbb+\xa5\x05\xce>D\xcab\xf7\xcaH/\xfe\xf3\x98\x11\xb2\xbd\xb4\x1d5\xe3j5\xc6?\"\xf3\x1anBXl\xf6\xec2\x87o.\xca\x13\rl\xf6c\xc1\x7f\a\x00\x00\xff\xff\xb1J-\xe7\xa6\v\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VA\x93\xdb6\x0f\xbd\xfbW`&\x87\xbdDr\xf2}\x97\x8e/\x9d̦\x87L\x93f'N\xf7N\x8b\x90\x8d\x9a\"U\x10\xd4\xc6\xfd\xf5\x1d\x90\xd2\xdak\xcb\xc9n\xa7\xd5\xc5c\n\x04\x1f\xde\xc3\x03UU\xd5\xc2\xf4t\x8f\x1c)\xf8\x15\x98\x9e\xf0\x9b\xa0\xd7\x7f\xb1\xde\xff\x14k\n\xcb\xe1\xedbOޮ\xe06E\t\xdd\x17\x8c!q\x83\xef\xb1%OB\xc1/:\x14c\x8d\x98\xd5\x02\xc0x\x1f\xc4\xe8rԿ\x00M\xf0\xc2\xc19\xe4j\x8b\xbeާ\rn\x129\x8b\x9c\x93OG\x0fo\xea\xb7\xff\xab\xdf,\x00\xbc\xe9p\x05Cp\xa9\xc3\xe8M\x1fwA\\hJ\xcez@\x87\x1cj\n\x8b\xd8c\xa3Gl9\xa4~\x05\xc7\x17%\xc5x|\x81~\x9f\xb3\xad\xc7l\x1f\xc7l9\xc0Q\x94_\xbf\x13\xf4\x91\xa2\xe4\xc0\xde%6\xee*\xb2\x1c\x13w\x81\xe5\xb7\xe3\xe9\x15\fѕ7\xe4\xb7\xc9\x19\xbe\xb6\x7f\x01\x10\x9b\xd0\xe3\n\xf2\xf6\xde4h\x17\x00#?9]5Q\xf3\xb6dlvؙr\x0e@\xe8ѿ\xbb\xfbp\xff\xff\xf5\x93e\x00\x8b\xb1a\xea%\xb3<_\"P\x04\x03\x13\x12x\xd8!#\xdcg>!J`\x8c#\xe8Ǥ\x00\x13\xfeX?.\xf6\x1czd\xa1\xa9\xf8\xf2\x9c\xf4\xd7\xc9\xea\x19\xae\x1b\x85^\xa2\xc0jca\x04\xd9\xe1T>ڱZ\b-Ȏ\"0\xf6\x8c\x11\xbd\x1c\x85<>\xa1\x05\xe3!l\xfe\xc0FjX#k\x1a\xd5&9\xab\xfd8 \v06a\xeb\xe9\xaf\xc7\xdc\x11$\xe4C\x9d\x11\x1c5?>\xe4\x05\xd9\x1b\a\x83q\t_\x83\xf1\x16:s\x00F=\x05\x92?ɗCb\r\x9f\x02#\x90o\xc3\nv\"}\\-\x97[\x92\xc9WM\xe8\xba\xe4I\x0e\xcbl\x11\xda$\t\x1c\x97\x16\at\xcbH\xdb\xcap\xb3#\xc1F\x12\xe3\xd2\xf4Te\xe8\xbe\xf8\xa0\xb3\xafxtb\xbcy\x82U\x0e\xdaEQ\x98\xfc\xf6\xe4E6\xc2w\x14P\x0f\x94F([K\x15G\xa2uI\xd9\xf9\xf2\xcb\xfa+LGg1\xce\xd9ϼ\x1f7ƣ\x04J\x18\xf9\x16\xb9\x88\xd8r\xe8rN\xf4\xb6\x0f\xe4%\xffi\x1c\xa1?\xa7?\xa6MG\xa2\xba\xff\x990\x8ajU\xc3m\x1e6\xb0AH\xbd5\x82\xb6\x86\x0f\x1enM\x87\xee\xd6D\xfc\xcf\x05P\xa6c\xa5\xc4>O\x82\xd39y\x1e\\X;5\xd88ޮ\xe85\xef\xe4u\x8f\xcd\x13\x03i\x16jitv\x1b\xf8\x8cW3\xf9|>_\xfd$|\xde\xe0P\x86|K\xdb\xf3U\x00cm\xbe\"\x8c\xbb\xbb\xba\xf7;\x84\xcd\xd4}\x9bO\xd2Fm\x03+\xa2\x81,r5\xd59\"I<\x16L\xe8l\xac/R^\xe1<\x97\xc2hUc\xe3.\x81>E\xf2\x18\x98\xef8C\xbeP~L\x90[\x8f\xbbq\xc6zAo\xf3P\xbf@\x13r\x0fG\xb4\xf0@\xb2+\xe6p\xa7\x97\xd4\xf3T\xd0g\x8f\x87\xb9\xe53\xec_w\xa8\x91e\x9c\"Dl\x18EqDtj^uf\r\xf0)\xc5l/3\x9b\x11tD\x90\x9dv\xef\xf1pI4\xfcH\xdc\xf1\xbe\xff1\xe4\x1b\xbd\x17'\xc0\x8c-2z\x99\xb5\xb8~b\xb0G\xc1\xecr\x1b\x9a\xa8\x06o\xb0\x97\xb8\f\x03\xf2@\xf8\xb0|\b\xbc'\xbf\xad\x94\xf0\xaa4B\\\xe6\xef\x86\xe5\xab\xfcs\xa5䯟\xdf\x7f^\xc1;k!\xc8\x0eYUk\x93\x9b\x1a\xed\xe4\xb6{\x9d'\xeekHd\x7f\xbe\xf9'\xbc\x84\xbe8\xe7\x19ܬs\xf7\x1f\xf4\xe6Π\x94\xa2uQ%0\xe8\xdcT\xb1\xbbQ\xcd2\x1f\xe6\x1aq´\t\xc1\xa1\xb9l=\x9d\xbe\xc4h/!Uz\xc2Kl\x06\xf0\xad:\nUu\xa6\xafJ\xb4\x91\xd0Qs\x16=\xf9\xfc\a\x96\xbc\x1b\xc3t<(\aӶ\xa9m\xcaWL\xfe\xa61[\xbc6\x16f\x14\x99/\xbcz<\xe0Y\x03]\x8c\xa4\xf8\U000917b7\x8d\x91\x9bq\xac7\x89\xb5\xfdǜ3\x9f?\xff\xceX\xefw&\xcex\xf3\x19\xa8\xeft\xe7$\x83\xa3\x16\x9bC\xe3\xb0$\x84\xd0\xce\xf4ދ \xeb\x83>us\x8d\xf8n0\xe4\xcc\xc6\xe1̻߽\xb9\xfa\xf6\xaa\xf8\xb3z^,F\xfdƱ+\x10N%\xf7\xd8e\xe3\xca\xdf\x01\x00\x00\xff\xff\xec\xa0\xe0\xa1k\r\x00\x00"), diff --git a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml index ffc279958..3be7f69c5 100644 --- a/config/crd/v2alpha1/bases/velero.io_datauploads.yaml +++ b/config/crd/v2alpha1/bases/velero.io_datauploads.yaml @@ -79,7 +79,7 @@ spec: nullable: true properties: snapshotClass: - description: StorageClass is the name of the snapshot class that + description: SnapshotClass is the name of the snapshot class that the volume snapshot is created with type: string storageClass: diff --git a/config/crd/v2alpha1/crds/crds.go b/config/crd/v2alpha1/crds/crds.go index 65981a8b6..3825a2d90 100644 --- a/config/crd/v2alpha1/crds/crds.go +++ b/config/crd/v2alpha1/crds/crds.go @@ -30,7 +30,7 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcY_o\xe4\xb8\r\x7fϧ \xb6\x0fyY;\xb7ע(\xe6mw\xd2\x02Ao\xd3\xc1e\x91w٢=\xbaȒ*ɓ\xa6E\xbf{A\xc9\xf2\xf8\x8f&N\xf6z緑(\xf2'\x92\xfa\x91\xd2\x14EqŌxD\xeb\x84V;`F\xe0\xbf<*\xfa\xe5ʧ\xbf\xb8R\xe8\x9bӧ\xab'\xa1\xf8\x0e\xf6\xbd\xf3\xba\xfb\x19\x9d\xeem\x8d\xb7\xd8\b%\xbc\xd0\xea\xaaC\xcf8\xf3lw\x05\xc0\x94ҞѰ\xa3\x9f\x00\xb5V\xdej)\xd1\x16-\xaa\U000a9bf0\xea\x85\xe4h\x83\xf2d\xfa\xf4C\xf9\xe9\xc7\xf2\x87+\x00\xc5:\xdc\x01\xe9\xe3\xfaYI\u0378+O(\xd1\xeaR\xe8+g\xb0&ŭս\xd9\xc1y\".\x1c\x8cF\xc0\xb7̳\xdbAG\x18\x96\xc2\xf9\xbf\xaf\xa6~\x12·i#{\xcb\xe4\xc2v\x98qB\xb5\xbddv>w\x05\xe0jmp\a\xf7dڰ\x1ail\xd8S\x80R\x00\xe3\xdd\x00S\xa0\xab_\xb0\xf6%<\xa0%5\xe0\x8e\xba\x97\x9ch\xff\x84փ\xc5Z\xb7J\xfc{\xd4\xed\xc0\xeb`T2\x8f\x03\xf1\x9e\xbf\xc0\x90\x8aI81\xd9\xe3G`\x8aC\xc7^\xc0\"Y\x81^M\xf4\x05\x11W\xc2Wm\x11\x84j\xf4\x0e\x8e\xde\x1b\xb7\xbb\xb9i\x85O\xe5\xab\xd6]\xd7+\xe1_nB%\x12U\xef\xb5u7\x1cO(o\x9ch\vf\xeb\xa3\xf0X\xfb\xde\xe2\r3\xa2\b\xd0U(ae\xc7\xff`\x87\x82\xe7\xaegXW\x11\x8d_\xa8<\xafD\x80\xca\x0f\xa5\x13\x1b\x96\xc6]\x9c\x1dMC䝟\xff\xfa\xf0\r\x92\xe9\x10\x8c\xa5\xf7\x83\xdf\xcf\v\xdd9\x04\xe40\xa1\x1a\xb41\x88\x8d\xd5]Љ\x8a\x1b-\x94\x0f?j)P-\xdd\xef\xfa\xaa\x13\x9e\xe2\xfe\xcf\x1e\x9d\xa7X\x95\xb0\x0f5\x1d*\x84\xde\xd0A\xe2%\xdc)س\x0e\xe5\x9e9\xfc\xcd\x03@\x9ev\x059\xf6m!\x98\xb6#K\xe1\xe8\xb5\xc9D\xea'.\xc4k\xca\x02\x0f\x06k\n\x1dy\x8f\x96\x89F\f<\xdbh\vl&[\xceT\xe6\x8f,}Y\xae]\n-0}ɭI\xc0Ԅ\xd1\x06\xd2wQr\xa5\x14@^,\x14\x16\x8dv\xc2k\xfbr.\x17\xe5JÅ\x00\xd0W3U\xa3\xdc\xd8\xc9>\b\x81P\x9c<\x89c\xde\x11ED\x05\x01\x93V\xad\xa6sq\xd9\xc1\xf1\xbb\xf3\xb4\x8a\x12ա\xa7=\xa9,\x93\v\x05\xe7>\n\xa6\xfd\xd2rg\x95\xd6\x12ْ\xf7(\xb7\xbe\xea\x13ur\xaa\x11\xedz\x8fӖ\xefR\xe07ܗIÉI\xda\x05\xe5\x1c!):\x1a/RB\x12\xf16\xa2\x1d\x8al\xc6h#Prw)\x96\xab\xf3\x916\x1c\xacl\x84sD\x99\x8e\xc7P^B\xd7\x11\x14P`\x89G\\h\xe7h2\x830\xa6`\tw\xcdD\xa3p\xf0\xe1\x03h\v\x1fb\xcb\xff\xe1cL\xd7^H_\b5\xb1\x91\xd1\xf8,\xa4Lvߕ\xc5\x14\xbd\xb1\xcdн\xdfp\xc0?\x16\xe2\v?x\xea\x7f\xc2\u07bd\x86g&\xfcX\xee2\x98G\xd3\xee#T\xd8\x10\xc5Z\xf4\xbdUt\x12\xd0Z\xa2\x1c\x17T\xea\u07bfkSN1\xe3\x8e\xda\xdf\xddnl\xe7a\x14L\xecrw\x9b\xb8\xe51Da\xa4\x98A\x12\xbc\xce\x05\x94\xa0G\x0e\t\xc5\xe8}hC\x05\x1c/X[\x90\xe7\xd2\t\xb7\xb6\xa2\x15\xd4V\xa8q\xe6Ly'\xba\x90\xe5\x12Q\xb8\xb0?\xe4Л\b\x9c(\x86\xaak\x85\xc0EӠE\xe5c}\x8d\x86\x0f\x8f\xfbkw6\x92\xd3\xd9L0\x84\x0e\xabc\xc6 \xa7\xbe\x98\";8\xea].\xf2̶\xe8\x1f\xc366\xfc\xf3m\"\x9a\x9cC\x95\x9b.1T\b\x86\xe8F\x8dpx\xdcS\a\x96\xd9\xc6\xe1q\x8d\xf0r\x95\x83\xa1\xe1\xbd\x10\xc1\x15\xcaU\xfc\x06<\xaf9v\x83N\x01\xcc\xe9\r\x96\x0f\x8f\xb9B:\xba\x03\xfc\x91y\x92\x18.(P\xbdduB:\x1fC8\xbf\x0fo\xfd&\xc0\xfbW\x11\uf5d0/\xe0\xad^~5d*\xde\xc2\"_\xa3.^\x89\\\x01\xe6\x94\x1d\xac\xdf^\xa2\xf2\x96\x8b|w\xb5\x90YR\xfcb\xfaL\x96ˉ9\xd3,f\xa7G\xf2Mmh\xb8B\xbe\xb5\x11\x8d\x0fCC\xd8\xeb\xde\x06\x1a\x1a\x9e\x8b\xe8V\xf6]\xadh\x1d\x1fZ\xa6w\xea\xad\xf6m\xbd\"\xdc\xf7,\x9f\xd4;\x96\x12*^\xec\xd3kN\xae\x7f;\xeb\x8bK\x03=\x92:\xe4\x80'T@\xad6\x13\x12y\xd2\xe9J\xf8F\xddx\xb8\xf8\\/\xafH\xc1߃\xa2Pv\xa9gʀ^\xafK\xaf:t\xdd)H\xc5JB\xf5R\xb2J\xe2\x0e\xbc\xed/\xf5\x8fكҡs\xac\xdd\"\xea\xafQ*^\x15\x87%\xc0*j*\x96=\xed\xb5\x1bb\xff\xae\xa2\xa14\xdf\xc2p\xafy\x00\xa0\xbe\xe3\x95\xe4]XB\x0f\xbe\x01\xe6@2\xb9\x9c\x1f\xa1\xbd~=@\xd5w9f\xba\xc7\xe7\xcc\xe8\xe7\xbaF\x93c\xcb\x02\x0e\x16\r\xb3٩\xd5\xf3\xect2^vrę\xe6\xb2:\xc7\xf7\xcf\xcc\xdc\xdf\xc2ax\x97\xa7\a|[\xceN\xb7\xa3\xa3\x96\xe90\x87'J\xd5w\x15Z\xf2xx\x04M\xaeO,\x999\x80L\xf1Y\xc8\xce\x1aƞ0\xa8\xa2\x93LU*^\xe0R\x97̅3\x92\xe5\x8al\xdaɬ}9\x1f\x90D:#\xbd\xbf\xb7_\x19\x9f\x8c\xf3E8\xf7\ue6cb\xc2\xf4\x05w1?>\x05\xff6\x16^\xb9\xd0͟\xe6\xb7Z\xea\x99\xf0\x16\xc1\x0f\xff\n\xe4\xe8}\xca\xd4k^\x9e\x9b\xf9=)9\xeb\xa8\xd5`@\xce'\xba\x87g\x95\xe9H_\x8d\x8f\x85;\xf8\xcf\x7f\xaf\xfe\x17\x00\x00\xff\xff\x9f\xc23\x7f`\x1b\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xbcY\xcbsۼ\x11\xbf\xfb\xaf\xd8I\x0f\xb9X\xf4\x97\xaf\x9dNG\xb7Dng4\xfd\xe2z\xa2\xd4w\x88\\\x91\x88A\x80\xc5C\xae\xdb\xe9\xff\xdeY<(> \xd1J\x93\xf2\xe0\xb1\x00\xecb\x1f\xd8\xdf\xee\x02\xab\xd5\xea\x86u\xfc\t\xb5\xe1J\xae\x81u\x1c\xffiQ\xd2/S<\xff\xc9\x14\\\xdd\x1d?\xdc\xfeR|\xf8\xb5\xf8\xe5\x06@\xb2\x16\xd7@\xfc\\'\x14\xabLqD\x81Z\x15\\ݘ\x0eKb[k\xe5\xba5\x9c&\x02Y\xdc2\x88{\xcf,\xfb\xbb\xe7\xe0\a\x057\xf6\xaf\x93\x89߸\xb1~\xb2\x13N31\xdaՏ\x1b.k'\x98\x1e\xce\xdc\x00\x98Ru\xb8\x86\aڲc%\xd2X\xd4ċ\xb0\x02VU\xde6LH\x12\xf8\x0e\xc7\x17E\xa1\xe3\x06\xea\x00\xb6A\xf8\xc4\xcag\xd7\xc1\xce*\xcdj\x84\xdfT\x19\x9c\xf7Ҡ\x8e\xceۇ%\xa6QNT\xb0O\x1a\x03\x18\xabt\u058b\x1d\x96E\xa0\x8a|\x13ۉ+\xc7{\xfe\xe0CVjd\xd9C\x96P\xa6\xf0+\xb8\x92\xf9\x93\xf6\xb1\xc67\x9d\xb2\xa15\xa5\xaa\xb07\x1d\x0e%\xe2\x06:\xadJ4\xe6¹'\xf2\x91\f\x0f\xa7\x81\x99Y\u008a\xe3\xafLt\r\xfb\x10P\xa6l\xb0e\xebH\xa1:\x94\x1f\x1f\xb7O\xbfߍ\x86\x81\x04\xe9P[\x9e`.|\x03\xfc\x1e\x8c\xc2X\xd9\xf7\xc40\xac\x82\x8a\x80\x1b\x8d\xd74\x82\x16VQ\x86`\x10n@c\xa7Ѡ\xb4C\x17\xa7O\x1d\x80IP\xfboX\xda\x02v\xa8\x89M:h\xa5\x92G\xd4\x164\x96\xaa\x96\xfc_=o\x03V\xf9M\x05\xb3\x18q\xf7\xf4y\x90\x94L\xc0\x91\t\x87\xb7\xc0d\x05-{\x05\x8d\xb4\v89\xe0痘\x02>+\x8d\xc0\xe5A\xad\xa1\xb1\xb63뻻\x9a۔\xb7JնNr\xfbz\xe7S\x10\xdf;\xab\xb4\xb9\xab\xf0\x88\xe2\xce\xf0z\xc5t\xd9p\x8b\xa5u\x1a\xefX\xc7W^t\xe9sW\xd1V\xbf\xd31ә\xf7#Yg\x1e\r\x9fO:\x17<@\xb9\x87\x8e\x13\x8b\xa4A\x8b\x93\xa1i\x88\xac\xf3\xe5ϻ\xaf\x90\xb6\xf6ΘZ\xdf\xdb\xfdDhN. \x83qy@\x1d\x9cxЪ\xf5}%\x93%\x8a\x05M6~\x11pY\x91\x1d\xb1?s\x04\x0f\x81\x81\x97I\xc9ZQL\x9c3o\xf8\xb6\x96h\xe8\x88\x1a\xb4\xa4\x91\xcc 8\x97p*\xa2`X,M\xb5\xda+%\x90M\xf1\xae4|'Yg\x1ae\x17t\xdb\x1e \xad\xfc\xfa\xda!m\xbe\xd9mo\xe9O\x1a\xa7sq\xe4U\x04`\n\x1e*'\xe6 \v\x01hi\xd1f\xb7\x05\x13\xc9\xe7F\x90N\b\xb6\x17\xb8\x06\xab\xdd\\\xb1\xf3ǐ\xbe\xc4v#\x98\xc9.\x98(\x18\x0f\xa0_\x9e;}\x89\x1f\x94~\x85m\xd8\x14iz\x83S\xfa\xa1\xa2x@\xc4\xfb\xf4\x0f/\xdc6Y\xca\v\xc7\x0fbq\x93\x04\xfc\x11\xfa\xc4b'\xa8\xa3\x0e\x17\x94y|\xdax}\x974#T\xfe\x1e\xcd\x02\xcb\xf3\aq\xa6\xdbӈ \xa7\xddD\xcas\xca)\x8a/\xc2\b\xac\xc0u\xd7\xcbN\x01\xce5Vs\x99W#\x7fe\xa6\xc7J\x9f\x89\xda\x19\xb6{S0\xcb>\xab#u^\xf2\xc0\xeb\xf9\xde\xc3\x16\xedR\x88\\Tm\x963\x06[\x92\xc5)E\x90$\xab\x96\xc6W)\x7fP\x95t\xe0u\xac\x863\x9b\x1e8\x8a\xca\\\x1d\xec\v\xf6\xf0B,@X\xafDJv\x11\xa9\x88\x1e<\x83x \x9c\xf1\x1d\x1aMf\x14\b)\xa5 D>m\xdeT\xd4\xfb\x86\xfcme}\xb8i\x8bV.\x9d\xd6(m\xba\x7f\xa3\x0e\xf7;\n\xfb2\xdc\\\r\xef&\x96\x8a\xe19\x85\xef\x9cu5@\x1b\x96\nt\x7f?\x92n\xc7r\xe5\xf0\x89]\xa0\xf4\x9d\xfe\\\xfct\a Y\x8bk ~\xae\x13\x8aU\xa68\xa2@\xad\n\xae\xeeL\x87%\xb1\xad\xb5r\xdd\x1aN\x13\x81\xac\xdf2\x88\xfb\xc0,\xfb\xbb\xe7\xe0\a\x057\xf6\xaf\x93\x89_\xb8\xb1~\xb2\x13N31\xdaՏ\x1b.k'\x98Ng\xee\x00L\xa9:\\\xc3#mٱ\x12i\xac\xd7ċ\xb0\x02VU\xde6LH\x12\xf8\xa6㋢\xd0q\x03u\x00\xdb |f\xe5\x8b\xeb`g\x95f5\xc2/\xaa\f\xce{mP\xf7\xceۇ%\xa6QNT\xb0\x8f\x1a\x03\x18\xabt\u058b\x1d\x96E\xa0\xea\xf9F\xb6\x13W\x8e\xf7\xfc\x8d\x0fY\xa9\x91e\x0fYD\x99¯\xe0J\xe6Oڧ\x1a\xaf:e\xa95\xa5\xaap0\x1d\xa6\x12q\x03\x9dV%\x1as\xe1\xdc\x13\xf9H\x86\xc7\xd3\xc0\xcc,a\xc5\xf1g&\xba\x86}\f(S6زuO\xa1:\x94\x9f\x9e\xb6Ͽߍ\x86\x81\x04\xe9P[\x1ea.|\t~'\xa30V\xf6=1\f\xab\xa0\"\xe0F\xe35\xedA\v\xab^\x86`\x10n@c\xa7Ѡ\xb4\xa9\x8b\xe3\xa7\x0e\xc0$\xa8\xfd\xafX\xda\x02v\xa8\x89M\xe9\\\xf0\x00\xe5\x1e:N\xac'\rZ\x9c\fMCd\x9d\xaf\x7f\xde}\x83\xb8\xb5w\xc6\xd4\xfa\xde\xee'Bsr\x01\x19\x8c\xcb\x03\xea\xe0ăV\xad牲\xea\x14\x97\xd6\xff(\x05G95\xbfq\xfb\x96[\xf2\xfb?\x1c\x1aK\xbe*`\xe3\x939\xec\x11\\GaT\x15\xb0\x95\xb0a-\x8a\r3\xf8\xc3\x1d@\x966+2\xecu.H\xeb\x90\xe9\xe2`\xb5d\"\x96\x12g\xfcu\u0080]\x87%9\x8elGD\xfc\xc0{\xb0=(\r,YY\x8c\xd8\xe5Õ\xbe,\xc6N\x17M\xe4\xf9\x9c\xa3\x89b\xc9\x04\xcb\"쇕3\xa6\x00b\x9a+\x06\x1a\x8d\x9d2\xdc*\xfdF\x8cC\x9a(f\x1c\xce\x18\x9f\xbe\x92\xc9\x12ł&\x1b\xbf\b\xb8\xacȎ8\x9c9\x82\x87\xc0\xc0ˤd\xad(&Ι7|[K4tD\rZ\xd2Hf\x10\x9cK8\x15Q\x90\x16KS\xad\xf6J\tdS\xbc+\r\xdfI֙F\xd9\x05ݶ\a\x88+\xbf\xbduH\x9bov\xdb\x0f\xf4'\x8eӹ8\xf2\xaa\a`\n\x1e*'\xe6 \v\x01hi\xd1f\xb7\x05ӓύ \x9d\x10l/p\rV\xbb\xb9b\xe7\x8f!}\x91\xedF0\x93]0Qp\x97\xae\xcf\x1d\xbf\xc8\x10J\xbf\xc26l\n5\x83\xc5)\xffPU\x9c\x10\xf1!\xff\xc3+\xb7M\x96\xf2\xc2\xf9\x83\xbe\xbaa5^\xadP\xb2<\xabO_\xed\x04u\xd4\xe1\x822O\xcf\x1b\xaf\xef\x92f\x04\xcbߣY`y\xfe$\xcet{\x1e\x11䴛HyN9E\x01F \x81\x15\xb8\xeev\xd9)¹\xc6j.\xf3j\xe4\xaf\xcc\xf4X\xe93a;\x03wo\nf\xd9\x17u\xa4\xd6K\x1ex=\xdf;\xed\xd1.\xc5\xc8E\xd5fI#ْ,N9\x82$Y\xb54\xbe\x8a\t\x84ʤ\x03\xaf\xfbr8\xb3遣\xa8\xcc\xcdѾ`\x0f/\xc4\x02\x86\rJ\xc4l\xd7C\x15уg\xd0\x1f\bg|\x8bF\x93\x19\x05BN)\b\x12O\x1c\xb9\x81w\xef@ix\x17Z\xf7w\x1fB\xfeq\\\xd8\x15\x97\xc9\x1e\x19\x8e\xaf\\\x88\xb8\xefMi\x89\x9c;t\f\xca-\x81\xf8\xdf&\xcb'v\xb0\xd4\xc8xݭ\x82W\xc6\xedP\xbb\xe6\x10<\xf22\x1f`\x8f\a\xaa\x974Z\xa7%\xa56Ԛ*\b\xe3Y*\x97\xc1\xf6\vJ\x99$\xcf,(4MI^\v\xfa\x7f\x8a\xd9i\xa0g\x94q\xddm\x12\xfa\x12v\xb8$Y\x12r\xbc:ʩ4\xaf9\xf5\x05r\x989\xd5-\x01\x1c2\x92\xf6\xed\xaf\x87+\x8f\xb7\x05U\n\xb1\x88#\x00<\xb1\xa3\b\r\x9b\x13\x80S߱\xd9m3<\a\x8a\xaa\x8f\xafLt.Z\xe3\xe9ys\x95\x1dH\x94\f^\xd3\xf0k\xc3\xcbf\xec\xb7Y\x8f\xe0ea/\xe8k\xd4\x1b\xc4\xcc\x03\xf5*_\xb1N\xd6L\xa3l2\x9d\x9e\xd7\xe9\xd4\xd8\xf5\xd9٧\xe7\xcdUU\xbd\xefȯ\xab\xeb\xc3U[o\xe5\xd2i\x8d\xd2\xc6\v8jq\xbf\xa3\xb2/\xc3\xd5Uz9\xb1T\r\xcf)|묫\x04mX\xac\xd0\xfd\x05I\xbc\x1e\xcb\xd5\xc3'v\x81ҷ\xf2\xc4\r+\xc0#J\xa0\xb6\x85qA\xc8\xedY\x9abJ\x93\x0f\xa7\x81K\x8fb\xe1.46\xad\xbdx\xf1J\xe0\x1b\x1dNߓ\xbe7\x17xz\x10\xa5\xf0\xcb\x18a~\xa2\xe3\xbd\x1bu\xa2\xab,ӫrc68\x87Z\xe1+\x1a'2\t\xe2\a\xd6\na\xcb\xd0n\x99l\xadp\xb9I`\x06\x18\xe8\xc0\xa4\x87\x89K=\xd3\xf7\x17\x10-\x1a\xc3\xea%\x1c\xff\x12V\x85\xab\x8e\x9e\x04؞\xf2\xe8X\xb4\xf7\xa6\x0f\xb6\x9b`T\xaajI\x82GU\xf9\xed\xe5\xcd7|7I\xd21\xdb,H\xf2\xc4l\x13\x01\xe6\xe0\x84\xf04\xb3\xac\xdb\x17\xe1{\xa4h\xfa\xad\x92\xaf\xefr\x97ģ59\x00\xc4k\x0e\x12J\xd7\xe6\n\xfaG|͌~*K\xeclF\xb3\x15\xd2\xc9p\x91\x90\v\xb48\x97\xe594JDd\xf7/\x00ҵ{\xd4do\xff\xc60n\xccr\xd1.\xab\x91\xbb\x12\xfa\xa1\xe0\xf1\x9c\b\x85\xa9\xe9\f\xb7#\xb1^\xad\xb8\xe9\x04{\xcb0\x8e\x8a\xa4h\x93\xc4mD\xf8\x98\xe4\xe7\xe7\xe1\xf2E\xc3\xf0\x1e\x93\xef[s\x8f*9\x1f\xa4\xcf#\x93\xf9\xe1\x9d\xe5\xc7\xecp\x01\x18c$o\x1f\xae,ķ\x0f1\xeax\x85\xd2Ro\xa1}\"\x1cWu\xf2bk\x95\\\xd7\xddV\x88\x8e^\xe9\x96$\x1e-^\xa8L\xfa\xf7\xc1\\]\xb2\xa3\x10'`\xf1wқ\xe9\v·\xe1A\x88\xd9\xfeb\xbcl\x98\xac) $%7\x9f\x1cs\x8cg\xa5ƨ\xb0\x18\x8b\xff\xff\xac)\xb2\xc7e6\xe8%\xaf\x12\xde\xfdmH:\xe2\xf6\xc3C\xc4\x1a\xfe\xfd\x9f\xbb\xff\x06\x00\x00\xff\xff\xe6g\x1ap\xb5\x1f\x00\x00"), } var CRDs = crds() diff --git a/design/volume-snapshot-data-movement/volume-snapshot-data-movement.md b/design/volume-snapshot-data-movement/volume-snapshot-data-movement.md index 8e218b6d1..030e8ba7a 100644 --- a/design/volume-snapshot-data-movement/volume-snapshot-data-movement.md +++ b/design/volume-snapshot-data-movement/volume-snapshot-data-movement.md @@ -245,7 +245,7 @@ spec: of the CSI snapshot. properties: snapshotClass: - description: StorageClass is the name of the snapshot class that + description: SnapshotClass is the name of the snapshot class that the volume snapshot is created with type: string storageClass: diff --git a/pkg/apis/velero/v2alpha1/data_upload_types.go b/pkg/apis/velero/v2alpha1/data_upload_types.go index 2586f21a6..77d081153 100644 --- a/pkg/apis/velero/v2alpha1/data_upload_types.go +++ b/pkg/apis/velero/v2alpha1/data_upload_types.go @@ -76,7 +76,7 @@ type CSISnapshotSpec struct { // StorageClass is the name of the storage class of the PVC that the volume snapshot is created from StorageClass string `json:"storageClass"` - // StorageClass is the name of the snapshot class that the volume snapshot is created with + // SnapshotClass is the name of the snapshot class that the volume snapshot is created with // +optional SnapshotClass string `json:"snapshotClass"` } From 94d3494d256308911051db146b2633614bc9ec68 Mon Sep 17 00:00:00 2001 From: Anshul Ahuja Date: Tue, 25 Jul 2023 14:33:03 +0530 Subject: [PATCH 04/26] Add docs for resourcemodifiers Signed-off-by: Anshul Ahuja --- .../docs/main/restore-resource-modifiers.md | 103 ++++++++++++++++++ site/data/docs/main-toc.yml | 2 + 2 files changed, 105 insertions(+) create mode 100644 site/content/docs/main/restore-resource-modifiers.md diff --git a/site/content/docs/main/restore-resource-modifiers.md b/site/content/docs/main/restore-resource-modifiers.md new file mode 100644 index 000000000..538237641 --- /dev/null +++ b/site/content/docs/main/restore-resource-modifiers.md @@ -0,0 +1,103 @@ +--- +title: "Restore Resource Modifiers" +layout: docs +--- + +## Resource Modifiers +Velero provides a generic ability to modify the resources during restore by specifying json patches. The json patches are applied to the resources before they are restored. The json patches are specified in a configmap and the configmap is referenced in the restore command. + +**Creating resource Modifiers** + +Below is the two-step of using resource modifiers to modify the resources during restore. +1. Creating resource modifiers configmap + + You need to create one configmap in Velero install namespace from a YAML file that defined resource modifiers. The creating command would be like the below: + ```bash + kubectl create cm --from-file -n velero + ``` +2. Creating a restore reference to the defined resource policies + + You can create a restore with the flag `--resource-modifier-configmap`, which will apply the defined resource modifiers to the current restore. The creating command would be like the below: + ```bash + velero restore create --resource-modifier-configmap + ``` + +**YAML template** + +- Yaml template: + ```yaml + version: v1 + resourceModifierRules: + - conditions: + groupKind: persistentvolumeclaims + resourceNameRegex: "^mysql.*$" + namespaces: + - bar + - foo + patches: + - operation: replace + path: "/spec/storageClassName" + value: "premium" + - operation: remove + path: "/metadata/labels/test" + ``` + +- The above configmap will apply the JSON Patch to all the PVCs in the namespaces bar and foo with name starting with mysql. The JSON Patch will replace the storageClassName with "premium" and remove the label "test" from the PVCs. +- You can specify multiple JSON Patches for a particular resource. The patches will be applied in the order specified in the configmap. A subsequent patch is applied in order and if multiple patches are specified for the same path, the last patch will override the previous patches. +- You can can specify multiple resourceModifierRules in the configmap. The rules will be applied in the order specified in the configmap. + +### Operations supported by the JSON Patch RFC: +- add +- remove +- replace +- move +- copy +- test (covered below) + +### Advanced scenarios +#### **Conditional patches using test operation** + The `test` operation can be used to check if a particular value is present in the resource. If the value is present, the patch will be applied. If the value is not present, the patch will not be applied. This can be used to apply a patch only if a particular value is present in the resource. For example, if you wish to change the storage class of a PVC only if the PVC is using a particular storage class, you can use the following configmap. +```yaml +version: v1 +resourceModifierRules: +- conditions: + groupKind: persistentvolumeclaims.storage.k8s.io + resourceNameRegex: ".*" + namespaces: + - bar + - foo + patches: + - operation: test + path: "/spec/storageClassName" + value: "premium" + - operation: replace + path: "/spec/storageClassName" + value: "standard" +``` + +#### **Other examples** +```yaml +version: v1 +resourceModifierRules: +- conditions: + groupKind: deployments.apps + resourceNameRegex: "^test-.*$" + namespaces: + - bar + - foo + patches: + # Dealing with complex values by escaping the yaml + - operation: add + path: "/spec/template/spec/containers/0" + value: "{\"name\": \"nginx\", \"image\": \"nginx:1.14.2\", \"ports\": [{\"containerPort\": 80}]}" + # Copy Operator + - operation: copy + from: "/spec/template/spec/containers/0" + path: "/spec/template/spec/containers/1" +``` + +**Note:** +- The design and approach is inspired from [kubectl patch command](https://github.com/kubernetes/kubectl/blob/0a61782351a027411b8b45b1443ec3dceddef421/pkg/cmd/patch/patch.go#L102C2-L104C1) +- Update a container's image using a json patch with positional arrays +kubectl patch pod valid-pod -type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]' +- Before creating the resource modifier yaml, you can try it out using kubectl patch command. The same commands should work as it is. diff --git a/site/data/docs/main-toc.yml b/site/data/docs/main-toc.yml index 620865c95..75ea67bc6 100644 --- a/site/data/docs/main-toc.yml +++ b/site/data/docs/main-toc.yml @@ -43,6 +43,8 @@ toc: url: /restore-reference - page: Restore hooks url: /restore-hooks + - page: Restore Resource Modifiers + url: /restore-resource-modifiers - page: Run in any namespace url: /namespace - page: CSI Support From 2f20fac78b47865c045bf0ac5f0148a3405f0f27 Mon Sep 17 00:00:00 2001 From: yanggang Date: Fri, 28 Jul 2023 16:16:26 +0800 Subject: [PATCH 05/26] Delete redundance checkNodeAgent code Signed-off-by: yanggang --- pkg/cmd/server/server.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index e30d2b30a..a42c0589c 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -586,13 +586,6 @@ func (s *server) checkNodeAgent() { } func (s *server) initRepoManager() error { - // warn if node agent does not exist - if err := nodeagent.IsRunning(s.ctx, s.kubeClient, s.namespace); err == nodeagent.ErrDaemonSetNotFound { - s.logger.Warn("Velero node agent not found; pod volume backups/restores will not work until it's created") - } else if err != nil { - s.logger.WithError(errors.WithStack(err)).Warn("Error checking for existence of velero node agent") - } - // ensure the repo key secret is set up if err := repokey.EnsureCommonRepositoryKey(s.kubeClient.CoreV1(), s.namespace); err != nil { return err From 289aae15811cc3c49ef9d772cfb876812b71e3c7 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Fri, 28 Jul 2023 11:23:22 -0400 Subject: [PATCH 06/26] GetS3Credentials pass profile from config to NewSharedCredentials Signed-off-by: Tiger Kaovilai --- changelogs/unreleased/6558-kaovilai | 1 + pkg/repository/config/aws.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/6558-kaovilai diff --git a/changelogs/unreleased/6558-kaovilai b/changelogs/unreleased/6558-kaovilai new file mode 100644 index 000000000..42e57b9da --- /dev/null +++ b/changelogs/unreleased/6558-kaovilai @@ -0,0 +1 @@ +Non default s3 credential profiles work on Unified Repository Provider (kopia) \ No newline at end of file diff --git a/pkg/repository/config/aws.go b/pkg/repository/config/aws.go index 936389412..2acbbb502 100644 --- a/pkg/repository/config/aws.go +++ b/pkg/repository/config/aws.go @@ -70,7 +70,7 @@ func GetS3Credentials(config map[string]string) (*credentials.Value, error) { return nil, errors.New("missing credential file") } - creds := credentials.NewSharedCredentials(credentialsFile, "") + creds := credentials.NewSharedCredentials(credentialsFile, config[awsProfileKey]) credValue, err := creds.Get() if err != nil { return nil, err From 16613f5fe1ec3a6a8178b2dd2436ca020bb0114e Mon Sep 17 00:00:00 2001 From: Yang Gang Date: Mon, 31 Jul 2023 15:58:07 +0800 Subject: [PATCH 07/26] Fix the velero crd define locations. (#6554) Signed-off-by: yanggang --- design/upload-progress.md | 2 +- pkg/cmd/server/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/design/upload-progress.md b/design/upload-progress.md index 556d39cb1..2a3db0c71 100644 --- a/design/upload-progress.md +++ b/design/upload-progress.md @@ -89,7 +89,7 @@ will be deleted and at that point any uploads still in progress should be aborte ### Uploading (new) The "Uploading" phase signifies that the main part of the backup, including snapshotting has completed successfully -and and uploading is continuing. In the event of an error during uploading, the phase will change to +and uploading is continuing. In the event of an error during uploading, the phase will change to UploadingPartialFailure. On success, the phase changes to Completed. The backup cannot be restored from when it is in the Uploading state. diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index a42c0589c..826fb3000 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -503,7 +503,7 @@ func (s *server) veleroResourcesExist() error { } if len(errs) > 0 { - errs = append(errs, errors.New("Velero custom resources not found - apply examples/common/00-prereqs.yaml to update the custom resource definitions")) + errs = append(errs, errors.New("Velero custom resources not found - apply config/crd/v1/bases/*.yaml,config/crd/v2alpha1/bases*.yaml, to update the custom resource definitions")) return kubeerrs.NewAggregate(errs) } From d6134ec444d6ff73cbb802d090bafa59b0d471ee Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 31 Jul 2023 21:45:04 +0800 Subject: [PATCH 08/26] fix issue 6561 Signed-off-by: Lyndon-Li --- Dockerfile | 4 +- cmd/velero-helper/velero-helper.go | 27 ++++++++++ .../data_download_controller_test.go | 15 +++++- pkg/controller/data_upload_controller_test.go | 15 +++++- pkg/exposer/csi_snapshot.go | 12 +++-- pkg/exposer/csi_snapshot_test.go | 16 ++++++ pkg/exposer/generic_restore.go | 12 +++-- pkg/exposer/generic_restore_test.go | 15 ++++++ pkg/exposer/image.go | 49 +++++++++++++++++++ pkg/nodeagent/node_agent.go | 10 ++++ 10 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 cmd/velero-helper/velero-helper.go create mode 100644 pkg/exposer/image.go diff --git a/Dockerfile b/Dockerfile index 7f600d0f2..2edd111ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,9 @@ COPY . /go/src/github.com/vmware-tanzu/velero RUN mkdir -p /output/usr/bin && \ export GOARM=$( echo "${GOARM}" | cut -c2-) && \ go build -o /output/${BIN} \ - -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} + -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} && \ + go build -o /output/velero-helper \ + -ldflags "${LDFLAGS}" ${PKG}/cmd/velero-helper # Restic binary build section FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as restic-builder diff --git a/cmd/velero-helper/velero-helper.go b/cmd/velero-helper/velero-helper.go new file mode 100644 index 000000000..5991531a6 --- /dev/null +++ b/cmd/velero-helper/velero-helper.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + "time" +) + +const ( + // workingModePause indicates it is for general purpose to hold the pod under running state + workingModePause = "pause" +) + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "ERROR: at least one argument must be provided, the working mode") + os.Exit(1) + } + + switch os.Args[1] { + case workingModePause: + time.Sleep(time.Duration(1<<63 - 1)) + default: + fmt.Fprintln(os.Stderr, "ERROR: wrong working mode provided") + os.Exit(1) + } +} diff --git a/pkg/controller/data_download_controller_test.go b/pkg/controller/data_download_controller_test.go index a0a9b645f..726d9b6a0 100644 --- a/pkg/controller/data_download_controller_test.go +++ b/pkg/controller/data_download_controller_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -141,6 +142,18 @@ func initDataDownloadReconcilerWithError(objects []runtime.Object, needError ... } func TestDataDownloadReconcile(t *testing.T) { + daemonSet := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "node-agent", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{}, + } + tests := []struct { name string dd *velerov2alpha1api.DataDownload @@ -283,7 +296,7 @@ func TestDataDownloadReconcile(t *testing.T) { t.Run(test.name, func(t *testing.T) { var objs []runtime.Object if test.targetPVC != nil { - objs = []runtime.Object{test.targetPVC} + objs = []runtime.Object{test.targetPVC, daemonSet} } r, err := initDataDownloadReconciler(objs, test.needErrs...) require.NoError(t, err) diff --git a/pkg/controller/data_upload_controller_test.go b/pkg/controller/data_upload_controller_test.go index 3e0c04606..e17aad527 100644 --- a/pkg/controller/data_upload_controller_test.go +++ b/pkg/controller/data_upload_controller_test.go @@ -27,6 +27,7 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -145,6 +146,18 @@ func initDataUploaderReconcilerWithError(needError ...error) (*DataUploadReconci RestoreSize: &restoreSize, }, } + daemonSet := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "node-agent", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{}, + } + now, err := time.Parse(time.RFC1123, time.RFC1123) if err != nil { return nil, err @@ -176,7 +189,7 @@ func initDataUploaderReconcilerWithError(needError ...error) (*DataUploadReconci } fakeSnapshotClient := snapshotFake.NewSimpleClientset(vsObject, vscObj) - fakeKubeClient := clientgofake.NewSimpleClientset() + fakeKubeClient := clientgofake.NewSimpleClientset(daemonSet) fakeFS := velerotest.NewFakeFileSystem() pathGlob := fmt.Sprintf("/host_pods/%s/volumes/*/%s", "", dataUploadName) _, err = fakeFS.Create(pathGlob) diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index 4d452a39e..a98ee0ad5 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -345,6 +345,11 @@ func (e *csiSnapshotExposer) createBackupPVC(ctx context.Context, ownerObject co func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject corev1.ObjectReference, backupPVC *corev1.PersistentVolumeClaim, label map[string]string) (*corev1.Pod, error) { podName := ownerObject.Name + podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace) + if err != nil { + return nil, errors.Wrap(err, "error to get inherited pod info from node-agent") + } + var gracePeriod int64 = 0 pod := &corev1.Pod{ @@ -366,15 +371,16 @@ func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject co Containers: []corev1.Container{ { Name: podName, - Image: "alpine:latest", - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{"sleep", "infinity"}, + Image: podInfo.image, + ImagePullPolicy: corev1.PullNever, + Command: []string{"/velero-helper", "pause"}, VolumeMounts: []corev1.VolumeMount{{ Name: backupPVC.Name, MountPath: "/" + backupPVC.Name, }}, }, }, + ServiceAccountName: podInfo.serviceAccount, TerminationGracePeriodSeconds: &gracePeriod, Volumes: []corev1.Volume{{ Name: backupPVC.Name, diff --git a/pkg/exposer/csi_snapshot_test.go b/pkg/exposer/csi_snapshot_test.go index e538e721e..dfd4884d1 100644 --- a/pkg/exposer/csi_snapshot_test.go +++ b/pkg/exposer/csi_snapshot_test.go @@ -31,6 +31,7 @@ import ( "k8s.io/client-go/kubernetes/fake" clientTesting "k8s.io/client-go/testing" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" @@ -82,6 +83,18 @@ func TestExpose(t *testing.T) { }, } + daemonSet := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "node-agent", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{}, + } + tests := []struct { name string snapshotClientObj []runtime.Object @@ -257,6 +270,9 @@ func TestExpose(t *testing.T) { vsObject, vscObj, }, + kubeClientObj: []runtime.Object{ + daemonSet, + }, kubeReactors: []reactor{ { verb: "create", diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index f11ac27c8..8881b12b1 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -251,6 +251,11 @@ func (e *genericRestoreExposer) createRestorePod(ctx context.Context, ownerObjec restorePodName := ownerObject.Name restorePVCName := ownerObject.Name + podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace) + if err != nil { + return nil, errors.Wrap(err, "error to get inherited pod info from node-agent") + } + var gracePeriod int64 = 0 pod := &corev1.Pod{ @@ -272,15 +277,16 @@ func (e *genericRestoreExposer) createRestorePod(ctx context.Context, ownerObjec Containers: []corev1.Container{ { Name: restorePodName, - Image: "alpine:latest", - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{"sleep", "infinity"}, + Image: podInfo.image, + ImagePullPolicy: corev1.PullNever, + Command: []string{"/velero-helper", "pause"}, VolumeMounts: []corev1.VolumeMount{{ Name: restorePVCName, MountPath: "/" + restorePVCName, }}, }, }, + ServiceAccountName: podInfo.serviceAccount, TerminationGracePeriodSeconds: &gracePeriod, Volumes: []corev1.Volume{{ Name: restorePVCName, diff --git a/pkg/exposer/generic_restore_test.go b/pkg/exposer/generic_restore_test.go index 23dd3f480..e55657710 100644 --- a/pkg/exposer/generic_restore_test.go +++ b/pkg/exposer/generic_restore_test.go @@ -30,6 +30,7 @@ import ( velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerotest "github.com/vmware-tanzu/velero/pkg/test" + appsv1 "k8s.io/api/apps/v1" corev1api "k8s.io/api/core/v1" clientTesting "k8s.io/client-go/testing" ) @@ -64,6 +65,18 @@ func TestRestoreExpose(t *testing.T) { }, } + daemonSet := &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: "node-agent", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DaemonSetSpec{}, + } + tests := []struct { name string kubeClientObj []runtime.Object @@ -97,6 +110,7 @@ func TestRestoreExpose(t *testing.T) { ownerRestore: restore, kubeClientObj: []runtime.Object{ targetPVCObj, + daemonSet, }, kubeReactors: []reactor{ { @@ -116,6 +130,7 @@ func TestRestoreExpose(t *testing.T) { ownerRestore: restore, kubeClientObj: []runtime.Object{ targetPVCObj, + daemonSet, }, kubeReactors: []reactor{ { diff --git a/pkg/exposer/image.go b/pkg/exposer/image.go new file mode 100644 index 000000000..c29a59447 --- /dev/null +++ b/pkg/exposer/image.go @@ -0,0 +1,49 @@ +/* +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 exposer + +import ( + "context" + + "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + + "github.com/vmware-tanzu/velero/pkg/nodeagent" +) + +type inheritedPodInfo struct { + image string + serviceAccount string +} + +func getInheritedPodInfo(ctx context.Context, client kubernetes.Interface, veleroNamespace string) (inheritedPodInfo, error) { + podInfo := inheritedPodInfo{} + + podSpec, err := nodeagent.GetPodSpec(ctx, client, veleroNamespace) + if err != nil { + return podInfo, errors.Wrap(err, "error to get node-agent pod template") + } + + if len(podSpec.Containers) != 1 { + return podInfo, errors.Wrap(err, "unexpected pod template from node-agent") + } + + podInfo.image = podSpec.Containers[0].Image + podInfo.serviceAccount = podSpec.ServiceAccountName + + return podInfo, nil +} diff --git a/pkg/nodeagent/node_agent.go b/pkg/nodeagent/node_agent.go index b0d8e6725..83e76d2a4 100644 --- a/pkg/nodeagent/node_agent.go +++ b/pkg/nodeagent/node_agent.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "github.com/vmware-tanzu/velero/pkg/util/kube" @@ -73,3 +74,12 @@ func IsRunningInNode(ctx context.Context, namespace string, nodeName string, pod return errors.Errorf("daemonset pod not found in running state in node %s", nodeName) } + +func GetPodSpec(ctx context.Context, kubeClient kubernetes.Interface, namespace string) (*v1.PodSpec, error) { + ds, err := kubeClient.AppsV1().DaemonSets(namespace).Get(ctx, daemonSet, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "error to get node-agent daemonset") + } + + return &ds.Spec.Template.Spec, nil +} From 7cf3559fab080e2fefe9ff000b9409aea035ea2b Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 31 Jul 2023 19:44:17 +0800 Subject: [PATCH 09/26] fix issue 6560 Signed-off-by: Lyndon-Li --- pkg/apis/velero/v1/register.go | 13 +++++++++ pkg/apis/velero/v2alpha1/register.go | 13 +++++++++ pkg/cmd/server/server.go | 35 ++++++++++-------------- pkg/cmd/server/server_test.go | 41 ++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 29 deletions(-) diff --git a/pkg/apis/velero/v1/register.go b/pkg/apis/velero/v1/register.go index 13915293a..9b694f2b6 100644 --- a/pkg/apis/velero/v1/register.go +++ b/pkg/apis/velero/v1/register.go @@ -20,6 +20,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" ) // Resource gets a Velero GroupResource for a specified resource @@ -59,6 +60,18 @@ func CustomResources() map[string]typeInfo { } } +// CustomResourceKinds returns a list of all custom resources kinds within the Velero +func CustomResourceKinds() sets.String { + kinds := sets.NewString() + + resources := CustomResources() + for kind := range resources { + kinds.Insert(kind) + } + + return kinds +} + func addKnownTypes(scheme *runtime.Scheme) error { for _, typeInfo := range CustomResources() { scheme.AddKnownTypes(SchemeGroupVersion, typeInfo.ItemType, typeInfo.ItemListType) diff --git a/pkg/apis/velero/v2alpha1/register.go b/pkg/apis/velero/v2alpha1/register.go index 31b471281..d8945fa6a 100644 --- a/pkg/apis/velero/v2alpha1/register.go +++ b/pkg/apis/velero/v2alpha1/register.go @@ -20,6 +20,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" ) // Resource gets a Velero GroupResource for a specified resource @@ -50,6 +51,18 @@ func CustomResources() map[string]typeInfo { } } +// CustomResourceKinds returns a list of all custom resources kinds within the Velero +func CustomResourceKinds() sets.String { + kinds := sets.NewString() + + resources := CustomResources() + for kind := range resources { + kinds.Insert(kind) + } + + return kinds +} + func addKnownTypes(scheme *runtime.Scheme) error { for _, typeInfo := range CustomResources() { scheme.AddKnownTypes(SchemeGroupVersion, typeInfo.ItemType, typeInfo.ItemListType) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index a42c0589c..5f4b6e9a6 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -475,31 +475,26 @@ func (s *server) initDiscoveryHelper() error { func (s *server) veleroResourcesExist() error { s.logger.Info("Checking existence of Velero custom resource definitions") - var veleroGroupVersion *metav1.APIResourceList - for _, gv := range s.discoveryHelper.Resources() { - if gv.GroupVersion == velerov1api.SchemeGroupVersion.String() { - veleroGroupVersion = gv - break + // add more group versions whenever available + gvResources := map[string]sets.String{ + velerov1api.SchemeGroupVersion.String(): velerov1api.CustomResourceKinds(), + velerov2alpha1api.SchemeGroupVersion.String(): velerov2alpha1api.CustomResourceKinds(), + } + + for _, lst := range s.discoveryHelper.Resources() { + if resources, found := gvResources[lst.GroupVersion]; found { + for _, resource := range lst.APIResources { + s.logger.WithField("kind", resource.Kind).Info("Found custom resource") + resources.Delete(resource.Kind) + } } } - if veleroGroupVersion == nil { - return fmt.Errorf("velero API group %s not found. Apply examples/common/00-prereqs.yaml to create it", velerov1api.SchemeGroupVersion) - } - - foundResources := sets.NewString() - for _, resource := range veleroGroupVersion.APIResources { - foundResources.Insert(resource.Kind) - } - var errs []error - for kind := range velerov1api.CustomResources() { - if foundResources.Has(kind) { - s.logger.WithField("kind", kind).Debug("Found custom resource") - continue + for gv, resources := range gvResources { + for kind := range resources { + errs = append(errs, errors.Errorf("custom resource %s not found in Velero API group %s", kind, gv)) } - - errs = append(errs, errors.Errorf("custom resource %s not found in Velero API group %s", kind, velerov1api.SchemeGroupVersion)) } if len(errs) > 0 { diff --git a/pkg/cmd/server/server_test.go b/pkg/cmd/server/server_test.go index 4d7944434..fc487d3d1 100644 --- a/pkg/cmd/server/server_test.go +++ b/pkg/cmd/server/server_test.go @@ -32,8 +32,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/client/mocks" "github.com/vmware-tanzu/velero/pkg/controller" discovery_mocks "github.com/vmware-tanzu/velero/pkg/discovery/mocks" @@ -64,24 +64,40 @@ func TestVeleroResourcesExist(t *testing.T) { } assert.Error(t, server.veleroResourcesExist()) - // Velero API group doesn't contain any custom resources: should error - veleroAPIResourceList := &metav1.APIResourceList{ - GroupVersion: v1.SchemeGroupVersion.String(), + // Velero v1 API group doesn't contain any custom resources: should error + veleroAPIResourceListVelerov1 := &metav1.APIResourceList{ + GroupVersion: velerov1api.SchemeGroupVersion.String(), } - fakeDiscoveryHelper.ResourceList = append(fakeDiscoveryHelper.ResourceList, veleroAPIResourceList) + fakeDiscoveryHelper.ResourceList = append(fakeDiscoveryHelper.ResourceList, veleroAPIResourceListVelerov1) assert.Error(t, server.veleroResourcesExist()) - // Velero API group contains all custom resources: should not error - for kind := range v1.CustomResources() { - veleroAPIResourceList.APIResources = append(veleroAPIResourceList.APIResources, metav1.APIResource{ + // Velero v2alpha1 API group doesn't contain any custom resources: should error + veleroAPIResourceListVeleroV2alpha1 := &metav1.APIResourceList{ + GroupVersion: velerov2alpha1api.SchemeGroupVersion.String(), + } + + fakeDiscoveryHelper.ResourceList = append(fakeDiscoveryHelper.ResourceList, veleroAPIResourceListVeleroV2alpha1) + assert.Error(t, server.veleroResourcesExist()) + + // Velero v1 API group contains all custom resources, but v2alpha1 doesn't contain any custom resources: should error + for kind := range velerov1api.CustomResources() { + veleroAPIResourceListVelerov1.APIResources = append(veleroAPIResourceListVelerov1.APIResources, metav1.APIResource{ + Kind: kind, + }) + } + assert.Error(t, server.veleroResourcesExist()) + + // Velero v1 and v2alpha1 API group contain all custom resources: should not error + for kind := range velerov2alpha1api.CustomResources() { + veleroAPIResourceListVeleroV2alpha1.APIResources = append(veleroAPIResourceListVeleroV2alpha1.APIResources, metav1.APIResource{ Kind: kind, }) } assert.NoError(t, server.veleroResourcesExist()) // Velero API group contains some but not all custom resources: should error - veleroAPIResourceList.APIResources = veleroAPIResourceList.APIResources[:3] + veleroAPIResourceListVelerov1.APIResources = veleroAPIResourceListVelerov1.APIResources[:3] assert.Error(t, server.veleroResourcesExist()) } @@ -270,6 +286,13 @@ func Test_veleroResourcesExist(t *testing.T) { {Kind: "ServerStatusRequest"}, }, }, + { + GroupVersion: velerov2alpha1api.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Kind: "DataUpload"}, + {Kind: "DataDownload"}, + }, + }, }) assert.Nil(t, server.veleroResourcesExist()) } From 0da9134f1545804e9198a66b850ae9cc7c00bc21 Mon Sep 17 00:00:00 2001 From: Anshul Ahuja Date: Wed, 2 Aug 2023 10:35:01 +0530 Subject: [PATCH 10/26] Update restore-resource-modifiers.md Fix indent in resource modifier docs Signed-off-by: Anshul Ahuja Fix --- .../docs/main/restore-resource-modifiers.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/site/content/docs/main/restore-resource-modifiers.md b/site/content/docs/main/restore-resource-modifiers.md index 538237641..a7aa3a669 100644 --- a/site/content/docs/main/restore-resource-modifiers.md +++ b/site/content/docs/main/restore-resource-modifiers.md @@ -25,22 +25,22 @@ Below is the two-step of using resource modifiers to modify the resources during **YAML template** - Yaml template: - ```yaml - version: v1 - resourceModifierRules: - - conditions: - groupKind: persistentvolumeclaims - resourceNameRegex: "^mysql.*$" - namespaces: - - bar - - foo - patches: - - operation: replace - path: "/spec/storageClassName" - value: "premium" - - operation: remove - path: "/metadata/labels/test" - ``` +```yaml +version: v1 +resourceModifierRules: +- conditions: + groupKind: persistentvolumeclaims + resourceNameRegex: "^mysql.*$" + namespaces: + - bar + - foo + patches: + - operation: replace + path: "/spec/storageClassName" + value: "premium" + - operation: remove + path: "/metadata/labels/test" + ``` - The above configmap will apply the JSON Patch to all the PVCs in the namespaces bar and foo with name starting with mysql. The JSON Patch will replace the storageClassName with "premium" and remove the label "test" from the PVCs. - You can specify multiple JSON Patches for a particular resource. The patches will be applied in the order specified in the configmap. A subsequent patch is applied in order and if multiple patches are specified for the same path, the last patch will override the previous patches. From bb96c2155c010007641c54da2d1128f332fd9270 Mon Sep 17 00:00:00 2001 From: lyndon <98304688+Lyndon-Li@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:27:05 +0800 Subject: [PATCH 11/26] fix issue 6575 (#6587) Signed-off-by: Lyndon-Li --- changelogs/unreleased/6587-Lyndon-Li | 1 + pkg/repository/provider/unified_repo.go | 7 ++++- pkg/repository/provider/unified_repo_test.go | 33 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/6587-Lyndon-Li diff --git a/changelogs/unreleased/6587-Lyndon-Li b/changelogs/unreleased/6587-Lyndon-Li new file mode 100644 index 000000000..d72a27778 --- /dev/null +++ b/changelogs/unreleased/6587-Lyndon-Li @@ -0,0 +1 @@ +Fix issue 6575, flush the repo after delete the snapshot, otherwise, the changes(deleting repo snapshot) cannot be committed to the repo. \ No newline at end of file diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 592d271ac..8dc470185 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -67,7 +67,7 @@ const ( repoOpDescMaintain = "repo maintenance" repoOpDescForget = "forget" - repoConnectDesc = "unfied repo" + repoConnectDesc = "unified repo" ) // NewUnifiedRepoProvider creates the service provider for Unified Repo @@ -302,6 +302,11 @@ func (urp *unifiedRepoProvider) Forget(ctx context.Context, snapshotID string, p return errors.Wrap(err, "error to delete manifest") } + err = bkRepo.Flush(ctx) + if err != nil { + return errors.Wrap(err, "error to flush repo") + } + log.Debug("Forget snapshot complete") return nil diff --git a/pkg/repository/provider/unified_repo_test.go b/pkg/repository/provider/unified_repo_test.go index 8ca73127b..08f33eb57 100644 --- a/pkg/repository/provider/unified_repo_test.go +++ b/pkg/repository/provider/unified_repo_test.go @@ -783,6 +783,7 @@ func TestForget(t *testing.T) { backupRepo *reposervicenmocks.BackupRepo retFuncOpen []interface{} retFuncDelete interface{} + retFuncFlush interface{} credStoreReturn string credStoreError error expectedErr string @@ -843,6 +844,37 @@ func TestForget(t *testing.T) { }, expectedErr: "error to delete manifest: fake-error-3", }, + { + name: "flush fail", + getter: new(credmock.SecretStore), + credStoreReturn: "fake-password", + funcTable: localFuncTable{ + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { + return map[string]string{}, nil + }, + getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { + return map[string]string{}, nil + }, + }, + repoService: new(reposervicenmocks.BackupRepoService), + backupRepo: new(reposervicenmocks.BackupRepo), + retFuncOpen: []interface{}{ + func(context.Context, udmrepo.RepoOptions) udmrepo.BackupRepo { + return backupRepo + }, + + func(context.Context, udmrepo.RepoOptions) error { + return nil + }, + }, + retFuncDelete: func(context.Context, udmrepo.ID) error { + return nil + }, + retFuncFlush: func(context.Context) error { + return errors.New("fake-error-4") + }, + expectedErr: "error to flush repo: fake-error-4", + }, } for _, tc := range testCases { @@ -871,6 +903,7 @@ func TestForget(t *testing.T) { if tc.backupRepo != nil { backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(tc.retFuncDelete) + backupRepo.On("Flush", mock.Anything).Return(tc.retFuncFlush) backupRepo.On("Close", mock.Anything).Return(nil) } From 69bc84cf0c1a7d15f061564da32242b89347ee16 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Thu, 3 Aug 2023 12:31:34 +0800 Subject: [PATCH 12/26] fix issue 6571 Signed-off-by: Lyndon-Li --- changelogs/unreleased/6594-Lyndon-Li | 1 + pkg/controller/restore_controller.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/6594-Lyndon-Li diff --git a/changelogs/unreleased/6594-Lyndon-Li b/changelogs/unreleased/6594-Lyndon-Li new file mode 100644 index 000000000..67232c083 --- /dev/null +++ b/changelogs/unreleased/6594-Lyndon-Li @@ -0,0 +1 @@ +Fix issue #6571, fix the problem for restore item operation to set the errors correctly so that they can be recorded by Velero restore and then reflect the correct status for Velero restore. \ No newline at end of file diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index b118781eb..076baf85c 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -535,8 +535,8 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu // Completed yet. inProgressOperations, _, opsCompleted, opsFailed, errs := getRestoreItemOperationProgress(restoreReq.Restore, pluginManager, *restoreReq.GetItemOperationsList()) if len(errs) > 0 { - for err := range errs { - restoreLog.Error(err) + for _, err := range errs { + restoreErrors.Velero = append(restoreErrors.Velero, fmt.Sprintf("error from restore item operation: %v", err)) } } From 846f0de17892c99fa21a64a315f6765ecc0aac3b Mon Sep 17 00:00:00 2001 From: allenxu404 Date: Wed, 2 Aug 2023 17:46:56 +0800 Subject: [PATCH 13/26] enhance uninstall and backup deletion for restore finalizer Signed-off-by: allenxu404 --- pkg/cmd/cli/uninstall/uninstall.go | 160 +++++++++++++----- pkg/controller/backup_deletion_controller.go | 37 +++- .../backup_deletion_controller_test.go | 4 - 3 files changed, 152 insertions(+), 49 deletions(-) diff --git a/pkg/cmd/cli/uninstall/uninstall.go b/pkg/cmd/cli/uninstall/uninstall.go index ec2de648f..feff423a4 100644 --- a/pkg/cmd/cli/uninstall/uninstall.go +++ b/pkg/cmd/cli/uninstall/uninstall.go @@ -25,23 +25,31 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + appsv1api "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/labels" kubeerrs "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/wait" kbclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/cli" + "github.com/vmware-tanzu/velero/pkg/controller" "github.com/vmware-tanzu/velero/pkg/install" + kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube" ) +var gracefulDeletionMaximumDuration = 1 * time.Minute + // uninstallOptions collects all the options for uninstalling Velero from a Kubernetes cluster. type uninstallOptions struct { wait bool // deprecated @@ -117,7 +125,6 @@ func Run(ctx context.Context, kbClient kbclient.Client, namespace string) error } // CRDs - veleroLabelSelector := labels.SelectorFromSet(install.Labels()) opts := []kbclient.DeleteAllOfOption{ kbclient.InNamespace(namespace), @@ -160,8 +167,7 @@ func Run(ctx context.Context, kbClient kbclient.Client, namespace string) error } func deleteNamespace(ctx context.Context, kbClient kbclient.Client, namespace string) error { - // delete resources with finalizer attached first to ensure finalizer can be handled by corresponding controller and resources can be deleted successfully before controller's pod is deleted. - // otherwise the process of deleting namespace will get stuck in deleting those resources forever. + // Deal with resources with attached finalizers to ensure proper handling of those finalizers. if err := deleteResourcesWithFinalizer(ctx, kbClient, namespace); err != nil { return errors.Wrap(err, "Fail to remove finalizer from restores") } @@ -211,58 +217,136 @@ func deleteNamespace(ctx context.Context, kbClient kbclient.Client, namespace st return nil } +// A few things needed to be noticed here: +// 1. When we delete resources with attached finalizers, the corresponding controller will deal with the finalizer then resources can be deleted successfully. +// So it is important to delete these resources before deleting the pod that runs that controller. +// 2. The controller may encounter errors while handling the finalizer, in such case, the controller will keep trying until it succeeds. +// So it is important to set a timeout, once the process exceed the timeout, we will forcedly delete the resources by removing the finalizer from them, +// otherwise the deletion process may get stuck indefinitely. +// 3. There is only restore finalizer supported as of v1.12. If any new finalizers are added in the future, the corresponding deletion logic can be +// incorporated into this function. func deleteResourcesWithFinalizer(ctx context.Context, kbClient kbclient.Client, namespace string) error { - //check if restore crd exists + fmt.Println("Waiting for resource with attached finalizer to be deleted") + return deleteRestore(ctx, kbClient, namespace) +} + +func deleteRestore(ctx context.Context, kbClient kbclient.Client, namespace string) error { + // Check if restore crd exists, if it does not exist, return immediately. + var err error v1crd := &apiextv1.CustomResourceDefinition{} key := kbclient.ObjectKey{Name: "restores.velero.io"} - if err := kbClient.Get(ctx, key, v1crd); err != nil { + if err = kbClient.Get(ctx, key, v1crd); err != nil { if apierrors.IsNotFound(err) { return nil } else { - return err + return errors.Wrap(err, "Error getting restore crd") } } - // delete all the restores - restoreList := &velerov1api.RestoreList{} - if err := kbClient.List(ctx, restoreList, &kbclient.ListOptions{Namespace: namespace}); err != nil { - return err + // First attempt to gracefully delete all the restore within a specified time frame, If the process exceeds the timeout limit, + // it is likely that there may be errors during the finalization of restores. In such cases, we should proceed with forcefully deleting the restores. + err = gracefullyDeleteRestore(ctx, kbClient, namespace) + if err != nil && err != wait.ErrWaitTimeout { + return errors.Wrap(err, "Error deleting restores") } + if err == wait.ErrWaitTimeout { + err = forcedlyDeleteRestore(ctx, kbClient, namespace) + if err != nil { + return errors.Wrap(err, "Error deleting restores") + } + } + return nil +} + +func gracefullyDeleteRestore(ctx context.Context, kbClient kbclient.Client, namespace string) error { + var err error + restoreList := &velerov1api.RestoreList{} + if err = kbClient.List(ctx, restoreList, &kbclient.ListOptions{Namespace: namespace}); err != nil { + return errors.Wrap(err, "Error getting restores during graceful deletion") + } + for i := range restoreList.Items { - if err := kbClient.Delete(ctx, &restoreList.Items[i]); err != nil { + if err = kbClient.Delete(ctx, &restoreList.Items[i]); err != nil { if apierrors.IsNotFound(err) { continue + } + return errors.Wrap(err, "Error deleting restores during graceful deletion") + } + } + + // Wait for the deletion of all the restores within a specified time frame + err = wait.PollImmediate(time.Second, gracefulDeletionMaximumDuration, func() (bool, error) { + restoreList := &velerov1api.RestoreList{} + if errList := kbClient.List(ctx, restoreList, &kbclient.ListOptions{Namespace: namespace}); errList != nil { + return false, errList + } + + if len(restoreList.Items) > 0 { + fmt.Print(".") + return false, nil + } else { + return true, nil + } + }) + + return err +} + +func forcedlyDeleteRestore(ctx context.Context, kbClient kbclient.Client, namespace string) error { + // Delete velero deployment first in case: + // 1. finalizers will be added back by restore controller after they are removed at next step; + // 2. new restores attached with finalizer will be created by restore controller after we remove all the restores' finalizer at next step; + deploy := &appsv1api.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "velero", + Name: namespace, + }, + } + + err := kbClient.Delete(ctx, deploy) + if err != nil && !apierrors.IsNotFound(err) { + return errors.Wrap(err, "Error deleting velero deployment during force deletion") + } + + ctxc, cancel := context.WithCancel(ctx) + defer cancel() + + checkFunc := func() { + deploy := &appsv1api.Deployment{} + key := kbclient.ObjectKey{Namespace: namespace, Name: "velero"} + + if err = kbClient.Get(ctxc, key, deploy); err != nil { + if apierrors.IsNotFound(err) { + err = nil + } + cancel() + return + } + } + + // Wait until velero deployment are deleted. + wait.Until(checkFunc, 100*time.Millisecond, ctxc.Done()) + if err != nil { + return errors.Wrap(err, "Error deleting velero deployment during force deletion") + } + + // Remove all the restores' finalizer so they can be deleted during the deletion of velero namespace. + restoreList := &velerov1api.RestoreList{} + if err := kbClient.List(ctx, restoreList, &kbclient.ListOptions{Namespace: namespace}); err != nil { + return errors.Wrap(err, "Error getting restores during force deletion") + } + + for i := range restoreList.Items { + if controllerutil.ContainsFinalizer(&restoreList.Items[i], controller.ExternalResourcesFinalizer) { + update := &restoreList.Items[i] + original := update.DeepCopy() + controllerutil.RemoveFinalizer(update, controller.ExternalResourcesFinalizer) + if err := kubeutil.PatchResource(original, update, kbClient); err != nil { + return errors.Wrap(err, "Error removing restore finalizer during force deletion") } - return err } } - fmt.Println("Waiting for resource with finalizer attached to be deleted") - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - var err error - checkFunc := func() { - restoreList := &velerov1api.RestoreList{} - if err = kbClient.List(ctx, restoreList, &kbclient.ListOptions{Namespace: namespace}); err != nil { - cancel() - return - } - - if len(restoreList.Items) > 0 { - fmt.Print(".") - } else { - cancel() - return - } - } - - // wait until all the restores are deleted - wait.Until(checkFunc, 100*time.Millisecond, ctx.Done()) - if err != nil { - return err - } - return nil } diff --git a/pkg/controller/backup_deletion_controller.go b/pkg/controller/backup_deletion_controller.go index a654958be..5c9168a29 100644 --- a/pkg/controller/backup_deletion_controller.go +++ b/pkg/controller/backup_deletion_controller.go @@ -34,7 +34,9 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" kubeerrs "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/clock" + ctrl "sigs.k8s.io/controller-runtime" "github.com/vmware-tanzu/velero/internal/credentials" @@ -363,24 +365,45 @@ func (r *backupDeletionReconciler) Reconcile(ctx context.Context, req ctrl.Reque }); err != nil { log.WithError(errors.WithStack(err)).Error("Error listing restore API objects") } else { + // Restore files in object storage will be handled by restore finalizer, so we simply need to initiate a delete request on restores here. for i, restore := range restoreList.Items { if restore.Spec.BackupName != backup.Name { continue } restoreLog := log.WithField("restore", kube.NamespaceAndName(&restoreList.Items[i])) - restoreLog.Info("Deleting restore log/results from backup storage") - if err := backupStore.DeleteRestore(restore.Name); err != nil { - errs = append(errs, err.Error()) - // if we couldn't delete the restore files, don't delete the API object - continue - } - restoreLog.Info("Deleting restore referencing backup") if err := r.Delete(ctx, &restoreList.Items[i]); err != nil { errs = append(errs, errors.Wrapf(err, "error deleting restore %s", kube.NamespaceAndName(&restoreList.Items[i])).Error()) } } + + // Wait for the deletion of restores within certain amount of time. + // Notice that there could be potential errors during the finalization process, which may result in the failure to delete the restore. + // Therefore, it is advisable to set a timeout period for waiting. + err := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) { + restoreList := &velerov1api.RestoreList{} + if err := r.List(ctx, restoreList, &client.ListOptions{Namespace: backup.Namespace, LabelSelector: selector}); err != nil { + return false, err + } + cnt := 0 + for _, restore := range restoreList.Items { + if restore.Spec.BackupName != backup.Name { + continue + } + cnt++ + } + + if cnt > 0 { + return false, nil + } else { + return true, nil + } + }) + if err != nil { + log.WithError(err).Error("Error polling for deletion of restores") + errs = append(errs, errors.Wrapf(err, "error deleting restore %s", err).Error()) + } } if len(errs) == 0 { diff --git a/pkg/controller/backup_deletion_controller_test.go b/pkg/controller/backup_deletion_controller_test.go index edf9d0879..c808f249d 100644 --- a/pkg/controller/backup_deletion_controller_test.go +++ b/pkg/controller/backup_deletion_controller_test.go @@ -322,8 +322,6 @@ func TestBackupDeletionControllerReconcile(t *testing.T) { td.backupStore.On("GetBackupVolumeSnapshots", input.Spec.BackupName).Return(snapshots, nil) td.backupStore.On("GetBackupContents", input.Spec.BackupName).Return(io.NopCloser(bytes.NewReader([]byte("hello world"))), nil) td.backupStore.On("DeleteBackup", input.Spec.BackupName).Return(nil) - td.backupStore.On("DeleteRestore", "restore-1").Return(nil) - td.backupStore.On("DeleteRestore", "restore-2").Return(nil) _, err := td.controller.Reconcile(context.TODO(), td.req) require.NoError(t, err) @@ -363,8 +361,6 @@ func TestBackupDeletionControllerReconcile(t *testing.T) { assert.Nil(t, err) td.backupStore.AssertCalled(t, "DeleteBackup", input.Spec.BackupName) - td.backupStore.AssertCalled(t, "DeleteRestore", "restore-1") - td.backupStore.AssertCalled(t, "DeleteRestore", "restore-2") // Make sure snapshot was deleted assert.Equal(t, 0, td.volumeSnapshotter.SnapshotsTaken.Len()) From 1777bbe5b4a1e970bf06c9cd05295f6c79dcf440 Mon Sep 17 00:00:00 2001 From: Xun Jiang/Bruce Jiang <59276555+blackpiglet@users.noreply.github.com> Date: Fri, 4 Aug 2023 18:09:21 +0800 Subject: [PATCH 14/26] Add warning log for snapshot data mover fell backup to Velero native snapshot. (#6602) Enlarge throttle of UT case TestThrottle_ShouldOutput to avoid occasional CI failure due to timeout caused by test environment's CPU speed Signed-off-by: Xun Jiang --- pkg/backup/item_backupper.go | 5 +++++ pkg/uploader/kopia/progress_test.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index 4c1ba94e7..f1cafbfca 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -525,6 +525,11 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie // After that, this warning can be removed. if boolptr.IsSetToTrue(ib.backupRequest.Spec.SnapshotMoveData) { log.Warnf("VolumeSnapshotter plugin doesn't support data movement.") + + if features.IsEnabled(velerov1api.CSIFeatureFlag) && pv.Spec.CSI == nil { + log.Warn("Cannot use CSI data mover to handle PV, because PV doesn't contain CSI in spec.", + " Fall back to Velero native snapshot.") + } } if ib.backupRequest.ResPolicies != nil { diff --git a/pkg/uploader/kopia/progress_test.go b/pkg/uploader/kopia/progress_test.go index 7b349cbe1..2d17dd65e 100644 --- a/pkg/uploader/kopia/progress_test.go +++ b/pkg/uploader/kopia/progress_test.go @@ -37,7 +37,7 @@ func TestThrottle_ShouldOutput(t *testing.T) { expectedOutput bool }{ {interval: time.Second, expectedOutput: true}, - {interval: time.Second, throttle: time.Now().UnixNano() + int64(time.Nanosecond*10000), expectedOutput: false}, + {interval: time.Second, throttle: time.Now().UnixNano() + int64(time.Nanosecond*100000000), expectedOutput: false}, } p := new(Progress) for _, tc := range testCases { From c637057dabfdd58b912dd543cf37eaacf6c79d47 Mon Sep 17 00:00:00 2001 From: danfengl Date: Thu, 27 Jul 2023 08:04:07 +0000 Subject: [PATCH 15/26] The minimum worker node count of wowrkload cluster should be 2 Signed-off-by: danfengl --- test/e2e/backup/backup.go | 5 ++++- test/e2e/backups/sync_backups.go | 4 +++- test/e2e/backups/ttl.go | 8 ++++---- .../e2e/basic/api-group/enable_api_group_extentions.go | 6 ++++-- test/e2e/basic/api-group/enable_api_group_versions.go | 4 ++-- test/e2e/basic/pvc-selected-node-changing.go | 2 +- test/e2e/e2e_suite_test.go | 5 ++++- test/e2e/migration/migration.go | 10 +++++++--- test/e2e/upgrade/upgrade.go | 8 ++++++-- test/e2e/util/velero/install.go | 5 ++++- 10 files changed, 39 insertions(+), 18 deletions(-) diff --git a/test/e2e/backup/backup.go b/test/e2e/backup/backup.go index 529b6cb41..9ad7b94eb 100644 --- a/test/e2e/backup/backup.go +++ b/test/e2e/backup/backup.go @@ -19,6 +19,7 @@ import ( "context" "flag" "fmt" + "time" "github.com/google/uuid" . "github.com/onsi/ginkgo" @@ -68,7 +69,9 @@ func BackupRestoreTest(useVolumeSnapshots bool) { DeleteBackups(context.Background(), *veleroCfg.ClientToInstallVelero) }) if veleroCfg.InstallVelero { - err = VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace) + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + err = VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace) Expect(err).To(Succeed()) } } diff --git a/test/e2e/backups/sync_backups.go b/test/e2e/backups/sync_backups.go index a12a270c8..62f724e14 100644 --- a/test/e2e/backups/sync_backups.go +++ b/test/e2e/backups/sync_backups.go @@ -69,7 +69,9 @@ func BackupsSyncTest() { DeleteBackups(context.Background(), *VeleroCfg.ClientToInstallVelero) }) if VeleroCfg.InstallVelero { - Expect(VeleroUninstall(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace)).To(Succeed()) + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + Expect(VeleroUninstall(ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace)).To(Succeed()) } } diff --git a/test/e2e/backups/ttl.go b/test/e2e/backups/ttl.go index a60d95ea5..4cc2d5920 100644 --- a/test/e2e/backups/ttl.go +++ b/test/e2e/backups/ttl.go @@ -80,11 +80,11 @@ func TTLTest() { By("Clean backups after test", func() { DeleteBackups(context.Background(), *veleroCfg.ClientToInstallVelero) }) - if veleroCfg.InstallVelero { - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) - } - ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) defer ctxCancel() + if veleroCfg.InstallVelero { + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) + } Expect(DeleteNamespace(ctx, client, test.testNS, false)).To(Succeed(), fmt.Sprintf("Failed to delete the namespace %s", test.testNS)) } }) diff --git a/test/e2e/basic/api-group/enable_api_group_extentions.go b/test/e2e/basic/api-group/enable_api_group_extentions.go index 002833d55..40bd64000 100644 --- a/test/e2e/basic/api-group/enable_api_group_extentions.go +++ b/test/e2e/basic/api-group/enable_api_group_extentions.go @@ -73,13 +73,15 @@ func APIExtensionsVersionsTest() { }) if veleroCfg.InstallVelero { By("Uninstall Velero and delete CRD ", func() { + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) Expect(DeleteCRDByName(context.Background(), crdName)).To(Succeed()) Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) Expect(DeleteCRDByName(context.Background(), crdName)).To(Succeed()) }) diff --git a/test/e2e/basic/api-group/enable_api_group_versions.go b/test/e2e/basic/api-group/enable_api_group_versions.go index b2901be31..776c8966e 100644 --- a/test/e2e/basic/api-group/enable_api_group_versions.go +++ b/test/e2e/basic/api-group/enable_api_group_versions.go @@ -45,13 +45,11 @@ var veleroCfg VeleroConfig type apiGropuVersionsTest struct { name string - namespaces []string srcCrdYaml string srcCRs map[string]string tgtCrdYaml string tgtVer string cm *corev1api.ConfigMap - gvs map[string][]string want map[string]map[string]string } @@ -108,6 +106,8 @@ func APIGropuVersionsTest() { Context("When EnableAPIGroupVersions flag is set", func() { It("Should back up API group version and restore by version priority", func() { + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*60) + defer ctxCancel() Expect(runEnableAPIGroupVersionsTests( ctx, *veleroCfg.ClientToInstallVelero, diff --git a/test/e2e/basic/pvc-selected-node-changing.go b/test/e2e/basic/pvc-selected-node-changing.go index 847188e34..94db1c52c 100644 --- a/test/e2e/basic/pvc-selected-node-changing.go +++ b/test/e2e/basic/pvc-selected-node-changing.go @@ -91,7 +91,7 @@ func (p *PVCSelectedNodeChanging) CreateResources() error { By("Prepare ConfigMap data", func() { nodeNameList, err := GetWorkerNodes(p.Ctx) Expect(err).To(Succeed()) - Expect(len(nodeNameList) > 2).To(Equal(true)) + Expect(len(nodeNameList) >= 2).To(Equal(true)) for _, nodeName := range nodeNameList { if nodeName != p.oldNodeName { p.newNodeName = nodeName diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 1d3c4127d..c62d9e87f 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -22,6 +22,7 @@ import ( "flag" "fmt" "testing" + "time" . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/reporters" @@ -205,6 +206,8 @@ var _ = BeforeSuite(func() { var _ = AfterSuite(func() { if VeleroCfg.InstallVelero && !VeleroCfg.Debug { By("release test resources after testing") - Expect(VeleroUninstall(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace)).To(Succeed()) + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + Expect(VeleroUninstall(ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace)).To(Succeed()) } }) diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 0a35e00cb..c62d7f27f 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -73,7 +73,9 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) // need to uninstall Velero first in case of the affection of the existing global velero installation if veleroCfg.InstallVelero { By("Uninstall Velero", func() { - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) }) } @@ -86,13 +88,15 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) // }) if veleroCfg.InstallVelero { By(fmt.Sprintf("Uninstall Velero and delete sample workload namespace %s", migrationNamespace), func() { + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) DeleteNamespace(context.Background(), *veleroCfg.DefaultClient, migrationNamespace, true) Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) DeleteNamespace(context.Background(), *veleroCfg.StandbyClient, migrationNamespace, true) }) diff --git a/test/e2e/upgrade/upgrade.go b/test/e2e/upgrade/upgrade.go index f5102b79a..077823526 100644 --- a/test/e2e/upgrade/upgrade.go +++ b/test/e2e/upgrade/upgrade.go @@ -77,7 +77,9 @@ func BackupUpgradeRestoreTest(useVolumeSnapshots bool, veleroCLI2Version VeleroC // need to uninstall Velero first in case of the affection of the existing global velero installation if veleroCfg.InstallVelero { By("Uninstall Velero", func() { - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) }) } @@ -92,7 +94,9 @@ func BackupUpgradeRestoreTest(useVolumeSnapshots bool, veleroCLI2Version VeleroC }) if veleroCfg.InstallVelero { By("Uninstall Velero", func() { - Expect(VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) }) } diff --git a/test/e2e/util/velero/install.go b/test/e2e/util/velero/install.go index e0daab267..7d9ce3546 100644 --- a/test/e2e/util/velero/install.go +++ b/test/e2e/util/velero/install.go @@ -127,6 +127,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste RestoreHelperImage: veleroCfg.RestoreHelperImage, VeleroServerDebugMode: veleroCfg.VeleroServerDebugMode, }) + if err != nil { RunDebug(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace, "", "") return errors.WithMessagef(err, "Failed to install Velero in the cluster") @@ -536,7 +537,9 @@ func PrepareVelero(ctx context.Context, caseName string) error { ready, err := IsVeleroReady(context.Background(), VeleroCfg.VeleroNamespace, VeleroCfg.UseNodeAgent) if err != nil { fmt.Printf("error in checking velero status with %v", err) - VeleroUninstall(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace) + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) + defer ctxCancel() + VeleroUninstall(ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace) ready = false } if ready { From b51d1a020216f38e3fe7c91a8c2e77733194c5f8 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 7 Aug 2023 22:56:39 +0800 Subject: [PATCH 16/26] fix issue 6614 Signed-off-by: Lyndon-Li --- pkg/controller/data_download_controller.go | 2 +- pkg/controller/data_download_controller_test.go | 2 +- pkg/controller/data_upload_controller.go | 2 +- pkg/controller/data_upload_controller_test.go | 2 +- pkg/exposer/csi_snapshot.go | 13 ++++++++----- pkg/exposer/generic_restore.go | 13 ++++++++----- pkg/exposer/host_path.go | 12 ++++++------ pkg/exposer/host_path_test.go | 4 ++-- pkg/exposer/types.go | 2 +- 9 files changed, 29 insertions(+), 23 deletions(-) diff --git a/pkg/controller/data_download_controller.go b/pkg/controller/data_download_controller.go index a5947444e..06fce563f 100644 --- a/pkg/controller/data_download_controller.go +++ b/pkg/controller/data_download_controller.go @@ -255,7 +255,7 @@ func (r *DataDownloadReconciler) Reconcile(ctx context.Context, req ctrl.Request } func (r *DataDownloadReconciler) runCancelableDataPath(ctx context.Context, fsRestore datapath.AsyncBR, dd *velerov2alpha1api.DataDownload, res *exposer.ExposeResult, log logrus.FieldLogger) (reconcile.Result, error) { - path, err := exposer.GetPodVolumeHostPath(ctx, res.ByPod.HostingPod, res.ByPod.PVC, r.client, r.fileSystem, log) + path, err := exposer.GetPodVolumeHostPath(ctx, res.ByPod.HostingPod, res.ByPod.VolumeName, r.client, r.fileSystem, log) if err != nil { return r.errorOut(ctx, dd, err, "error exposing host path for pod volume", log) } diff --git a/pkg/controller/data_download_controller_test.go b/pkg/controller/data_download_controller_test.go index 726d9b6a0..0750240b7 100644 --- a/pkg/controller/data_download_controller_test.go +++ b/pkg/controller/data_download_controller_test.go @@ -343,7 +343,7 @@ func TestDataDownloadReconcile(t *testing.T) { } else if test.notNilExpose { hostingPod := builder.ForPod("test-ns", "test-name").Volumes(&corev1.Volume{Name: "test-pvc"}).Result() hostingPod.ObjectMeta.SetUID("test-uid") - ep.On("GetExposed", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&exposer.ExposeResult{ByPod: exposer.ExposeByPod{HostingPod: hostingPod, PVC: "test-pvc"}}, nil) + ep.On("GetExposed", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&exposer.ExposeResult{ByPod: exposer.ExposeByPod{HostingPod: hostingPod, VolumeName: "test-pvc"}}, nil) } else if test.isGetExposeErr { ep.On("GetExposed", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("Error to get restore exposer")) } diff --git a/pkg/controller/data_upload_controller.go b/pkg/controller/data_upload_controller.go index f52de9f9b..65857ca6a 100644 --- a/pkg/controller/data_upload_controller.go +++ b/pkg/controller/data_upload_controller.go @@ -250,7 +250,7 @@ func (r *DataUploadReconciler) Reconcile(ctx context.Context, req ctrl.Request) func (r *DataUploadReconciler) runCancelableDataUpload(ctx context.Context, fsBackup datapath.AsyncBR, du *velerov2alpha1api.DataUpload, res *exposer.ExposeResult, log logrus.FieldLogger) (reconcile.Result, error) { log.Info("Run cancelable dataUpload") - path, err := exposer.GetPodVolumeHostPath(ctx, res.ByPod.HostingPod, res.ByPod.PVC, r.client, r.fileSystem, log) + path, err := exposer.GetPodVolumeHostPath(ctx, res.ByPod.HostingPod, res.ByPod.VolumeName, r.client, r.fileSystem, log) if err != nil { return r.errorOut(ctx, du, err, "error exposing host path for pod volume", log) } diff --git a/pkg/controller/data_upload_controller_test.go b/pkg/controller/data_upload_controller_test.go index e17aad527..08c4dfadb 100644 --- a/pkg/controller/data_upload_controller_test.go +++ b/pkg/controller/data_upload_controller_test.go @@ -253,7 +253,7 @@ func (f *fakeSnapshotExposer) GetExposed(ctx context.Context, du corev1.ObjectRe if err != nil { return nil, err } - return &exposer.ExposeResult{ByPod: exposer.ExposeByPod{HostingPod: pod, PVC: dataUploadName}}, nil + return &exposer.ExposeResult{ByPod: exposer.ExposeByPod{HostingPod: pod, VolumeName: dataUploadName}}, nil } func (f *fakeSnapshotExposer) CleanUp(context.Context, corev1.ObjectReference, string, string) { diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index a98ee0ad5..d0dab6ca5 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -218,7 +218,7 @@ func (e *csiSnapshotExposer) GetExposed(ctx context.Context, ownerObject corev1. curLog.WithField("backup pvc", backupPVCName).Info("Backup PVC is bound") - return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, PVC: backupPVCName}}, nil + return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, VolumeName: pod.Spec.Volumes[0].Name}}, nil } func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1.ObjectReference, vsName string, sourceNamespace string) { @@ -345,6 +345,9 @@ func (e *csiSnapshotExposer) createBackupPVC(ctx context.Context, ownerObject co func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject corev1.ObjectReference, backupPVC *corev1.PersistentVolumeClaim, label map[string]string) (*corev1.Pod, error) { podName := ownerObject.Name + volumeName := string(ownerObject.UID) + containerName := string(ownerObject.UID) + podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace) if err != nil { return nil, errors.Wrap(err, "error to get inherited pod info from node-agent") @@ -370,20 +373,20 @@ func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject co Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: podName, + Name: containerName, Image: podInfo.image, ImagePullPolicy: corev1.PullNever, Command: []string{"/velero-helper", "pause"}, VolumeMounts: []corev1.VolumeMount{{ - Name: backupPVC.Name, - MountPath: "/" + backupPVC.Name, + Name: volumeName, + MountPath: "/" + volumeName, }}, }, }, ServiceAccountName: podInfo.serviceAccount, TerminationGracePeriodSeconds: &gracePeriod, Volumes: []corev1.Volume{{ - Name: backupPVC.Name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ ClaimName: backupPVC.Name, diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index 8881b12b1..18d837957 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -143,7 +143,7 @@ func (e *genericRestoreExposer) GetExposed(ctx context.Context, ownerObject core curLog.WithField("restore pvc", restorePVCName).Info("Restore PVC is bound") - return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, PVC: restorePVCName}}, nil + return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, VolumeName: pod.Spec.Volumes[0].Name}}, nil } func (e *genericRestoreExposer) CleanUp(ctx context.Context, ownerObject corev1.ObjectReference) { @@ -251,6 +251,9 @@ func (e *genericRestoreExposer) createRestorePod(ctx context.Context, ownerObjec restorePodName := ownerObject.Name restorePVCName := ownerObject.Name + volumeName := string(ownerObject.UID) + containerName := string(ownerObject.UID) + podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace) if err != nil { return nil, errors.Wrap(err, "error to get inherited pod info from node-agent") @@ -276,20 +279,20 @@ func (e *genericRestoreExposer) createRestorePod(ctx context.Context, ownerObjec Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: restorePodName, + Name: containerName, Image: podInfo.image, ImagePullPolicy: corev1.PullNever, Command: []string{"/velero-helper", "pause"}, VolumeMounts: []corev1.VolumeMount{{ - Name: restorePVCName, - MountPath: "/" + restorePVCName, + Name: volumeName, + MountPath: "/" + volumeName, }}, }, }, ServiceAccountName: podInfo.serviceAccount, TerminationGracePeriodSeconds: &gracePeriod, Volumes: []corev1.Volume{{ - Name: restorePVCName, + Name: volumeName, VolumeSource: corev1.VolumeSource{ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ ClaimName: restorePVCName, diff --git a/pkg/exposer/host_path.go b/pkg/exposer/host_path.go index 82f3e075e..458667d92 100644 --- a/pkg/exposer/host_path.go +++ b/pkg/exposer/host_path.go @@ -34,23 +34,23 @@ var getVolumeDirectory = kube.GetVolumeDirectory var singlePathMatch = kube.SinglePathMatch // GetPodVolumeHostPath returns a path that can be accessed from the host for a given volume of a pod -func GetPodVolumeHostPath(ctx context.Context, pod *corev1.Pod, pvcName string, +func GetPodVolumeHostPath(ctx context.Context, pod *corev1.Pod, volumeName string, cli ctrlclient.Client, fs filesystem.Interface, log logrus.FieldLogger) (datapath.AccessPoint, error) { - logger := log.WithField("pod name", pod.Name).WithField("pod UID", pod.GetUID()).WithField("pvc", pvcName) + logger := log.WithField("pod name", pod.Name).WithField("pod UID", pod.GetUID()).WithField("volume", volumeName) - volDir, err := getVolumeDirectory(ctx, logger, pod, pvcName, cli) + volDir, err := getVolumeDirectory(ctx, logger, pod, volumeName, cli) if err != nil { - return datapath.AccessPoint{}, errors.Wrapf(err, "error getting volume directory name for pvc %s in pod %s", pvcName, pod.Name) + return datapath.AccessPoint{}, errors.Wrapf(err, "error getting volume directory name for volume %s in pod %s", volumeName, pod.Name) } - logger.WithField("volDir", volDir).Info("Got volume for backup PVC") + logger.WithField("volDir", volDir).Info("Got volume dir") pathGlob := fmt.Sprintf("/host_pods/%s/volumes/*/%s", string(pod.GetUID()), volDir) logger.WithField("pathGlob", pathGlob).Debug("Looking for path matching glob") path, err := singlePathMatch(pathGlob, fs, logger) if err != nil { - return datapath.AccessPoint{}, errors.Wrapf(err, "error identifying unique volume path on host for pvc %s in pod %s", pvcName, pod.Name) + return datapath.AccessPoint{}, errors.Wrapf(err, "error identifying unique volume path on host for volume %s in pod %s", volumeName, pod.Name) } logger.WithField("path", path).Info("Found path matching glob") diff --git a/pkg/exposer/host_path_test.go b/pkg/exposer/host_path_test.go index 9300be6f0..f71518d2d 100644 --- a/pkg/exposer/host_path_test.go +++ b/pkg/exposer/host_path_test.go @@ -48,7 +48,7 @@ func TestGetPodVolumeHostPath(t *testing.T) { }, pod: builder.ForPod(velerov1api.DefaultNamespace, "fake-pod-1").Result(), pvc: "fake-pvc-1", - err: "error getting volume directory name for pvc fake-pvc-1 in pod fake-pod-1: fake-error-1", + err: "error getting volume directory name for volume fake-pvc-1 in pod fake-pod-1: fake-error-1", }, { name: "single path match fail", @@ -60,7 +60,7 @@ func TestGetPodVolumeHostPath(t *testing.T) { }, pod: builder.ForPod(velerov1api.DefaultNamespace, "fake-pod-2").Result(), pvc: "fake-pvc-1", - err: "error identifying unique volume path on host for pvc fake-pvc-1 in pod fake-pod-2: fake-error-2", + err: "error identifying unique volume path on host for volume fake-pvc-1 in pod fake-pod-2: fake-error-2", }, } diff --git a/pkg/exposer/types.go b/pkg/exposer/types.go index 91fe0d066..253256eb9 100644 --- a/pkg/exposer/types.go +++ b/pkg/exposer/types.go @@ -33,5 +33,5 @@ type ExposeResult struct { // ExposeByPod defines the result for the expose method that a hosting pod is created type ExposeByPod struct { HostingPod *corev1.Pod - PVC string + VolumeName string } From 22c88ba330fe8bb481b28512aabfb3a315748510 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 8 Aug 2023 12:05:56 +0800 Subject: [PATCH 17/26] data mover fail earlier for snapshot creation error Signed-off-by: Lyndon-Li --- pkg/util/csi/volume_snapshot.go | 11 ++++++- pkg/util/csi/volume_snapshot_test.go | 43 ++++++++++++++++++++++++++-- pkg/util/stringptr/stringptr.go | 27 +++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 pkg/util/stringptr/stringptr.go diff --git a/pkg/util/csi/volume_snapshot.go b/pkg/util/csi/volume_snapshot.go index b4585e3ed..91e560d4f 100644 --- a/pkg/util/csi/volume_snapshot.go +++ b/pkg/util/csi/volume_snapshot.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/vmware-tanzu/velero/pkg/util/boolptr" + "github.com/vmware-tanzu/velero/pkg/util/stringptr" snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned/typed/volumesnapshot/v1" @@ -53,7 +54,15 @@ func WaitVolumeSnapshotReady(ctx context.Context, snapshotClient snapshotter.Sna return false, errors.Wrapf(err, fmt.Sprintf("error to get volumesnapshot %s/%s", volumeSnapshotNS, volumeSnapshot)) } - if tmpVS.Status == nil || tmpVS.Status.BoundVolumeSnapshotContentName == nil || !boolptr.IsSetToTrue(tmpVS.Status.ReadyToUse) || tmpVS.Status.RestoreSize == nil { + if tmpVS.Status == nil { + return false, nil + } + + if tmpVS.Status.Error != nil { + return false, errors.Errorf("volume snapshot creation error %s", stringptr.GetString(tmpVS.Status.Error.Message)) + } + + if !boolptr.IsSetToTrue(tmpVS.Status.ReadyToUse) { return false, nil } diff --git a/pkg/util/csi/volume_snapshot_test.go b/pkg/util/csi/volume_snapshot_test.go index 37f202ef9..7a678deb9 100644 --- a/pkg/util/csi/volume_snapshot_test.go +++ b/pkg/util/csi/volume_snapshot_test.go @@ -31,6 +31,7 @@ import ( clientTesting "k8s.io/client-go/testing" "github.com/vmware-tanzu/velero/pkg/util/boolptr" + "github.com/vmware-tanzu/velero/pkg/util/stringptr" velerotest "github.com/vmware-tanzu/velero/pkg/test" ) @@ -55,6 +56,8 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, } + errMessage := "fake-snapshot-creation-error" + tests := []struct { name string clientObj []runtime.Object @@ -116,7 +119,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { err: "timed out waiting for the condition", }, { - name: "restore size is nil in status", + name: "ready to use is false", vsName: "fake-vs", namespace: "fake-ns", clientObj: []runtime.Object{ @@ -127,12 +130,48 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, Status: &snapshotv1api.VolumeSnapshotStatus{ BoundVolumeSnapshotContentName: &vscName, - ReadyToUse: boolptr.True(), + ReadyToUse: boolptr.False(), }, }, }, err: "timed out waiting for the condition", }, + { + name: "snapshot creation error with message", + vsName: "fake-vs", + namespace: "fake-ns", + clientObj: []runtime.Object{ + &snapshotv1api.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vs", + Namespace: "fake-ns", + }, + Status: &snapshotv1api.VolumeSnapshotStatus{ + Error: &snapshotv1api.VolumeSnapshotError{ + Message: &errMessage, + }, + }, + }, + }, + err: "volume snapshot creation error fake-snapshot-creation-error", + }, + { + name: "snapshot creation error without message", + vsName: "fake-vs", + namespace: "fake-ns", + clientObj: []runtime.Object{ + &snapshotv1api.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vs", + Namespace: "fake-ns", + }, + Status: &snapshotv1api.VolumeSnapshotStatus{ + Error: &snapshotv1api.VolumeSnapshotError{}, + }, + }, + }, + err: "volume snapshot creation error " + stringptr.NilString, + }, { name: "success", vsName: "fake-vs", diff --git a/pkg/util/stringptr/stringptr.go b/pkg/util/stringptr/stringptr.go new file mode 100644 index 000000000..faee7b99d --- /dev/null +++ b/pkg/util/stringptr/stringptr.go @@ -0,0 +1,27 @@ +/* +Copyright 2017 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stringptr + +const NilString = "" + +func GetString(str *string) string { + if str == nil { + return NilString + } else { + return *str + } +} From 5f463c59ec4753ef09a86411eee08ef5dbcb07a9 Mon Sep 17 00:00:00 2001 From: Qi Xu <107547364+allenxu404@users.noreply.github.com> Date: Wed, 9 Aug 2023 05:16:53 +0800 Subject: [PATCH 18/26] add "Deleting" phase for restore display (#6604) Signed-off-by: allenxu404 --- pkg/cmd/cli/restore/get.go | 7 +++++++ pkg/cmd/util/output/restore_describer.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/pkg/cmd/cli/restore/get.go b/pkg/cmd/cli/restore/get.go index 74ac42ad3..9a4014b25 100644 --- a/pkg/cmd/cli/restore/get.go +++ b/pkg/cmd/cli/restore/get.go @@ -59,6 +59,13 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { cmd.CheckError(err) } + // Append "(Deleting)" to phase if deletionTimestamp is marked. + for i := range restores.Items { + if !restores.Items[i].DeletionTimestamp.IsZero() { + restores.Items[i].Status.Phase += " (Deleting)" + } + } + if printed, err := output.PrintWithFormat(c, restores); printed || err != nil { cmd.CheckError(err) return diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index 13ac580b5..00d1304dc 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -45,6 +45,12 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel phase = velerov1api.RestorePhaseNew } phaseString := string(phase) + + // Append "Deleting" to phaseString if deletionTimestamp is marked. + if !restore.DeletionTimestamp.IsZero() { + phaseString += " (Deleting)" + } + switch phase { case velerov1api.RestorePhaseCompleted: phaseString = color.GreenString(phaseString) From e9c170cb15e5f7c7a59251aab044ed7a4803e07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Wed, 9 Aug 2023 14:08:01 +0800 Subject: [PATCH 19/26] Upgrade to v3 for the codecov action (#6622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade to v3 for the codecov action Signed-off-by: Wenkai Yin(尹文开) --- .github/workflows/pr-ci-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-ci-check.yml b/.github/workflows/pr-ci-check.yml index 3df5c4684..d71b602a6 100644 --- a/.github/workflows/pr-ci-check.yml +++ b/.github/workflows/pr-ci-check.yml @@ -24,8 +24,9 @@ jobs: - name: Make ci run: make ci - name: Upload test coverage - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.out verbose: true + fail_ci_if_error: true From 7a8a68d9e9abcefe59203df1032c074b785b0dd4 Mon Sep 17 00:00:00 2001 From: allenxu404 Date: Wed, 9 Aug 2023 15:46:22 +0800 Subject: [PATCH 20/26] modify output for command of `restore delete` Signed-off-by: allenxu404 --- pkg/cmd/cli/restore/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/cli/restore/delete.go b/pkg/cmd/cli/restore/delete.go index cd180d507..e59465409 100644 --- a/pkg/cmd/cli/restore/delete.go +++ b/pkg/cmd/cli/restore/delete.go @@ -123,7 +123,7 @@ func Run(o *cli.DeleteOptions) error { errs = append(errs, errors.WithStack(err)) continue } - fmt.Printf("Restore %q deleted\n", r.Name) + fmt.Printf("Request to delete restore %q submitted successfully.\nThe restore will be fully deleted after all associated data (restore files in object storage) are removed.\n", r.Name) } return kubeerrs.NewAggregate(errs) } From 28c5dc9fdabbdbe12c49e4effc640dede776adf4 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Thu, 3 Aug 2023 08:58:05 +0100 Subject: [PATCH 21/26] fix: load AWS config and assume role Signed-off-by: Luis Davim --- Dockerfile | 2 +- changelogs/unreleased/6598-aws_creds | 1 + pkg/repository/config/aws.go | 33 ++++++++++++++++++++++------ pkg/restic/common.go | 22 ++++++++++++++++++- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/6598-aws_creds diff --git a/Dockerfile b/Dockerfile index 2edd111ba..587a0e098 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ ARG TARGETARCH ARG TARGETVARIANT ARG RESTIC_VERSION -env CGO_ENABLED=0 \ +ENV CGO_ENABLED=0 \ GO111MODULE=on \ GOPROXY=${GOPROXY} \ GOOS=${TARGETOS} \ diff --git a/changelogs/unreleased/6598-aws_creds b/changelogs/unreleased/6598-aws_creds new file mode 100644 index 000000000..74aee6ee1 --- /dev/null +++ b/changelogs/unreleased/6598-aws_creds @@ -0,0 +1 @@ +Fix how the AWS credentials are obtained from configuration diff --git a/pkg/repository/config/aws.go b/pkg/repository/config/aws.go index 2acbbb502..c3a3f4dd8 100644 --- a/pkg/repository/config/aws.go +++ b/pkg/repository/config/aws.go @@ -33,8 +33,13 @@ import ( const ( // AWS specific environment variable awsProfileEnvVar = "AWS_PROFILE" + awsRoleEnvVar = "AWS_ROLE_ARN" + awsKeyIDEnvVar = "AWS_ACCESS_KEY_ID" + awsSecretKeyEnvVar = "AWS_SECRET_ACCESS_KEY" + awsSessTokenEnvVar = "AWS_SESSION_TOKEN" awsProfileKey = "profile" awsCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE" + awsConfigFileEnvVar = "AWS_CONFIG_FILE" ) // GetS3ResticEnvVars gets the environment variables that restic @@ -51,32 +56,46 @@ func GetS3ResticEnvVars(config map[string]string) (map[string]string, error) { result[awsProfileEnvVar] = profile } + // GetS3ResticEnvVars reads the AWS config, from files and envs + // if needed assumes the role and returns the session credentials + // setting these variables emulates what would happen for example when using kube2iam + if creds, err := GetS3Credentials(config); err == nil && creds != nil { + result[awsKeyIDEnvVar] = creds.AccessKeyID + result[awsSecretKeyEnvVar] = creds.SecretAccessKey + result[awsSessTokenEnvVar] = creds.SessionToken + result[awsCredentialsFileEnvVar] = "" + result[awsProfileEnvVar] = "" + result[awsConfigFileEnvVar] = "" + } + return result, nil } // GetS3Credentials gets the S3 credential values according to the information // of the provided config or the system's environment variables func GetS3Credentials(config map[string]string) (*credentials.Value, error) { - if len(os.Getenv("AWS_ROLE_ARN")) > 0 { + if os.Getenv(awsRoleEnvVar) != "" { return nil, nil } + opts := session.Options{} credentialsFile := config[CredentialsFileKey] if credentialsFile == "" { credentialsFile = os.Getenv("AWS_SHARED_CREDENTIALS_FILE") } - - if credentialsFile == "" { - return nil, errors.New("missing credential file") + if credentialsFile != "" { + opts.SharedConfigFiles = append(opts.SharedConfigFiles, credentialsFile) + opts.SharedConfigState = session.SharedConfigEnable } - creds := credentials.NewSharedCredentials(credentialsFile, config[awsProfileKey]) - credValue, err := creds.Get() + sess, err := session.NewSessionWithOptions(opts) if err != nil { return nil, err } - return &credValue, nil + creds, err := sess.Config.Credentials.Get() + + return &creds, err } // GetAWSBucketRegion returns the AWS region that a bucket is in, or an error diff --git a/pkg/restic/common.go b/pkg/restic/common.go index 26eb2ef27..a5bf05c44 100644 --- a/pkg/restic/common.go +++ b/pkg/restic/common.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "strconv" + "strings" "time" "github.com/pkg/errors" @@ -71,11 +72,26 @@ func TempCACertFile(caCert []byte, bsl string, fs filesystem.Interface) (string, return name, nil } +// environ is a slice of strings representing the environment, in the form "key=value". +type environ []string + +// Unset a single environment variable. +func (e *environ) Unset(key string) { + for i := range *e { + if strings.HasPrefix((*e)[i], key+"=") { + (*e)[i] = (*e)[len(*e)-1] + *e = (*e)[:len(*e)-1] + break + } + } +} + // CmdEnv returns a list of environment variables (in the format var=val) that // should be used when running a restic command for a particular backend provider. // This list is the current environment, plus any provider-specific variables restic needs. func CmdEnv(backupLocation *velerov1api.BackupStorageLocation, credentialFileStore credentials.FileStore) ([]string, error) { - env := os.Environ() + var env environ + env = os.Environ() customEnv := map[string]string{} var err error @@ -113,6 +129,10 @@ func CmdEnv(backupLocation *velerov1api.BackupStorageLocation, credentialFileSto } for k, v := range customEnv { + env.Unset(k) + if v == "" { + continue + } env = append(env, fmt.Sprintf("%s=%s", k, v)) } From a26f0b972f6e44c64fbf1d0a82b221ba1ced10d5 Mon Sep 17 00:00:00 2001 From: allenxu404 Date: Wed, 9 Aug 2023 15:26:16 +0800 Subject: [PATCH 22/26] add upgrade doc for 1.12 Signed-off-by: allenxu404 --- ...{upgrade-to-1.11.md => upgrade-to-1.12.md} | 54 +++++++++---------- site/data/docs/main-toc.yml | 4 +- 2 files changed, 29 insertions(+), 29 deletions(-) rename site/content/docs/main/{upgrade-to-1.11.md => upgrade-to-1.12.md} (68%) diff --git a/site/content/docs/main/upgrade-to-1.11.md b/site/content/docs/main/upgrade-to-1.12.md similarity index 68% rename from site/content/docs/main/upgrade-to-1.11.md rename to site/content/docs/main/upgrade-to-1.12.md index ee412a0fa..c974b1fd5 100644 --- a/site/content/docs/main/upgrade-to-1.11.md +++ b/site/content/docs/main/upgrade-to-1.12.md @@ -1,28 +1,28 @@ --- -title: "Upgrading to Velero 1.11" +title: "Upgrading to Velero 1.12" layout: docs --- ## Prerequisites -- Velero [v1.10.x][5] installed. +- Velero [v1.11.x][5] installed. -If you're not yet running at least Velero v1.6, see the following: +If you're not yet running at least Velero v1.7, see the following: -- [Upgrading to v1.6][1] -- [Upgrading to v1.7][2] -- [Upgrading to v1.8][3] -- [Upgrading to v1.9][4] -- [Upgrading to v1.10][5] +- [Upgrading to v1.7][1] +- [Upgrading to v1.8][2] +- [Upgrading to v1.9][3] +- [Upgrading to v1.10][4] +- [Upgrading to v1.11][5] Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions -**Caution:** From Velero v1.10, except for using restic to do file-system level backup and restore, kopia is also been integrated, it could be upgraded from v1.10 to v1.11 directly, but it would be a little bit of difference when upgrading to v1.11 from a version lower than v1.10.0. +**Caution:** From Velero v1.10, except for using restic to do file-system level backup and restore, kopia is also been integrated, it could be upgraded from v1.10 or higher to v1.12 directly, but it would be a little bit of difference when upgrading to v1.12 from a version lower than v1.10.0. ### Upgrade from version lower than v1.10.0 -1. Install the Velero v1.11 command-line interface (CLI) by following the [instructions here][0]. +1. Install the Velero v1.12 command-line interface (CLI) by following the [instructions here][0]. Verify that you've properly installed it by running: @@ -34,7 +34,7 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw ```bash Client: - Version: v1.11.0 + Version: v1.12.0 Git commit: ``` @@ -51,7 +51,7 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw ```bash # uploader_type value could be restic or kopia kubectl get deploy -n velero -ojson \ - | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.11.0\"#g" \ + | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.12.0\"#g" \ | sed "s#\"server\",#\"server\",\"--uploader-type=$uploader_type\",#g" \ | sed "s#default-volumes-to-restic#default-volumes-to-fs-backup#g" \ | sed "s#default-restic-prune-frequency#default-repo-maintain-frequency#g" \ @@ -60,7 +60,7 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw # optional, if using the restic daemon set echo $(kubectl get ds -n velero restic -ojson) \ - | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.11.0\"#g" \ + | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.12.0\"#g" \ | sed "s#\"name\"\: \"restic\"#\"name\"\: \"node-agent\"#g" \ | sed "s#\[ \"restic\",#\[ \"node-agent\",#g" \ | kubectl apply -f - @@ -77,41 +77,41 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw ```bash Client: - Version: v1.11.0 + Version: v1.12.0 Git commit: Server: - Version: v1.11.0 + Version: v1.12.0 ``` -### Upgrade from v1.10 -If it's directly upgraded from v1.10, the other steps remain the same only except for step 3 above. The details as below: +### Upgrade from v1.10 or higher +If it's directly upgraded from v1.10 or higher, the other steps remain the same only except for step 3 above. The details as below: -3. Update the container image used by the Velero deployment, plugin and, optionally, the node agent daemon set: +1. Update the container image used by the Velero deployment, plugin and, optionally, the node agent daemon set: ```bash # set the container and image of the init container for plugin accordingly, # if you are using other plugin kubectl set image deployment/velero \ - velero=velero/velero:v1.11.0 \ - velero-plugin-for-aws=velero/velero-plugin-for-aws:v1.7.0 \ + velero=velero/velero:v1.12.0 \ + velero-plugin-for-aws=velero/velero-plugin-for-aws:v1.8.0 \ --namespace velero # optional, if using the node agent daemonset kubectl set image daemonset/node-agent \ - node-agent=velero/velero:v1.11.0 \ + node-agent=velero/velero:v1.12.0 \ --namespace velero ``` ## Notes -If upgraded from v1.9.x, there still remains some resources left over in the cluster and never used in v1.11.x, which could be deleted through kubectl and it is based on your desire: +If upgraded from v1.9.x, there still remains some resources left over in the cluster and never used in v1.12.x, which could be deleted through kubectl and it is based on your desire: - resticrepository CRD and related CRs - velero-restic-credentials secret in velero install namespace [0]: basic-install.md#install-the-cli -[1]: https://velero.io/docs/v1.6/upgrade-to-1.6 -[2]: https://velero.io/docs/v1.7/upgrade-to-1.7 -[3]: https://velero.io/docs/v1.8/upgrade-to-1.8 -[4]: https://velero.io/docs/v1.9/upgrade-to-1.9 -[5]: https://velero.io/docs/v1.10/upgrade-to-1.10 +[1]: https://velero.io/docs/v1.7/upgrade-to-1.7 +[2]: https://velero.io/docs/v1.8/upgrade-to-1.8 +[3]: https://velero.io/docs/v1.9/upgrade-to-1.9 +[4]: https://velero.io/docs/v1.10/upgrade-to-1.10 +[5]: https://velero.io/docs/v1.11/upgrade-to-1.11 diff --git a/site/data/docs/main-toc.yml b/site/data/docs/main-toc.yml index 75ea67bc6..7b5e9154d 100644 --- a/site/data/docs/main-toc.yml +++ b/site/data/docs/main-toc.yml @@ -13,8 +13,8 @@ toc: url: /basic-install - page: Customize Installation url: /customize-installation - - page: Upgrade to 1.11 - url: /upgrade-to-1.11 + - page: Upgrade to 1.12 + url: /upgrade-to-1.12 - page: Supported providers url: /supported-providers - page: Evaluation install From 0ec0c963d2247e5c6a9e6623dc661239e0aa418c Mon Sep 17 00:00:00 2001 From: Daniel Kucera Date: Thu, 10 Aug 2023 04:44:15 +0200 Subject: [PATCH 23/26] Allow updated resource version (#6499) Signed-off-by: Daniel Kucera --- pkg/restore/restore.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index cee3216f6..df6edb6ec 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1366,6 +1366,14 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso } } + // The object apiVersion might get modified by a RestorePlugin so we need to + // get a new client to reflect updated resource path. + resourceClient, err = ctx.getResourceClient(groupResource, obj, namespace) + if err != nil { + errs.AddVeleroError(fmt.Errorf("error getting updated resource client for namespace %q, resource %q: %v", namespace, &groupResource, err)) + return warnings, errs, itemExists + } + ctx.log.Infof("Attempting to restore %s: %v", obj.GroupVersionKind().Kind, name) createdObj, restoreErr := resourceClient.Create(obj) if restoreErr == nil { From b90c5bba3d533d51d8a1133b18a9571a6a9b9704 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Wed, 9 Aug 2023 12:54:43 +0800 Subject: [PATCH 24/26] continue wait on csi snapshot error Signed-off-by: Lyndon-Li --- pkg/exposer/csi_snapshot.go | 2 +- pkg/util/csi/volume_snapshot.go | 14 ++++++++++++-- pkg/util/csi/volume_snapshot_test.go | 14 +++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index d0dab6ca5..df9b08546 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -94,7 +94,7 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1.Obje curLog.Info("Exposing CSI snapshot") - volumeSnapshot, err := csi.WaitVolumeSnapshotReady(ctx, e.csiSnapshotClient, csiExposeParam.SnapshotName, csiExposeParam.SourceNamespace, csiExposeParam.Timeout) + volumeSnapshot, err := csi.WaitVolumeSnapshotReady(ctx, e.csiSnapshotClient, csiExposeParam.SnapshotName, csiExposeParam.SourceNamespace, csiExposeParam.Timeout, curLog) if err != nil { return errors.Wrapf(err, "error wait volume snapshot ready") } diff --git a/pkg/util/csi/volume_snapshot.go b/pkg/util/csi/volume_snapshot.go index 91e560d4f..b9b134fde 100644 --- a/pkg/util/csi/volume_snapshot.go +++ b/pkg/util/csi/volume_snapshot.go @@ -26,6 +26,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "github.com/vmware-tanzu/velero/pkg/util/boolptr" @@ -45,8 +46,9 @@ const ( // WaitVolumeSnapshotReady waits a VS to become ready to use until the timeout reaches func WaitVolumeSnapshotReady(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface, - volumeSnapshot string, volumeSnapshotNS string, timeout time.Duration) (*snapshotv1api.VolumeSnapshot, error) { + volumeSnapshot string, volumeSnapshotNS string, timeout time.Duration, log logrus.FieldLogger) (*snapshotv1api.VolumeSnapshot, error) { var updated *snapshotv1api.VolumeSnapshot + errMessage := sets.NewString() err := wait.PollImmediate(waitInternal, timeout, func() (bool, error) { tmpVS, err := snapshotClient.VolumeSnapshots(volumeSnapshotNS).Get(ctx, volumeSnapshot, metav1.GetOptions{}) @@ -59,7 +61,7 @@ func WaitVolumeSnapshotReady(ctx context.Context, snapshotClient snapshotter.Sna } if tmpVS.Status.Error != nil { - return false, errors.Errorf("volume snapshot creation error %s", stringptr.GetString(tmpVS.Status.Error.Message)) + errMessage.Insert(stringptr.GetString(tmpVS.Status.Error.Message)) } if !boolptr.IsSetToTrue(tmpVS.Status.ReadyToUse) { @@ -70,6 +72,14 @@ func WaitVolumeSnapshotReady(ctx context.Context, snapshotClient snapshotter.Sna return true, nil }) + if err == wait.ErrWaitTimeout { + err = errors.Errorf("volume snapshot is not ready until timeout, errors: %v", errMessage.List()) + } + + if errMessage.Len() > 0 { + log.Warnf("Some errors happened during waiting for ready snapshot, errors: %v", errMessage.List()) + } + return updated, err } diff --git a/pkg/util/csi/volume_snapshot_test.go b/pkg/util/csi/volume_snapshot_test.go index 7a678deb9..66e76b10c 100644 --- a/pkg/util/csi/volume_snapshot_test.go +++ b/pkg/util/csi/volume_snapshot_test.go @@ -84,7 +84,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", }, { name: "vsc is nil in status", @@ -99,7 +99,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { Status: &snapshotv1api.VolumeSnapshotStatus{}, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", }, { name: "ready to use is nil in status", @@ -116,7 +116,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", }, { name: "ready to use is false", @@ -134,7 +134,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", }, { name: "snapshot creation error with message", @@ -153,7 +153,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "volume snapshot creation error fake-snapshot-creation-error", + err: "volume snapshot is not ready until timeout, errors: [fake-snapshot-creation-error]", }, { name: "snapshot creation error without message", @@ -170,7 +170,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "volume snapshot creation error " + stringptr.NilString, + err: "volume snapshot is not ready until timeout, errors: [" + stringptr.NilString + "]", }, { name: "success", @@ -187,7 +187,7 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { t.Run(test.name, func(t *testing.T) { fakeSnapshotClient := snapshotFake.NewSimpleClientset(test.clientObj...) - vs, err := WaitVolumeSnapshotReady(context.Background(), fakeSnapshotClient.SnapshotV1(), test.vsName, test.namespace, time.Millisecond) + vs, err := WaitVolumeSnapshotReady(context.Background(), fakeSnapshotClient.SnapshotV1(), test.vsName, test.namespace, time.Millisecond, velerotest.NewLogger()) if err != nil { assert.EqualError(t, err, test.err) } else { From 1784f63b93ee03306b3e1a6cd1add7b04fbeae81 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 10 Aug 2023 18:46:14 +0800 Subject: [PATCH 25/26] get resource client again after restore actions in case resource's gv is changed Signed-off-by: lou --- pkg/restore/restore.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index df6edb6ec..2cc9c5622 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1368,7 +1368,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso // The object apiVersion might get modified by a RestorePlugin so we need to // get a new client to reflect updated resource path. - resourceClient, err = ctx.getResourceClient(groupResource, obj, namespace) + newGR := schema.GroupResource{Group: obj.GroupVersionKind().Group, Resource: groupResource.Resource} + resourceClient, err = ctx.getResourceClient(newGR, obj, obj.GetNamespace()) if err != nil { errs.AddVeleroError(fmt.Errorf("error getting updated resource client for namespace %q, resource %q: %v", namespace, &groupResource, err)) return warnings, errs, itemExists From 797267c89a9bd33844e1d7fa665bbadf1facd184 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 11 Aug 2023 10:11:24 +0800 Subject: [PATCH 26/26] add changelog Signed-off-by: lou --- changelogs/unreleased/6634-27149chen | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/unreleased/6634-27149chen diff --git a/changelogs/unreleased/6634-27149chen b/changelogs/unreleased/6634-27149chen new file mode 100644 index 000000000..61e71665e --- /dev/null +++ b/changelogs/unreleased/6634-27149chen @@ -0,0 +1 @@ +Fixes #6498. Get resource client again after restore actions in case resource's gv is changed. This is an improvement of pr #6499, to support group changes. A group change usually happens in a restore plugin which is used for resource conversion: convert a resource from a not supported gv to a supported gv \ No newline at end of file