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 diff --git a/Dockerfile b/Dockerfile index 7f600d0f2..587a0e098 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 @@ -52,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/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/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/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/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/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 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/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/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/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/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}" 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/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"` } 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/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/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) } 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/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/cmd/server/server.go b/pkg/cmd/server/server.go index e30d2b30a..fcd3c3d50 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -475,35 +475,30 @@ 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 { - 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) } @@ -586,13 +581,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 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()) } 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) 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()) 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 a0a9b645f..0750240b7 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) @@ -330,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 3e0c04606..08c4dfadb 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) @@ -240,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/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)) } } diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index 4d452a39e..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") } @@ -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,14 @@ 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") + } + var gracePeriod int64 = 0 pod := &corev1.Pod{ @@ -365,19 +373,20 @@ func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject co Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: podName, - Image: "alpine:latest", - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{"sleep", "infinity"}, + 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/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..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,14 @@ 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") + } + var gracePeriod int64 = 0 pod := &corev1.Pod{ @@ -271,19 +279,20 @@ func (e *genericRestoreExposer) createRestorePod(ctx context.Context, ownerObjec Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: restorePodName, - Image: "alpine:latest", - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{"sleep", "infinity"}, + 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/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/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/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/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 } 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 +} diff --git a/pkg/repository/config/aws.go b/pkg/repository/config/aws.go index 936389412..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, "") - 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/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) } 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)) } diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index cee3216f6..2cc9c5622 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1366,6 +1366,15 @@ 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. + 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 + } + ctx.log.Infof("Attempting to restore %s: %v", obj.GroupVersionKind().Kind, name) createdObj, restoreErr := resourceClient.Create(obj) if restoreErr == 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 { diff --git a/pkg/util/csi/volume_snapshot.go b/pkg/util/csi/volume_snapshot.go index b4585e3ed..b9b134fde 100644 --- a/pkg/util/csi/volume_snapshot.go +++ b/pkg/util/csi/volume_snapshot.go @@ -26,9 +26,11 @@ 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" + "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" @@ -44,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{}) @@ -53,7 +56,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 { + errMessage.Insert(stringptr.GetString(tmpVS.Status.Error.Message)) + } + + if !boolptr.IsSetToTrue(tmpVS.Status.ReadyToUse) { return false, nil } @@ -61,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 37f202ef9..66e76b10c 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 @@ -81,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", @@ -96,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", @@ -113,10 +116,10 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", }, { - name: "restore size is nil in status", + name: "ready to use is false", vsName: "fake-vs", namespace: "fake-ns", clientObj: []runtime.Object{ @@ -127,11 +130,47 @@ func TestWaitVolumeSnapshotReady(t *testing.T) { }, Status: &snapshotv1api.VolumeSnapshotStatus{ BoundVolumeSnapshotContentName: &vscName, - ReadyToUse: boolptr.True(), + ReadyToUse: boolptr.False(), }, }, }, - err: "timed out waiting for the condition", + err: "volume snapshot is not ready until timeout, errors: []", + }, + { + 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 is not ready until timeout, errors: [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 is not ready until timeout, errors: [" + stringptr.NilString + "]", }, { name: "success", @@ -148,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 { 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 + } +} 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..a7aa3a669 --- /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/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 620865c95..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 @@ -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 diff --git a/test/e2e/backup/backup.go b/test/e2e/backup/backup.go index 16a300384..267b92cd4 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 abb5ffafc..d7b3f9640 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 dff3e444f..93eb7a405 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 793331d69..cf33251f3 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 22095698a..b0e4b3d72 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, @@ -121,12 +121,12 @@ func runEnableAPIGroupVersionsTests(ctx context.Context, client TestClient, grou tests := []apiGropuVersionsTest{ { name: "Target and source cluster preferred versions match; Preferred version v1 is restored (Priority 1, Case A).", - srcCrdYaml: "testdata/enable_api_group_versions/case-a-source.yaml", + srcCrdYaml: "../testdata/enable_api_group_versions/case-a-source.yaml", srcCRs: map[string]string{ - "v1": "testdata/enable_api_group_versions/music_v1_rockband.yaml", - "v1alpha1": "testdata/enable_api_group_versions/music_v1alpha1_rockband.yaml", + "v1": "../testdata/enable_api_group_versions/music_v1_rockband.yaml", + "v1alpha1": "../testdata/enable_api_group_versions/music_v1alpha1_rockband.yaml", }, - tgtCrdYaml: "testdata/enable_api_group_versions/case-a-target.yaml", + tgtCrdYaml: "../testdata/enable_api_group_versions/case-a-target.yaml", tgtVer: "v1", cm: nil, want: map[string]map[string]string{ @@ -140,13 +140,13 @@ func runEnableAPIGroupVersionsTests(ctx context.Context, client TestClient, grou }, { name: "Latest common non-preferred supported version v2beta2 is restored (Priority 3, Case D).", - srcCrdYaml: "testdata/enable_api_group_versions/case-b-source-manually-added-mutations.yaml", + srcCrdYaml: "../testdata/enable_api_group_versions/case-b-source-manually-added-mutations.yaml", srcCRs: map[string]string{ - "v2beta2": "testdata/enable_api_group_versions/music_v2beta2_rockband.yaml", - "v2beta1": "testdata/enable_api_group_versions/music_v2beta1_rockband.yaml", - "v1": "testdata/enable_api_group_versions/music_v1_rockband.yaml", + "v2beta2": "../testdata/enable_api_group_versions/music_v2beta2_rockband.yaml", + "v2beta1": "../testdata/enable_api_group_versions/music_v2beta1_rockband.yaml", + "v1": "../testdata/enable_api_group_versions/music_v1_rockband.yaml", }, - tgtCrdYaml: "testdata/enable_api_group_versions/case-d-target-manually-added-mutations.yaml", + tgtCrdYaml: "../testdata/enable_api_group_versions/case-d-target-manually-added-mutations.yaml", tgtVer: "v2beta2", cm: nil, want: map[string]map[string]string{ @@ -160,25 +160,25 @@ func runEnableAPIGroupVersionsTests(ctx context.Context, client TestClient, grou }, { name: "No common supported versions means no rockbands custom resource is restored.", - srcCrdYaml: "testdata/enable_api_group_versions/case-a-source.yaml", + srcCrdYaml: "../testdata/enable_api_group_versions/case-a-source.yaml", srcCRs: map[string]string{ - "v1": "testdata/enable_api_group_versions/music_v1_rockband.yaml", - "v1alpha1": "testdata/enable_api_group_versions/music_v1alpha1_rockband.yaml", + "v1": "../testdata/enable_api_group_versions/music_v1_rockband.yaml", + "v1alpha1": "../testdata/enable_api_group_versions/music_v1alpha1_rockband.yaml", }, - tgtCrdYaml: "testdata/enable_api_group_versions/case-b-target-manually-added-mutations.yaml", + tgtCrdYaml: "../testdata/enable_api_group_versions/case-b-target-manually-added-mutations.yaml", tgtVer: "", cm: nil, want: nil, }, { name: "User config map overrides Priority 3, Case D and restores v2beta1", - srcCrdYaml: "testdata/enable_api_group_versions/case-b-source-manually-added-mutations.yaml", + srcCrdYaml: "../testdata/enable_api_group_versions/case-b-source-manually-added-mutations.yaml", srcCRs: map[string]string{ - "v2beta2": "testdata/enable_api_group_versions/music_v2beta2_rockband.yaml", - "v2beta1": "testdata/enable_api_group_versions/music_v2beta1_rockband.yaml", - "v1": "testdata/enable_api_group_versions/music_v1_rockband.yaml", + "v2beta2": "../testdata/enable_api_group_versions/music_v2beta2_rockband.yaml", + "v2beta1": "../testdata/enable_api_group_versions/music_v2beta1_rockband.yaml", + "v1": "../testdata/enable_api_group_versions/music_v1_rockband.yaml", }, - tgtCrdYaml: "testdata/enable_api_group_versions/case-d-target-manually-added-mutations.yaml", + tgtCrdYaml: "../testdata/enable_api_group_versions/case-d-target-manually-added-mutations.yaml", tgtVer: "v2beta1", cm: builder.ForConfigMap(veleroCfg.VeleroNamespace, "enableapigroupversions").Data( "restoreResourcesVersionPriority", @@ -195,9 +195,9 @@ func runEnableAPIGroupVersionsTests(ctx context.Context, client TestClient, grou }, { name: "Restore successful when CRD doesn't (yet) exist in target", - srcCrdYaml: "testdata/enable_api_group_versions/case-a-source.yaml", + srcCrdYaml: "../testdata/enable_api_group_versions/case-a-source.yaml", srcCRs: map[string]string{ - "v1": "testdata/enable_api_group_versions/music_v1_rockband.yaml", + "v1": "../testdata/enable_api_group_versions/music_v1_rockband.yaml", }, tgtCrdYaml: "", tgtVer: "v1", diff --git a/test/e2e/basic/pvc-selected-node-changing.go b/test/e2e/basic/pvc-selected-node-changing.go index 0fbfe9963..d26350f2b 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 745331312..d050b37dd 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 0760a1756..1cb1398dd 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 993700732..6bc23c070 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/util/velero/install.go b/test/util/velero/install.go index 40821072b..fcf2a2090 100644 --- a/test/util/velero/install.go +++ b/test/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")