From 2f059146d39eae518c8df6f0d005bea6916727a0 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Thu, 30 Nov 2023 12:51:43 -0500 Subject: [PATCH] Schedule SkipImmediately Signed-off-by: Tiger Kaovilai --- changelogs/unreleased/7169-kaovilai | 1 + config/crd/v1/bases/velero.io_schedules.yaml | 15 ++++ config/crd/v1/crds/crds.go | 2 +- pkg/apis/velero/v1/schedule_types.go | 12 ++++ pkg/apis/velero/v1/zz_generated.deepcopy.go | 9 +++ pkg/builder/schedule_builder.go | 6 ++ pkg/cmd/cli/install/install.go | 4 ++ pkg/cmd/cli/schedule/create.go | 4 ++ pkg/cmd/cli/schedule/pause.go | 22 +++++- pkg/cmd/cli/schedule/skip_options.go | 20 ++++++ pkg/cmd/cli/schedule/unpause.go | 5 +- pkg/cmd/server/server.go | 5 +- pkg/controller/schedule_controller.go | 66 +++++++++++++----- pkg/controller/schedule_controller_test.go | 72 ++++++++++++++++---- pkg/install/deployment.go | 11 +++ pkg/install/resources.go | 2 + 16 files changed, 219 insertions(+), 37 deletions(-) create mode 100644 changelogs/unreleased/7169-kaovilai create mode 100644 pkg/cmd/cli/schedule/skip_options.go diff --git a/changelogs/unreleased/7169-kaovilai b/changelogs/unreleased/7169-kaovilai new file mode 100644 index 00000000000..0865b569125 --- /dev/null +++ b/changelogs/unreleased/7169-kaovilai @@ -0,0 +1 @@ +Add `--skip-immediately` flag to schedule commands; `--schedule-skip-immediately` server and install \ No newline at end of file diff --git a/config/crd/v1/bases/velero.io_schedules.yaml b/config/crd/v1/bases/velero.io_schedules.yaml index 0cf3cbe52b1..ec217c0b93e 100644 --- a/config/crd/v1/bases/velero.io_schedules.yaml +++ b/config/crd/v1/bases/velero.io_schedules.yaml @@ -61,6 +61,16 @@ spec: description: Schedule is a Cron expression defining when to run the Backup. type: string + skipImmediately: + description: 'SkipImmediately specifies whether to skip backup if + schedule is due immediately from `schedule.status.lastBackup` timestamp + when schedule is unpaused or if schedule is new. If true, backup + will be skipped immediately when schedule is unpaused if it is due + based on .Status.LastBackupTimestamp or schedule is new, and will + run at next schedule time. If false, backup will not be skipped + immediately when schedule is unpaused, but will run at next schedule + time. If empty, will follow server configuration (default: false).' + type: boolean template: description: Template is the definition of the Backup to be run on the provided schedule @@ -549,6 +559,11 @@ spec: format: date-time nullable: true type: string + lastSkipped: + description: LastSkipped is the last time a Schedule was skipped + format: date-time + nullable: true + type: string phase: description: Phase is the current phase of the Schedule enum: diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index 65391cd5421..9dc147d4a75 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -37,7 +37,7 @@ var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\xdc6\xf2\xbf\xebSt9\a\xfdSerb\xff\xb7\xb6\xb6\xe6f\xcb\xeb-\xed&\xb2ʒ}I\xe5\x80!\x9a$\"\x12@\x00p\xa4\xd9T\xbe\xfbV\x03\x04\x87\x0f̌F\xb5\xce\xf2\"\r\x1e\xdd?4\xfa\x8d,\xcb.\x98\x16_\xd1X\xa1\xe4\x1a\x98\x16\xf8\xe4P\xd2/\x9b?\xfc\xcd\xe6B\xad\xb6o.\x1e\x84\xe4k\xb8\xea\xacS\xedg\xb4\xaa3\x05~\xc0RHᄒ\x17-:ƙc\xeb\v\x00&\xa5r\x8c\x86-\xfd\x04(\x94tF5\r\x9a\xacB\x99?t\x1b\xdct\xa2\xe1h<\xf1\xc8z\xfbC\xfe\xe6m\xfe\xc3\x05\x80d-\xaeA+\xbeUM\xd7\xe2\x86\x15\x0f\x9d\xb6\xf9\x16\x1b4*\x17\xea\xc2j,\x88veT\xa7װ\x9f\b{{\xbe\x01\xf3\xad\xe2_=\x99\xf7\x9e\x8c\x9fi\x84u\xffJ\xcd\xfe(\xac\xf3+t\xd3\x19\xd6,A\xf8I+d\xd55\xcc,\xa6/\x00l\xa14\xae\xe1\x86`hV \xbf\x00\xe8\x8f\xe8ae\xc08\xf7Bcͭ\x11ҡ\xb9\"\nQX\x19p\xb4\x85\x11\xday\xa1\xdc*\x0e\x01 \x04\x84`\x1ds\x9d\x05\xdb\x1550\v7\xf8\xb8\xba\x96\xb7FU\x06m\x80\a\xf0\xabU\xf2\x96\xb9z\ryX\x9e\xeb\x9aY\xecg\x83x\xef\xfcD?\xe4v\x04\xda:#d\x95\x82q/Z\x84\xc7\x1a%\xb8ZX\b\xa7\x85Gf\t\x8eq\xfe\x94i\xc6~\x9e\xb6[\xc7Z=Ape\x90\xed\xb7\x06\b\x9c9L\x01\x18\xe4\t\xaa\x04W#I\xde+\x16\x13R\xc8\xca\x0f\x85\x9b\x00\xa7`\x83\x1e\"r\xe8t\x02\x99\xc6\"\u05ca\xe72\x12\x9d\xc0\xba\x99\x8d\x9e\x92\r\xad\xffo\xa3\x9a\x00\xbaU\xfc\x05P\xce\xe2\x1b\x16O\xb8~\x1d\x0f\x9dԏ\x1a\xfd\x9aȼӍb\x1c\r\xb1\xaf\x99\xe4\r\xd2\xcd2p\x86I[\xa29\x00#n\xbb\xdf\xe9)\x98/\x91\xdeh\xe6\x1ca\xf4\xb6s\xe7\x94a\x15\u008f\xaa\xf0\x0e\x8aT\xda\xe0D\xa7m\xad\xba\x86\xc3&r\x01\xb0N\x99\xa4\x82\x13Ⱛ\xa7\x1b\xc9\xce\xecl\xca\xf30\xfa\x11\xed\xe8O\xf3\x82lD(\x99\xb6\xa0w\x15\xa6\xad'Lo\xdf\x04wU\xd4زu\xbfRi\x94\xefn\xaf\xbf\xfe\xff\xddd\x18@\x1b\xa5\xd18\x11\xddg\xf8F\xc1a4\nSQ_\x12\xc1\xb0\n8E\x05\xb4A\a\xc3\x18\xf2\x1eC\xb8\x0ea\xc1\xa06hQ\xba\xb1H\xe2\xa7J`\x12\xd4\xe6W,\\\x0ewh\x88L\xbc\x98B\xc9-\x1a\a\x06\vUI\xf1\uf076%]#\xa6\rs\xd8{\xf1\xfd\xe7\x1d\xadd\rlY\xd3\xe1k`\x92C\xcbv`\x90\xb8@'G\xf4\xfc\x12\x9b\xc3O\xca \bY\xaa5\xd4\xcei\xbb^\xad*\xe1bP,T\xdbvR\xb8\xdd\xca\xc77\xb1\xe9\x9c2v\xc5q\x8b\xcdʊ*c\xa6\xa8\x85\xc3\xc2u\x06WL\x8b\xccC\x97>0\xe6-\xff\xce\xf4a\xd4^N\xb0.\x14#|>\x98\x1d\xb9\x01\ng ,\xb0~k8\xc5^\xd0\xd1\x1d}\xfe\xfb\xdd=D\xd6\xfe2\xe6\xd2\xf7r\xdfo\xb4\xfb+ \x81\tY\x92Y\xd3%\x96F\xb5\x9e&J\xae\x95\x90\xce\xff(\x1a\x81r.~\xdbmZ\xe1\xe8\xde\x7f\xeb\xd0:\xba\xab\x1c\xae|\xa6@\xee\xa9Ӥ\xb9<\x87k\tW\xac\xc5\xe6\x8aY\xfc\xe6\x17@\x92\xb6\x19\t\xf6yW0Nr拃\xd4F\x131E9p_\xb3\xbc\xe3NcA\xb7G\x02\xa4\x9d\xa2\x14\xbd\x87*\x95\x016_\x9eO\b\xa7\r\x97\xbe\xa4w\x9a/\x9a!{\x9f\xda\x13\xb1ɑO\x8d\x0e3\xac\\\x10\x05h\xe6^v\xd8cP++\x9c2;\"\x1c\x1cl\xbe\xa0p\xe0\x1a蓊\xe3\x89s\xdc(\x8e)ش\x15\\͂\xb6R~E\xfe\xa8\x93rɅ>%\xcf\x02\xa6\x15?\x81\xab\xe7\xc8\xc0`\x89\x06e\x81\xd1q\x1dK\x1e\x12\xc8\xc6a}\x89\xf1\xb0R\xc0\x11\xaf\x9eD\xfc\xee\xf6:z\xf2(\xc4\x1e\xbb[\xf2=!\x1f\xfaJ\x81\r\xf7\x81\xee4\xef\xcb\xeb20\xf3>\xcd)`\xa0\x05\x864p\b\x12 \xa4u\xc88\xa82I\x91j\x12 \xc37\xd8\xefx\x1d\xad\x032R\xa8\xca{b\x8a\x9a\xa5\xa0l\x86Ҙ\x10\x8b\xbd6.\x82y\xfcl\x17\xd4\xc7)(j&+\f\xe7E(;\x8a\x8e\xf9\xe5K\xecx\x99\x92\xc4/\x91\x9a\xcc\x1d\xc7\xff,\xb8?\xf3p>\x83~\xc6\xe1\xc6U\xc6\xd1\xc3=t\x1b4\x12\x1d\xfa\xf3qUX:Z\x81\xdaٕڢ\xd9\n|\\=*\xf3 d\x95\x91jfA\a\xecʗ\xa9\xab\xef\xfc\x9f\x17\x9f\xc5W\xb4\xcf=Ф\xd2\xfe\x96\xa7\">v\xf5\xa2C\xc5\x1c\xf6\xf9q\xec\xf2\xaeϬ\xe6{\xc9,\x1ekQԱ8\xe9}\xec\x01c\x12\x94\t\xf3\xe0\x9a\x99\xdc}sU&\x81v\x86\x10\xed\xb2\xbe\x97\x961\xc9\xe9\x7f+\xac\xa3\xf1\x17I\xb0\x13\xcf2\xdf/\xd7\x1f\xfe\x1c\x05\xefċl\xf5@\x02\x1e\xbe\xa7l\x0f+k\x99\xce\xc2j\xe6T+\x8a\xd9j\xcaJ\xaf9\t\xbe\x14hN\xa4q\x9f'\x8bc\xa2\x99\xc8o\x875g呎U\x89\xc4m\xdc:<\x96\xde\x1d\x95״q\xc3*\v\xcc 0h\x99\xa6{~\xc0]\x16\x12\x02\xcd\x04Es\n\xd8CW\x04\x98֍H\x06\xee>\xec\xf7)k/\t*\xcbYe\x0f\x9d=yk\xb1\vt\xa5d)\xaa\x13\xf7\xf0e\xb2x\xc8\tlL;JQuf_J\x8d\x9bS\xe7&͚\x19\xd64\xd8|\x14\r\xda\xc0\xf7\x19\xf6s\xbb\xdc5\x94%]\xbbACB/ir`p\xc0\xef\x04\xdc>\xbf\xd2hJe\xdaВ\xedl,\x17\x0e\x9fl/r!\x1dVC\a\xee\xac\v\xb9\xa7%ϻ\x0eZ\x1a\x8fy\xa21\xe8ꔣ\x9d\xb4\v\x97\xe7AٵK(\x19<(-Xbܠu\v\x83\xa7\x89W\xcbD\xf1\x88\xf5\x04\xe5>!\x83\xbe_\x9f\xa8={\xdb\b\x89\xfaP\x7f\xfa\x16m\xaa\xc0;\\\xdd\x1d\x84h\xf0\xb7\x8eʎ)\xc4,\xdd\t\x98\xad\xa1\xcax6\xa4\x15\xbf\x98\vr\xec\xf7f\x93\x936\xf2\x18\xe9\xb2=\xe2_\a\xceh\x90\x84W\x8f^\xa6!\x1c\xba\xf8\x16B\xb5\xe0K[$\x85\xa22k\xd2b=q\xbdW\xcb\x1d\xbe\x1bix\xaf\xee\xa2%w:z#\xe9y\xa4z\x1c0\"\x17v\xfa\x04\x84\xa8!\xf75\x10\x95h%\x13\rr\x88\x0fa\xf3=\t\xaac*\x1b,)Z\aӋ\xae\xa2\x877\xd4\x195\x82\xf5m\xbeK{\x84fg\x91{?\x9a\x10²\xf6 \x17\xc5\\hKgI\xa2\xb2k\x1a\xb6ip\r\xcet\xcb\xe9#\x96آ\xb5\xac:e\x8a?\x85U\xa1\xe1\xd2o\x01\xb6Q\x9d\x1b:.\x93xui{\x9d:\xaf\xe9\x93\xece̢\x00՚\xb6\xaf\xb9\x9a\xc6\xef\x19;\x82\xfd#\xa9G\xb5\xc1t\xce\xf6\x12\x9f\x00\xe0_\xffN!\xa45)\x03\x1b\xbc\xd7Q\v\x83#N\xf9\x06\x1f\x13\xa3\x8bW\xcb\xf1\xe4U4\x99\xc4\xdcGo\rg\x9d\xbfgtJ\x04\xfd2\xa8U\x13\x8dY9\u058c\x02\xf5f\xe7\xd0N\xddy\xaa\xbd\xe6\xcb\xf2\xbd\x18G\xfb\xe3\xfd\x05J}\xa7\xa1`ҷ\xc1ɺ\x9c\x02.\xacn\xd8.\x95\x82D\x84\x94z\x93q\x91\v\xd8\xebs4j\x8d!\xe597\xc3\xf1\x98>(y\xa0P\x8c\xf6,\xa4\xfb\xeb_^\x90b@\x10\xe7{\xe2\xf2m8\x1cIb\xacd\xda\xd6\xca]\x7f8\xa1\x05w\xc3\xc2h\r\xfb\x1c~\xc8!#\xb5^\x15RW5\xf8\x96\xb3Lu\xfa^~\n\xead\xf1\x89(Կԧb\xd0\x1dR\xea\xe90<\xe9\\\xcd\xdf\x1c_\x83\x15\xbeOM\xa5@\xa8\rB\x17\xc9Rp\xa2\xd4J\x19L\xb8LX\x86\x95I\x10\x99\xc2\xff3\xe3GRO\x16\x83\x1e9\x1f\xd1\xee\xdf:\xc6#\xddfx\xc7[\xc3\xef\x7f\\\xfc'\x00\x00\xff\xffo\x05\xb2\xa4Q#\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4Y_\x93۶\x11\x7fק\xd8q\x1e\xae\x991\xa9\xd8\xedt:z\xb3\xef\x9aε\xc9Yc\x9d\xfd\x92\xc9\x03D\xacHD$\x80\x02\xa0tj&߽\xb3\x00A\xf1\x9f\xa4\xd3M.\xe1\x8b}\xc0b\xf1\xc3\x0f\xfb\x0f\xab$IfL\x8b\xafh\xacPr\x01L\v|r(\xe9/\x9bn\xffaS\xa1\xe6\xbbw\xb3\xad\x90|\x01\xb7\xb5u\xaa\xfa\x8cV\xd5&\xc3;\xdc\b)\x9cPrV\xa1c\x9c9\xb6\x98\x010)\x95c4l\xe9O\x80LIgTY\xa2Ir\x94\xe9\xb6^\xe3\xba\x16%G\xe3\x95ǭwߥ\xefާ\xdf\xcd\x00$\xabp\x01Z\xf1\x9d*\xeb\n\rZ\xa7\f\xdat\x87%\x1a\x95\n5\xb3\x1a3R\x9e\x1bU\xeb\x05\x1c'\xc2\xe2f\xe3\x00z\xa9\xf8W\xaf\xe7s\xd0\xe3\xa7Ja\xdd\x7f&\xa7\x7f\x10\xd6y\x11]ֆ\x95\x138\xfc\xac\x152\xafKf\xc6\xf33\x00\x9b)\x8d\vx (\x9ae\xc8g\x00\xcd9=\xb4\x04\x18\xe7\x9e9V.\x8d\x90\x0e\xcd-\xa9\x88\x8c%\xc0\xd1fFh\xe7\x99i\xf5\x80ڀ+\x90\xb6\xf4\xac2!\x85\xcc\xfdP\x80\x00N\xc1\x1a\xa1A½2\x80_\xac\x92K\xe6\x8a\x05\xa4D\\\xaa\x15Oe\xd4\xd9\xc8\x04\xce\x1f\x06\xa3\xee@\xe7\xb0\xce\b\x99\x9fB\xf6;\x83\xea\xe1Y*\xfeL$\x8f\x05z\x99\x88\xa6֥b\x1c\rm^0\xc9K\x042Pp\x86I\xbbAs\x02E\\\xf6x\xd0}$_\xa2\xbe\xce\xcc5\xec\\CE\x90\xedm\xff\xb5;tiߥ\xe2\xcd\x02h\x8c\x1a\xacc\xae\xb6`\xeb\xac\x00f\xe1\x01\xf7\xf3{\xb94*7h\xed\x04\f/\x9e\xea\x82\xd9>\x8e\x95\x9fx]\x1c\x1be*\xe6\x16 \xa4\xfb\xfb\xdfNck\x16\xa5N9V~<8\xb4=\xa4\x8f\xc3ဖ\x9c-o\xae\xffO\x81\xbb&HwJ\xf6y\xfd8\x18\x9d\x02\xdbQ\x1a\xe3m\x9a\x19\xf4\xa1\xf6QTh\x1d\xabtO뇼\xaf\x8f3\x17\x06\xc2\xf4\xee]\beY\x81\x15[4\x92J\xa3\xfc\xb0\xbc\xff\xfa\xd7Uo\x18@\x1b\xa5\xd18\x11\xa3k\xf8:ɣ3\n}foHa\x90\x02NY\x03mp\x8a0\x86\xbc\xc1\x10\x9cEX0\xa8\rZ\x94!\x8f\xf4\x14\x03\t1\tj\xfd\vf.\x85\x15\x1aR\x03\xb6Pu\xe9#\xd0\x0e\x8d\x03\x83\x99ʥ\xf8_\xabے\xefѦ%s\u0604\xf8\xe3\xe7c\xb0d%\xecXY\xe3[`\x92C\xc5\x0e`\x90v\x81Zv\xf4y\x11\x9b\u008fd!Bn\xd4\x02\n\xe7\xb4]\xcc\xe7\xb9p1if\xaa\xaaj)\xdca\xee\xf3\x9fX\xd7N\x19;\xe7\xb8\xc3rnE\x9e0\x93\x15\xc2a\xe6j\x83s\xa6E\xe2\xa1K\x9f8ӊ\x7fc\x9a4kozXGN\x17>\x9f\xeb\xce\xdc\x00%;\x10\x16X\xb34\x9c\xe2Ht\fٟ\xff\xb9z\x84\xb8\xb5\xbf\x8c!\xfb\x9e\xf7\xe3B{\xbc\x02\"L\xc8\r\x05]\xbačQ\x95\u05c9\x92k%\xa4\xf3\x7fd\xa5@9\xa4\xdf\xd6\xebJ8\xba\xf7\xff\xd6h\x1d\xddU\n\xb7\xbe\x92\xa0xYk\xb2\\\x9e½\x84[Vay\xcb,\xbe\xfa\x05\x10\xd36!b\x9fw\x05\xdd\"h(\x1cX\xebL\xc4\n\xe6\xc4}\r\xab\x92\x95ƌ\xae\x8f\x18\xa4\xa5b#2\xef\x1b\x14~\x80\x8d\xe4Ӟ\xeaiץoͲm\xadWN\x19\x96\xe3\x0f*\xe8\x1c\n\r\xb0}\x9cZ\x13\xc1\xc9N\xce\v\xca\xc1\x06ɑR\x802.\xde\x17h\xb0\xbbƠVV8e\x0e\xa48d\xcbt\xa4\xe1\xc4E\xf8#+~\xe1\x18\x14\xee\xbdC\x18ܠA\x99a\x8c\x10\xe7*\x99\x89St\x12\xfa\x18\xe2i\xea\xe1L\xf4\x9c\x04\xfcay\x1f#fd\xb8\x81\xee\xc6\xfb^\xa0\x87\xbe\x8d\xc0\x92\xfb\x84ry\xef\x9b\xfbM\xd8\xcc\xc7\x0e\xa7\x80\x81\x16\x18*\xd26\x18\x83\x90\xd6!\xe3\xa06\x93\x1a\xe9m\x00\xe4`\x06\x9b\x15oC\xa4hB\xd21\x84\x13\xf5\xc0(F\t\x0e\xff^}z\x98\xffk\x8a\xf9\xf6\x14\xc0\xb2\f\xad\xf5\xf9\x1a+\x94\xeem\x9b\xb39Za\x90S\xe1\x82iŤؠui\xb3\a\x1a\xfb\xd3\xfb\x9f\xa7\xd9\x03\xf8^\x19\xc0'V\xe9\x12߂\b\x8c\xb7\xe1/ڌ\xb0\x81\x8eV#\xec\x85+\xc40i\xb5\f\x90u5\xc7\xde\xfb\xe3:\xb6EP\xcdqk\x84Rlq\x01o|%x\x84\xf9+9\xd6ooNh\xfdKp\xa07$\xf4&\x80k\xf3]\xd7#\x8f ]\xc1\x1c8#\xf2\x1c\x8f\x85\xe8\xf0\xf3\xc1\x9bBⷠ\f1 UG\x85WL\xb7\x17\xe2\x11\xf2\x11\xe8\x9f\xde\xff|\x12q\x9f/\x10\x92\xe3\x13\xbc\a!\x037Z\xf1oSx\xf4\xd6q\x90\x8e=\xd1NY\xa1,\x9ebV\xc9\xf2\x10\xaa\xfd\x1d\x82U\x15\xc2\x1e\xcb2\t\xf5\x06\x87=;\x10\v\xf1\xe2\xc8\xde\x18hf\xdcYk\x8dU\xc6㧻O\x8b\x80\x8c\f*\xf7\xf1\x8e\xb2\xd3FP\xd5@\xe5B\xc8y\xde\x1aGI3~\xb6\x0e\xe6\xe3\x14d\x05\x939\x86\xf3\"lj\xcaB\xe9\xcdK\xfcx\x9c\xfa\xe37Q\x02\f\x03ǟ\x96D\x9fy8_\xa9>\xe3pݷ\xd6\xd9\xc3m\xeb5\x1a\x89\x0e\xfd\xf9\xb8\xca,\x1d-C\xed\xec\\\xed\xd0\xec\x04\xee\xe7{e\xb6B\xe6\t\x99f\x12l\xc0\xce\xfd\x93y\xfe\x8d\xff\xe7\xc5g\xf1\xaf\xeb\xe7\x1e\xa8\xf7\xe8\x7f\xcdS\xd1>v\xfe\xa2C\xc5Z\xf1\xf9y\xecf\xd5\x140õ\xe4\x16\xfbBdE|\x0441\xf6\x843\t\xaa8y\b\xcdL\x1e^ݔ\x89\xd0\xda\x10\xa2C\xd2\xf4\xb4\x12&9\xfd\xdf\n\xebh\xfcE\f\xd6\xe2Y\xee\xfb\xe5\xfe\xee\x8f1\xf0Z\xbc\xc8WO\x14\xba\xe1{J\x8e\xb0\x92\x8a\xe9$H3\xa7*\x91\r\xa4\xa9\xf6\xbb\xe7D\xfcF\xa0\xb9P\xc5}\xee\t\xc7*t\xa2\x8ale\xae*#\xadd\xda\x16\xca\xdd\xdf]\xc0\xb1j\x05#\x86\xe3u5\xc5c\xd45h\x02]\x87\xc7\xfb\xcb\xc3\xe9@\xd2\a\u0557\x8eȔ\x11\xb9O[\xad\xef\xfbW\x84d\x15\xeb6\xff\xba_Ŵ\x162\xbf\nk\xb7\x97v\x01藎hDy\xa1\x9b\xe7\x8a)\x9c\xbd\x1e\xdf\x18-ʺ\x1aCI`\xab\xb4`\x13\xe3tG#\xfb\xa4\x897\xe3\xba\xe6\f\x13\xc1\x00.pд\x9e&\xdeQ\x8d\xfd\x84\xbaҏ\xd0\xdb\xc5[\xd1t@\xbe֮\xe8\xd9MEr\x1fa2\xfd:\x1c\xc8h\xc5gCҺ.9\x98<:\xd4p\xa2o\xab\x83\xd9^K\xb4{\x9a\xf1\xc3\xda\xf7ۮyZ\x87\x1e_\xc3{\x88\xf0.v\xfe\xe8y\xf3\xe2\xc7u\xa6\xe8\xe9\xd0k\xcf]\xb0\x81\xdb\xf1\n\xdf\xc92\xbc\xf1\tQ\xa1\x7f\xb1\x86\xf6\xe4\x9eٸ\xc9\xd4}CG_X\xea\xb3*\xa9C\xee\v{zwl\x98(\x91C\xfb+\x8bo\xa5[\xdfҹ\x99\xaac\xa3\xa2\xda\"\xf7qc\x02\xf4x]\xec\x92r\xe60!\x15#\tY\x97%[\x97\xb8\x00g\xea\xf1\xf4\x19\xf7\xaa\xd0Z\x96_\xf2\xaf\x1f\x83Tx\xf37K\x80\xadU\xed\xdaG\x7f\xe3h\r\x157\xb6\xb1\x82\xeb\x1a\x0f\x05\xb3\x97\xa0,If\xca\xe2Z\x97?orp&\x94=\xe0~btԵ\xeeN\xdeF\x13\x9a\x98\xfb\xde[\xc7U\x044\x1b]\xe2\xa0\x11\x83B\x95Ѻ\x95\xa3\xa4TWk4D\x84o\x95GFb\xe0\x98\xea\xa2\xf8\xd7בɣ\x86\x18\v\x83\xaa\xe6=\x991雊d\xbfN\x01\x17V\x97\xec0\xa17\x9e\xc4\x17Xd\xbe\xe4GG\x8b\x89^H\xee\xef\xe7\xae\xed\xfe\xb4?\x05L\x97\x7fS?,L\xddB\xf7W\x82\xc1|\xfb\x1b\xc8\xeb\xecp\xa6䳎\x19\xf7ܰ\xb7\xea\t_\x8ax^\xf5t\xbc놮q\xa0\xeao\xf3GƨI\xa2F\x83\x1e9\xef\xe8n:\xa7ݑz\xdd\xfe.\xb0\x80_\x7f\x9b\xfd?\x00\x00\xff\xffg\b\x17r\xc1\x1f\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc=Msܸrw\xff\n\x94spR\xa5\x19?'\x97\x94n\x8a\xd7\xceN\xde[[%\xb9\xbcg\f\xd93\xc4\x13\tp\x01p\xe4I*\xff=\x85\xc6\a\xbf@\x12\x1ci\xf6\xed\x067q\x80\x06\xd0\xdd\xe8o@\x9b\xcd\xe6\r\xad\xd9w\x90\x8a\t~Kh\xcd\xe0\x87\x06n\xfeRۧ\x7fW[&ޟ>\xbcyb<\xbf%\x1f\x1b\xa5E\xf5\x00J42\x83\x9f\xe0\xc08\xd3L\xf07\x15h\x9aSMo\xdf\x10B9\x17\x9a\x9a\xcf\xca\xfcIH&\xb8\x96\xa2,An\x8e\xc0\xb7O\xcd\x1e\xf6\r+s\x90\b\xdcO}\xfa\xcb\xf6ÿn\xff\xf2\x86\x10N+\xb8%\x12\x94\x16\x12\xd4\xf6\x04%H\xb1e⍪!30\x8fR4\xf5-i\x7f\xb0c\xdc|v\xad\x0fv8~)\x99\xd2\x7f\xed~\xfd\x1bS\x1a\x7f\xa9\xcbFҲ\x9d\f?*ƏMIe\xf8\xfc\x86\x10\x95\x89\x1an\xc9\x173MM3\xc8\xdf\x10▎\xd3nܪO\x1f,\x88\xac\x80\x8a\xda\xf5\x10\"j\xe0w\xf7\xbb\xef\xff\xf6\xd8\xfbLH\x0e*\x93\xacֈ\x00\xb76\xc2\x14\xa1\xe4;\xee\xcd,\x00qMtA5\x91PKP\xc0\xb5\"\xba\x00B\xeb\xbad\x19\xa2:@$D\x1c\xc2(E\x0eRT-\xb4=͞\x9a\x9ahA(\xd1T\x1eA\x93\xbf6{\x90\x1c4(\x92\x95\x8d\xd2 \xb7\x01V-E\rR3\x8fX\xdb:\xec\xd2\xf9:\xd8\xcb;\xb3]ۋ\xe4\x86O\xc0.١\fr\x87!\xb3Z]0\xd5nm\xb8\x1d\xb7%ʉ\xd8\xff\x1d2\xbd%\x8f \r\x18\xa2\nє\xb9a\xaf\x13H\x83\x9cL\x1c9\xfb\xef\x00[\x99\x8d\x9aIK\xaa\xc1ѻm\x8ck\x90\x9c\x96\xe4D\xcb\x06n\b\xe59\xa9\xe8\x99H0\xb3\x90\x86w\xe0a\x17\xb5%\xbf y\xf8AܒB\xebZݾ\x7f\x7fd\xda\x1f\x93LTUÙ>\xbfG\x8eg\xfbF\v\xa9\xde\xe7p\x82\xf2\xbdb\xc7\r\x95Y\xc14d\xba\x91\xf0\x9e\xd6l\x83K\xe7xT\xb6U\xfeO\x81l\xefzk\xd5g\xc3yJKƏ\x9d\x1f\x90\xcdg(`\x18\xde\xf2\x92\x1djw\xd1\"\xda|2\xd8y\xf8\xf4\xf8\xad\xcbgL\r\xb1\x8fx\xef0_K\x02\x830\xc6\x0f -\x11\x91\xdb\fL\xe0y-\x18\xd7\xf8GV2\xe0C\xf4\xabf_1m\xe8\xfe[\x03\xca0\xb4ؒ\x8f(;\xc8\x1eHS\xe7TC\xbe%;N>\xd2\nʏT\xc1\xd5\t`0\xad6\x06\xb1i$芽ag\x8b\xb5\xce\x0f^xM\xd0˝\xfe\xc7\x1a\xb2މ1\xc3\xd8\xc1\x1dsr\x10\xb2'\x1c̐m\x0fh\xfcКfO\xbf\x91`\xc3_\x06K\xf9\x8f\xd0\xd1\xf0\x8fYD\xc3\xd9o\r\xa0\x88\xb3'\x16F\"e\x04\x92\xf8\xf5![lG\xbfO\xe0\xd44\xf8\x91\x95M\x0ey\x90\xb6\xa3\xbd\fV\xfci4\x00\xb5\x0ee\xdc\xf0\xbf\x11\xfffټ\xfdՈ\xd3Ȋ\xa9\x04b8\x90q\v\x8f0\x8e\x9b\x8db\xda4\xa6\xa1\x8a,nvw\x84\xf0\xa6,龄[\xa2e\x03\x13\x98\xa1R\xd2\xf3\x04b\xbc\nN\xc5K\xe8\xef\x04B\xc92\xe8*\n\x8b\x1a\xabd\xa8\x1c\xaf\x88\xfc\xc1\xb1\u0094\x11g~\x97\xf7\xa2d\xd9y\x115\xb1A\xfe\xb8\xb9\xc3\xe79x\x0f\x05=1!#[2'\xd2t\xed(\xd2V\x98\n#\xcb\x1c\x90\xfc\xb2\rG\x91U\b\xf1\xb4D\xfb\x9fM\x9fVj\x93\f\x8d\xb7\xb0\x15Gm\xa7D\xf7@\xe0\ad\x8d\x8e,\x93\x90\xbcA\x05\"$\xa9\x85\xd2\xd3t\x9f\x96=Ċ\x83)\xa6%sL3ڙ\x13\x95\x9erf\xa3=\xb1)8\x98\xb5V\x86rm_)\x1a\xdbw\xa8\xdf:\x18\x8fc\x84쩂\x9c\b\xc7\xf5M\t\xca͕#\xf9[\xb9r3\t:l\xdeZ\x1a%\xddCI\x14\x94\x90i!ǘL\xc1\xa7m)\xb2r\x02\x8f\x11\xa9\xd9g\xffvc3 \x89a\xf3\xe7\x82e\x855\x02\fo\"\x1c\x92\vP(8\x8c\xa1z\x9e\xda$Y\xa2\xbd\x9bdNt\xb4m\xe1L\r\xe1\xc5\xc4I\xdb\x12\xc4m\xdb\x16\x04\xefH\xb0\xb8\xefQ\xcdٶ\xff\x9f\x88\xf5\x9a\xe4\x02\xa6ݍ\x86\xbe.ӢSe\x8c\xfd݁@U\xeb\xf3\ra\xda\x7f]\x82H˲3\xff\x9f\x980\xeb9~7\x1c\xf9\xaa\x1c?K\x95%\x88\x86*a\xfa?!QPY<:]\x91L\x90\xbfuG\xdd\x10v\b\x04\xc9oȁ\x95\x1a\xe4\x802/:/\xaf\x81\x8c\x14}gZEuV|\xfaa,\x1b\xd5ƙ\x12\xf12\x1clMb\xef#\xf4\x15\xf3\x02\\\x82\xee+\x93PY\xb7\xf8\x1bb\xb3\xfd\x82\xfe\xc4ݗ\x9f \x9fC\x0fI\xe3\xbc\xd1F\xee\x06\x8b\xedN\xed\xec\xfc\xd4m8\xd3'\xf8L6\xe0qC(y\x82\xb3\xb5X('\x868T\xa3\xbd\x1b\xf5\x9e\xc6\xc8\xc1\xc8\v2\xd9\x13\x9c\x11\x8c\v\xa5,\x8eNe\x05۞ b\xee\xc7Z\x0f\x81fM\xce\xc1\xb5\x984\x1f\x10\x11\xe8x\xa7#\x8f`X\xccˢ\xe5͑tA\xe2\x9b\xc7\xfd\x05\xdb\fd\xeb\x84\x0f\x91\xb0\xef\x94%\x919\x05\x05\xab\x137\x8a\xd1C\x05xZ|`\xec;-Y\x1e&\xb2|\xbf\xe3\xd3\xd6p\xbf}\x11z\xc7o\xacG\xa6\x90K~\x12\xa0\xbe\b\x8d_\xae\x82N\xbb\xf0\v\x90i\a\xe2\xf1\xe2Vl\x1b\xc1\xf9\xed\xcdH\x0e\xbc\xdd\xf1\xb7V\xc1\xaf\x167\xc1Z\x10\xbc<\x93\xb78\xf6\xedK\x8c\xa0DNL\xec\xf6c\xf3\x14Br\x9b\x8a\xd6\x1bǽZT,\x9b\x1cǣ\xe1\xf1\xb6\xf5ة\x1b\"oc\xe3\xce<\x9e\xdbm\x12\xff\xd6B\xe9\x9fま\x89\xf5\xdc\xfb\x11}\x9b6\x12/[\xb4\xf5]\xec+\bcc\x01\x1e4H\x17\xfc\xb3\x02\xda{\x0e/\xf4\xa9\x96\x82{!\xb0GC@\xd6 x\x81\x9bl\xaa$e\x89k\xacM\x83\x97\x95v\xfa\xa7\x1f\x9dؤ9\xd9\xe6\xef\xeeF^\xdb\x1a\xceDU\xd1ar0i\xa9\x1f\xedH\xcf\xd3\x0e\x90\xa5\xbe<6x\x9e\xd3\xcdD\xcfC\x98\x16|f\xba`\x9cP/6@:\x86\xa2\xa4\x16\xcb\x12̶\x82*\xb2\a\xe0!\xa6\xfeG\xd0\xf3\x15\xe3;\x9c\x80|xu\xbb\x80\xb4躈\x9c\x1eՁ\xa0\xe1\x03j\xaaT\x93J\xe4\xe4\xb9\x00\t=\xae\x18\aʍ\xa5\x99\b\x92\vݍG\x18\xb8\xb5\xc8\xdf)r`R\xe9\xeeBS\x19\xaeQ\xa9찒\xc2fw\xdfX\x05\xa2\xd1\x17\xd0\xe0S;\xba\x97\u05ed\xe8\x0fV5\x15\xa1\x95h\x12\x8c\x02ی~aUH\xbe:\n\v\xf9\x004\xbf$\x00\xf3kg8\x01\xae\x1a\x89\x89{+^\x9eY\x99\xb6fC9R҆g\x05\xa0\x9c\xe2=\xf1A,xƕ\x06\x9a\xca\v\xc6jj8g\xfc\x98F\xbb\xe4\x10g\xdb,\xaa\xf7B\x94@\x87\x05O\xb1fp}\xb9\x18\xfa\xb5\x1d\xfd\xbb\x88\xa1@\x81tsa\x0f\x8eTN\x16Q\xad\xa1\xaa\xedy\x13D6\xbc\xab}\xae \x85\xd6\xf8\xe0n\x15\xaf\xe9\\3\xce\x12\b;ȹ0ݵ,\r\x88\xabZ\x96f\x82`T\\\x12>\xdb\xf5\x00\x98\xd3\xe9\x9d\x14\\{\xe0\x9a\x15V\xe6\x1e\b\xcds\xc8m`Ҙ*\xceg\xb1\xe5e\x13\xa5\n\xd1ݭ7\x13\x93(\xeb[\xcf#\xc5P\xac<\xc1\xa6\xe1O\\<\xf3\rz\xf2j\xb5\x00I\x0f\r\xbe\xea\xf4\xfabI\xf4{J\xa1>\xbf\xa6\xf3\x947\x9e\xae e\x92\xf9fU4d\x8e\v\x96\xe4\x9a-]\x9e\xf8qq\x15s\xf3\xcf\fv\x89揶\xe68\xb5\x9em\x17\x1f\xd51\xfe\x9e\v\xd0\x05H_̼\xc1\xba혜n\xf3ѭ\x1f\x13\n\xdc\f\xffxS\xd8\x16^\x0eJ\xde⎎\xb1\x02n\fcӦԶ\xfcX6\x11&J*\xfc\x8a[\x06)\x95\x13K\xf5\x12\xfd\x1a\xc0P\xaf\xe0\x8b\x00\x85\x9f$\xb2CKK[\xe9\xdbM\xc6\xf7\v\x1f0\xe4\xe7W\xfa\x0f/\x0fL\xa8iX\xa8d\x98/\x9a\x9c\xc3טm\xba\x18ky\xd0\xf5sմ\x7f,\xf4i\xa8\xbe\xd6\xee\x1cL\x1a\xa0}\fF\x86\f\xcaAPr\x1b\x97\x1d\v\v(\x8b\t\x17s\n]8\xd0@\xbc\xcb\xf0$\n\x0fXa\xa8ٝ6W\xdd\xce\x14\xf9@\n\xd1DJ\xeaf\xb0\xb3P`1]V\xe1\x92\b\xa0\xe9\xe9ö\xff\x8b\x16\xae\xc8\x02#_\x91ݡ\xa3\xd2FS\x19\xcfى\xe5\r-{\x87\xac\xc3\x16-\xf7\x10!\tge,\xbfj\xd8ʏ\xef\xb1\x11\xf9Z\xdb<\xcbjq4o\"\xa6\xd5b\\\\\x81ѯ\xb0\x98PRkS\x0e饦\xe95\x16\xf3E\x11k*+\x86u\x13\x93@\x97\xeb)R\xac\xfb\x85ډ\v*&\x12\xab\xe5^\x9c I\xa9\x89\xb8\xa8\x12b\xb1\xa0,\xb1\xfe\xa1_\xd90\x0frE\xd5C\x12r\x96+\x1cV\xd75\xb8:\x82\xd9}$W3D\xea\x14f\x01O\xd60\xccU'̣\xff.\xf0V\xd0\xfe\x8c\x90\xbe>\x84Ӹ\x1d8\x0e4v\x86\x9e\xa1,\tU\xe3\xedg\xf6bl&6x\xd7\xcdP\xd2\xf3\x83\xbb@{\x83'6\xe6\xb1s\x7fe\xb32`\xf0r\xad\x8aDD&uѼ=lMw\xfc\xf6[\x03\xf2L\xc4\ts\xfa\xce@Z(\xbb\xb7rE\x19\xff\xcdK:'.\xedu쁟\xd0\xca\x17rǭƎ\x82\x1d\xac\x11\xe1\x18\x11\xd7\xfaFF\x9a\x1b\xb7g\xa2k\x14*\x17at\x9c\x1ff\x15Sj\xcd\xfau=\xa5\xf5\xbeҢ\x95r\x15\x7f\xe9r\x8fi\x06dj\rzZNd\xb1\xe6\xfcZ\x9eӒ\xef\x94l4\xa6Ք_\xa3\x96|E\r\xf9\n\x1fj\x9d\x17\x95\x8c\xa6\x94Z\xf1\xab\xf8RW\xf4\xa6\xae\xe1O]\xe6Q-\x80\x1cԀ\xa7Tw'\xa5\xf1\x92s6)Y\xb6\xe5\xcc\xf1|\xd5vB\xb5vB6hi\xa5\tU\xd9몱\x13px%_\xebJ\xde\xd65\xfc\xad\xebz\\\x8b>\xd7\"\xe7,\xfc\xbc\xae\x8a\xfa\xe2$\x83OG\x7f\x119\xdc\v\xa9\x97\x1c\x84\xfba\xffH\n\xb0\xe34\x892'\xdcw\x8de\x1a\x8c\xed\xef\xec\xfe\xcb6\x15\xcf\xd6y\xf3\xf7\x17\x91\x9b\xb5-\xe5\x16\x1e\x06\xddGWh\x0f \x81ۇ%\xfe\xeb\xf1\xeb\x97\x00?f\x8f:\xa3w\xf0\xa6\x8150r\x87\x1c\x97}r\x057\x16[\xa8\xc3_9I@k\xf6\x9f\xf8f\xd7r@\xe6\xee~\x87]\xbd\xb5\x84o}\x85\x84~Ƚ\xed\xc1h\x8f\x80\x91I\xee\xdf\x1dz\x10#e\xa7\xe1O\x82/&y\xedŦj\xb2l\x11\x929u\xf7;\xbb\xba-\xf9lL7~&\xc22^\xc1d\xbe\xa9\xa9\xd4g\xe4\x0eu\x13\xd60\x1d\x94\xf1:d.t2)j\xc7oAEq럄\xc2\x04ܹ\xeeg3\x87\x18\xbdd\x1dӷ'\x16\xefM\xbc\xe2:\xa6\xd5\xf1\x061\x15\xf9\x1c\xad\x80x\xb5\x90\x94\x13C\xf7ߗ\xc4\xdaC\xe88/ό'\xeb\xc3:\x11\xfc\x98\xf1(\xd2\x14\xa7\xb5*\"Q\xa1\x97\xc94|\xa8JS\xdd$\xee\xc7\xf6\xedm\x89eEG\x00=\x83\x17Q\xb2\xf3l\xdf`M\xe6\xacZ@\xa8\x86ф欼\xe98\xe6\xbfO\xca3\xf1Y\x90\x8b\x1f\x04\xb1\xe8\x99\x10\x15\x18i2b\xcc\xf3B\x8b\x97\v\x92\x9d\x8b&\\Ba\xeb\xbcݙ\xf8\xa2\xc4\xc5oI,#+\x82\xa8\xa9g$R\x9e\x8a\xf8\x87\xe2sF$\xa9\xac\x80\xbc)!ၷ\xc7N\xd7\xe5'\xde<\xe0ؙ\x14\xfdG\xde\f^;\xea\xd5\x18\xbc\xfd\xc7\xe4\x1c\xd2\x1d\xe4\x89\x12\xef.Hk\xd9\xdbW\xa72cǫ&\xcb@\xa9CS\xfaj\x99L\x02Ր\xfb\xee\xd1\xca|\xbf\x87\x15e!q-\xb2\xe9<\xa3\xf7&\x812*\"&gDdFk\x8dU\xf0he4R\xe2\x96\xedo\xe20z\xfa\xaf\avZh\xb9rFW\x8c\xa34\xad\"\x96\xd8\xf0\"\xd8p\x04>\xb0)\xf3N\xf9N\xf7\x85\xb4P\x95\x13\vkS\x15**\xf3m\a\xb6\x05\x83Ə\x01\r9\x81\x13p\"8\xde5\x81|\xae|\xf7\x1b\x86\xcd\xe4\t\xe4;\x15\xe0`A\x91\xb1\x15\x1f5\x95:,}\xcc\x11\a!+\xaaoIN5l\xcc\xe8\xcb4d\xfc\xf5B)\x973\x1cxiŹ\xc1x\xd3\x04\xc9[\x96\xee\xaaI\x05Jѣ7ߟA\x029\x027(\x9e{\xa0\xad\xbd\xad\xe3Np\xa8;3آ\x99n\xa8\x9b\xc0jʐ\xfb\x89\xe5\r쫟\xe8\x11\x1c'\xcf\r\xe3\x1a\x8e\xa3\xac\x8b\xbb)\xf4\x00T\r_\x89\x1d!\xe2s\xb7\xaf\x8b\x99Y\x1c\xd8'I\xa8-\x12\xc3GE5\v^ʄ423\xaf*\xfd*\x84xJ2e~\x0e\x1d[\x97\x9dq\xcbGx?j/\x1a\xdd5R\xc4T\x80\x0f\x1f\xef{e\x93\x04a\xde\xd9\v\x13Sq\xa8\xf1\xeb\x80a@p\n\x84\xa6%\xe1M\xb5\a\x89\f\x19:\x143\x97\x86\x1f\xfd\x13\xa5ey\xbe\x19B\x1e\xbcY\xdc\u009e\x83\x88\xa4w2\xa0s\x91\xd4\aP\x06@l\xf7\x99\xf7\v\t>8\xeb\xf5\xc6ԋis\x1c\x1dp\xfc\x19\x17\x95\x8a`\xdb{\n\xbbv\xf1\xd6\x16\x06\x1e7\xe5\x89O`\xfacq\xc1\xd2g\x8c\x86\xba\xa0j\xc9Z\xb87}\xc2\rҎN\n\x86\xc2\xc3đ\x8c\xdfhې/\xf0\x1c\xf9j\x91\x85i\x80\xb8&ِ\x1d\xbf\x97\xe2(A\x8d\x19g\x83W\x9c\x18?~\x16\xf2\xbel\x8e\x8c\x87\n\xd4u\x9d\xef\xa9\xd4̰\xb2]Od\xecG\xaf\xcb\"\xbf-\x8f\x9e\xf8aFF\xd5nϋ11\xdbmI>9\x01\xfaN\xb5\xfa\"\x16?rж\xe4\x8b\xd0\xe0SU\xac\x0f\x94)\xb2\a\xa57p8\b\xa9m\bs\xb3!\xec\xe0\xec\x94X\xa8\x8d\xb2\x12Mm\xfbij\xb1\xbfCqU0\xfc\\8E\xa2R@\x1b\xbd\xa2g\x1b\x95\xa1Y\xd6\x185\xf8^i\x1a\xb3\xe7^$FѶwܜr\xc8w\xdd\xfe!\xa2\x11\x0e8\x82\xb3\xa8ë\xa5V\x03G\xd3\xf4\x04\xaf1vn\xb6\x13e\xb4\xd9%ǝXA\xb3\x9b\xf6S\xfa\xf7qB\xe7)9\xe5\xb6\xd1{\xccv\xba\x14\x97)?\xd4\xd0,+(?\x1a\xf6\x91\xa29\x16\x9e\x05\xa7\f\x95\t\xa0y\x83!\xcf\x1aO\xaa\xf2)_\xddH\xde\xc9X\xb8$p\xde.w\x0e\xe8\xc5\x12S\xb6\x05\xe9\xad̘Ѽ1c\x7fb\xf0\x04\xfec\xf1\xd40\x84\xaa3\xcf\xe6\xab\xe4m\xec\x94\xcd\\\x86\x9bCFt\xbfA\x02^\xb2\xdf08}\xbf]\xe5ݺ\x12k6?\xedf\xbe\x02:\xa6\x8c\x82e\\\xcc\x1b\b\xb8\xbf\xc8\xcaW\x91\xbbk`xS\"\x16\xd42\xc6\xc5:\\\xa8\x9e\x93\xb5\x14s\xe8u~\x993\x89\x13\x1bW\xf2\x8f\xeb\x04\x9e\x82\x19\xf3)\xc5\x1d\xfc>\xe8>\xb8rd\x1c\xc3\x16\xa2s\xe1\"\xc8\xf9gv\xf0\xff\x15d_¿\x8cz\xfc\xceW\x87\x9e\xa9\xe4\x8c\x1f\x976\xff\xab\xeb\x16\xf1\x86\x1d\x84\x88?\x1c\xd9D\xf0\x90W\xf9\xc3~\x91\x13\x0f\xdf\a\x1f\xf9\x05\x1eqT\x9d\x8c>\"#\xe7\x1d$\xbb\x99ܗ\xff\v\x00\x00\xff\xff\x0e;\xfc{\xabg\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Mw$)rw\xfd\n\x9e|\x18\xdbOU=m_\xfctk\xab{\xecz;ӭ\xd7\xd2\xf6\xc9\x17*3J\xc5(\x13r\x81,uy\xdf\xfew\xbf\b \xbf*\xc9$K\x92gfW\\\xba\x95\x05A\x10\x11\xc4\a\x04\xb0Z\xad.x%\xbe\x816B\xc9k\xc6+\x01\xdf-H\xfcˬ\x1f\xffì\x85zwx\x7f\xf1(d~\xcdnjcU\xf9\x15\x8c\xaau\x06\x1fa'\xa4\xb0Bɋ\x12,Ϲ\xe5\xd7\x17\x8cq)\x95\xe5\xf8\xd9\xe0\x9f\x8ceJZ\xad\x8a\x02\xf4\xea\x01\xe4\xfa\xb1\xde¶\x16E\x0e\x9a\x80\x87\xae\x0f?\xae\xdf\xff\xdb\xfa\xc7\v\xc6$/ᚙl\x0fy]\x80Y\x1f\xa0\x00\xad\xd6B]\x98\n2\x04\xfa\xa0U]]\xb3\xf6\a\xd7\xc8w落\xf3\xed\xe9S!\x8c\xfdS\xef\xf3\xcf\xc2X\xfa\xa9*j͋N\x7f\xf4\xd5\b\xf9P\x17\\\xb7\xdf/\x183\x99\xaa\xe0\x9a}Ʈ*\x9eA~\xc1\x98ǟ\xba^1\x9e\xe7D\x11^\xdcj!-\xe8\x1bU\xd4e\xa0Ċ\xe5`2-*K#\xbe\xb3\xdcֆ\xa9\x1d\xb3{\xe8\xf6\x83\xe5W\xa3\xe4-\xb7\xfbk\xb66To]\xed\xb9\t\xbf:\x129\x00\xfe\x93=\"n\xc6j!\x1f\xc6z\xfb\xc0n\xb4\x92\f\xbeW\x1a\f\xa2\xccrb\xa0|`O{\x90\xcc*\xa6kI\xa8\xfc'\xcf\x1e\xebj\x04\x91\n\xb2\xf5\x00O\x8fI\xff\xe3\x1c.\xf7{`\x057\x96YQ\x02\xe3\xbeC\xf6\xc4\r\xe1\xb0S\x9aٽ0\xf34A =l\x1d:?\x0f?;\x84rn\xc1\xa3\xd3\x01\x15\x84w\x9di \xb9\xbd\x17%\x18\xcb\xcb>\xcc\x0f\x0f\x90\x00\x8cHT\xf1ڐp\xb4\xado\xbb\x9f\x1c\x80\xadR\x05py\xd1V:\xbcw\xb2\x97\xed\xa1\xe4\u05fe\xb2\xaa@~\xb8\xdd|\xfb\xf7\xbb\xdeg6\x90%O)&\f\xe3\xec\x1bM\f\xa6\xfdLev\xcf-Ӏ\x9c\ai\xb1F\xa5a\x15\xa8\x9b7 \x19S\x9aU\xa0\x85\xcaE\x16\xb8B\x8d\xcd^\xd5Eζ\x80\fZ7\r*\xad*\xd0V\x84\xa9\xe7JG\xa3t\xbe\x0e0\xfe\x01\a\xe5j9I\x04C\xc2\xe7'\x14\xe4\x9e\x0en~\b\xd3\xe2OL\xea\x01fX\x89K\xa6\xb6\xbfBf\xd7\xec\x0e4\x82\tXgJ\x1e@#\x052\xf5 \xc5\xff6\xb0\rJ\xbd%a\xb4\xe0\xf5A[h\x02K^\xb0\x03/j\xb8b\\\xe6\xac\xe4G\xa6\x01{a\xb5\xec\xc0\xa3*f\xcd~Q\x1a\x98\x90;u\xcd\xf6\xd6V\xe6\xfaݻ\aa\x83&\xcdTY\xd6R\xd8\xe3;R\x8ab[[\xa5ͻ\x1c\x0eP\xbc3\xe2a\xc5u\xb6\x17\x162[kx\xc7+\xb1\"\xd4%i\xd3u\x99\xffS\xe0\xa8\xf9\xa1\x87\xeb\xc9|s\x85\x14\xe1\x04\aP#:\x81qM\xdd(ZB\xe3'\xa4\xce\xd7Ow\xf7]a\x12fH}\xa2{G\xc2Z\x16 \xc1\x84܁\x9f\xd1;\xadJ\x82\t2\xaf\x94\x90\x96\xfe\xc8\n\x01rH~SoKa\x91\xef\x7f\xa9\xc1X\xe4՚ݐyA9\xac+\x9c\x81\xf9\x9am$\xbb\xe1%\x147\xdc\xc0\xab3\x00)mVH\xd84\x16t-㰲\xa3Z\xe7\x87`\xde\"\xfc\ns\xfc\xae\x82\xac7e\xb0\x9d؉\x8c&\x06i\xcfF\x05\f4\xa8+㳖~!55\xfc:\xc0\xc3\xe9\xb2\xd0+\x18\xb4\x1fvO\x1cn\xcd\x18ʕ\x83\x86:E\xaa!wǴ`\x87\x12\x1e\xca\f&}\xad\x97j\xdfN`2\xaf\xea\xd6\x11\x1cO\xb8J?AY\xa1ژA\xf1\xdeWC\x14\x91>y\xe35\x05\xc3\x1fԬ\xf2ڕ\x9d(7\xean\x0fȷ\x83Ƚ\xf6:\xe1*\x9b\xe4,\x96̈;\xc9+\xb3W\x16m\x9c\xaa\xedX\xad\xc1\x00n\xee6\x83F\x1d\xce#VdÉ\xd1V\xb1'.N9\xed\n\xca\xe5\xcd݆}C\x97\b\x02L\xe6,9\xb3\xb5\x96\xa4\x8e\xbf\x02Ϗ\xf7\xea\xcf\x06X^\x93V\nv\xf9*\x02x\v;\x9c\xf4\x1a\x10\x066\x00\xadq\x0e\x18BM\xd5vM\x0eG\x0e;^\x17\xd6+9a\xd8\xfb\x1fY)dm\xe1\x94\xefl\x9a\xf7D$ny\xa9\x0e\xa0\x13h\xf8\x91[\xfe\v\xd6\x1d\x90\x0ea0\x02\xe2\xd9Od\xdc\x1e#\x03\xc5&['\xa9l\xb3\xeb@\x15\x86]^\xe2<\xbbt.\xf1啫[\x8b®\x84\xa4~\"0]\xefO\xa2(B\xff\xe7Q\xc3\x11\xd7\xf1\xd6ܫ\x9f\x8c\x13\xeb\x14\xe2D\x9a\x8e(\x98J\xe5\xec@\xf5b2&\n`\xe6h,\x94\x9eR\x1dυ\x88Kڱ(<\x18öǀ\xfb\xf8\xb8e]\x14|[\xc05\xb3\xba\x1e\xefvJ\x91\x8d\xd1\xe6+\x18+\xb2\x04\xca\\\x0eI\xe3Z\x8e\x10F\xd3\x0f\x11\xa2\f(\x80.\x0f\x7fD\xb7\xdbS\b}\xa7\xa2\xe8\x10w\x9e*\x8c\xfd\x8fd\x1f\xd1\xdcgh\x84\xaf\xbdq\x17P\x90C!\x15+\x94|\x00\xedzD\xc7)H\x98\x06\x94\xb8<\x02\x15-\xad\x86\x02]\x06\xb6\xab\xd1\b\xaf\x19j\x82\xa8\x8c\bi,\xf0|}\xf9Z̃\xefYQ\xe7\x90\xdf\x14\xb5\xb1\xa0\xef0\x04\xccC\b<\xaae\aL\xfc4\t\xc0\xbb_\x85\xc8\x00\xf9\x90\xb9J+\x8a4cDj=\xb1c\x05.\xf0E\xa6zL[\x17\xab\xa3*\fX\xacr\xf9\xaf\x971%\x8a\x12\xd0\xef\xbdߏa\\CC\x8d\x9eF\x8d@l\xf4,\x94\x95=\x8eˑ\xb0PF\x888\xabr\x16\xb0\x97k\xcdǔj\x18N\x13џ\xcf\xde\x18\x88\x01\x83e\xa8\xf6\x1b\xb1x\xd8\xff?\"\x93\xcfb\xab\xa1u,.$\xb2\xb3\x10\xc6\xf6\xb89\f\x88\x1a\xcc0vF\x9ab\xd0\"\xa4\x83\x89ʭü\xdf3\xcdΙ\t1\xd1o$͋\xf3\x9eDŽ\xea\x0fH\xb0\xbdR\x8f)D\xfao\xac\xd7\x06\xca,\xa3%U\xb6\x85=?\b\xa5\xcdp\xb5\x05\xbeCVۨ\x9e\xe0\x96\xe5b\xb7\x03\x8d\xb0h\x81\xb0YO\x9c\"\xd6t\x98\xc0:\n(Za0\xae\x96\xe9\xc8<\xa2Fl(\x14\x8eE\xa12B\x1c\xbdx\xb2\xee\xb98\x88\xbc\xe6\x05\x19z.37>\xde\xe0\x17sOf\x04\xe2\x04\x7f\xe7N\x84Q \x97zQ\xb6\x92\x80\xeeu\xa9t\xcc\xf3t\xe5\x14L\x9c\f[N\xc1q,$m\x8b\xae\v0\x1e\x15\xe7\xc0\xb6z\xe7\xaa\xe5\x94[\xa0*\xf8\x16\nf\xa0\x80\xcc*\x1d'O\x8a\x10\xb8\x92\xaa?#\x94\x1dѤ\xfd hV\x89\xb6\x05\x03̽\xc8\xf6\xce\xddD)#X,W`Hc\xf0\xaa*\"V\xa8-\xb3\x92\xe1;\x9bS\x1amIP\x1fC\xb81EҖD\x1dܖ\x19mܧz#6oD\xef\xa1)\x9f%웓\xe6//\xecHn\x01\x86\x9c>\U000bab98\xb0\xe1k\nԞ\x1fh\xfe\xce\x18w\xdel\xd9\f[\xbf\xf8ly\x11\xae5h\xfc\x9d0\x8d\x8c՝\xb7U\x8b\x18\xf6s\xb7\xe5\x15\x13\xbb\x86a\xf9\x15ۉ\xc2\x02\xf9Rs\x88v\x1c\x9dYν$\x81Rm/\x96\x92\xdbl\xff\xa9Y\xd6Nh1\xa0\xd5\x10\x80\xf3\xcbC\fC\f<\x9d.\n4\xc0$\x90\x9dA\x91\x9b\xd6\xc4xn?\xef\x8aq\xf6\bG\xe7Y\x8d.\x0f\x8d\x15d-o@j\xa0\xcdER#\x8fp$P~\xb70\t\xde\x12Qq\xe5\x11\x8e\xa9U\aDE\xfc\xfc>\x85\xa3.~\xa0Q\xa4L\xa5\xb64D\xf5s\x87Y\x956X\xb6L)\x85\x12(~\xe6\xb0\x1b\x86\xf5\xb6\xc8\x1f\xe1\xf8\x83q\xec\xc3Y\xb3\x17\xd5\x02\n\xa0¦%\x19\xb5k\xf6\x86\xbf\xf1B\xe4Mg4O\x16@\xdc\xc8+\xf6YY\xfc\xe7\xd3wa\x10E\x99\xb3\x8f\n\xccge\xe9˫\x92\xd8\r\xe2L\x02\xbb\xc64-\xa53\vH\x97E\xfd\xb78\x90\tE\x11m\xd8&\f\xdbH\x8c\xcf\x1c}\x96\xb0i\x0f\x019\x87VY\x1b\xda]\x96J\xaeܒ\x96\xefm\x01\xd0.^\x9eUJ\xf78u\xb5\x10\xe2(\x8a\x1e\xbd{\xb4V\ue5d3}\xf9\xa9\xa2\xa1*x\x06y\xd8e\xa3$\x00n\xe1Ad\xac\x04\xfd\x00\xacB\xbb\x91.T\v4\xb9+gHa\xbak\x11\x8a7\v#{\xdace\x85\xb3>\xb1f`sR\xf5Ȏ\xfft\xf5\xb4Q\x92y'\x7f(\x89\xfa\xdd\x14\xb5e\x96e!\xbfN}\x10\x87\xa4s?JN\x1bO\x7fE\xf3J\xe2\xfd\xb74kȅ6k\xf6\x81\x12\xf4\n\xe8\xb6\x0f\xab\x84\x9d\xae\x92@\"&\xc20\x94\x93\x03/\xd0}@\xe5-\x19\x14ΙP\xbb\x13\x0f*M\xc5<\xed\x95q6\xbf\xd9\x18\xbb|\x84\xa3ߜ\xedj\x89ˍ\x8c\xae\xda\xf7\v\xea\xfc\x13\xa5\xd5x-J\x16GvI\xbf]\x92c\xb6d\x8a\x9c\xe1\xbc-\x90\xea\x05U\xbf\xaf\x1e\xeb-h\t\x16̪\xe4\xd5\xca\xcf\x06\xab\xca\xe8\x1e\xa7+\x94F\xb7$\x8c\xc08=x<ظI6C\xf7\x7f\x8e\x02\xc9\xf3\xa1R&\x92i\x11A\xebV\x19\xeb\x16\x0f{\xae\xfa\xc8\xeabJ\xe4\xe8W\x1c\x19\xdfY\xd0\xccX\xa5Cb\x17\xaa\xec\xc1\xe2:J\x8d\x99\x97\x1b\xb7O\xe4W2\x1d`\fP/[\xed\xe2\xec\xc1\xa5۫\xc2\xff\xcf\xc3\xcc\xc8\xd1\"ؕV\x19\x98h6B[\x12\xad\xce\xccbo\xb3\xd0\xcb]\xe0\xb7KR\xeb)\xcbС,s㑴g\x04E\x9f\xbew֬Q\x85\xe1\xdf)\xa2|\x0e\x8e\x8cr\xbb˒\x0f\x93\f\x93ѽq\xad\xc3\x04\xf4\xc0\\\xb0\xa5\x1fjRH\xcb|n/\x92\xbf7\xa7\xa5\x14rC\x1d\xb1\xf7\xaf\xe6\xe8\xb0`\x06b\x19Ice\xc0\x0e߾eH\xf3!5\xf6e!UM\xd1>\x8f\x86\x1egOwA\xd29\xc5\xd0\x11\x97\xcav\x17z|O?\x18\xb6\x13\xda\xd8\x16\xe1\x05P\x85\x99\xc8z\x1a\x1d\xde\x19\xf1\xa9\xfc\xa4\xf5\xd9\xe1\xe9\x17\u05fa\xb3$\xb9WO>\xc1sIP\x1e\x88\xbf\xe7\a`bDŽe 3UKZ,Cu\x81\xdd,\x80\xe8\x98\xe8\x8cI\xa2\xcd\xec4\x96u\x99N\x90\x15I\xa7\x90\xb3+k\xdd&?q\x91\xb6\xb2\xc5\xcec\xab\x9dJ\xa2\x1c+\xfd\xccP\x9fM\xd9\xcd\xe4-\xf9wQ\xd6%\xe3%\xb2eI̹sy\x98!\xed\xd7\xf1\xfa\x89\v\xebOS\xb8M\xd9e\xda4SeU\x80\x85\x90a\x99)iD\x0e\x8d\xfb\xe0\xf9?\x9a\xaf\x1a+\x9c\xed\xb8(j\xbd@G/\xe6\xccҘϫ\xa7\x97\x0f\xe4\xd2\x11Y\x111\x13\x17\xec\x178\xdc\xf3\xf6\xa3\xd2\xcb\\\xe6[\r/\xef\x9aVZ(ʁ\x9d\xf1Nga\x92\xf7\xda\xf7N\xbd\xf0ry\x8c\xb9\xa7\xb3P\t\x937\xf7\xb4)o\xee\xe9\x9b{\xfa\xe6\x9e\x0eʛ{\xfa枾\xb9\xa7\xe3\xe5\xcd=\xed\x947\xf74\xd9~\xa4`\xb8\xa2\x95ۉ\nIX%\xa6o̡=ӗ\xcfR\xf2gA\x96dWo\xc6[\x8e\x9c\x05Zt\x86\xc4t\x8c^\x93n\x8dS2L&w\xa64\xc1\v\x7f\x81\xb36\x01\x81\xb3\xcf\xdal&\x01\xbc\xe0Y\x1b\x8f\xe9p\xed\xfc\x05O\xda\x04Z,?\x84q\xe5ӘJ\xe0aK\xc8\xe5\xa0\xe4\xb1nc^l\x0f\x8f\xd1:\xbfq\xd6\xfdI\xb6\xe6\xf9\"\xf3\xffr~'\"6'\xa7S#P\x85A\xb9\xfacp\xe2,\xdaG\xa9\xed\xfe\x17\x1b]KX\xa7x\xdd5\x03\xddT\xcb~\xca\xeb\x1fG\xb0ϑ\xe4\xd4\xf371\xe7<\xae\xdb:\xc4Խ\xf3\x1e\xbfoZZ(\xbfTޒ\xa5\x9f{ߌ4{\xc6\xc9wn\x8e2\xdbk%Um\xfc\n\x0f\xf6\xf0!sW\x01\x84\x8e\xcc\x12e\xf0\x9e\xedU\x1d9\xe31Cׄ\xcc\xdbx\xbe\xad\xcf\xe0\x00\xcb\x0f\xef\xd7\xfd_\xac\xf2ٷ\x11\xac\x9f\x84ݻ\xfb\x18x\x9e\xa3\xa3\xde9\xe2\x13&\xaf\xbf\x93e(x\x11\x88J3)\n'\x95\x01B߀~\xa9ܒ\xdf\xd9~\xcb\xfc\xc2Sz\x8e\xee\xd2\xcc\xdc&\x97r\xdeK~F>\xee\xb2\xc3R\xb3\xb9\xb7)H\xb3\x94\x8c\xdb\xf1\\\xda\x19\xa8K\xf2lS\xd7\x14\x13rj\xd33i\xd3\xc8\xc3\xe8&\xa5\xd4\xfc\xd9\xe4(45W\xf6u2d\x13\xf3b;ٮ\xb3 \xcf̆M&XZ\xe6kr\xbek'\x8bu\x9eZ\x13Y\xae㹫\xb3 \xc7r[S2V\x93pM\xceSm\xb2O\xe7wF\x9e\x95\x9d\xfa\xf2\xe7`^r\xddb:\xd74)\xc34imc\x1e\xe7\xa4\x1cҥ\x99\xa3IT]\x9a%\xdad\x80Nt\x9c\x94\x1bz\x9a\xf795\x94ٌ\xd0x\xb6\xe7\x14ر<Є\x1c\xcf\t\x90\xdd\xec\xcf\xc5n\xc0\xac4\xcdVX\x9a\xbb9~?Z(\xf3ֹ\xf8-d\xf6\xb9dR\xba\xe74\xa7\x04w_\x06MPZ\x82\x9f8\xe6\x88\xc7Ce瞟\xe1\x88G@nv\xac\xac\v+\xaa\xa2sA\x99\xddñ\xb9\xf2\xe7WE\a\u05f7G\x82\xf6\xe5k#\xf21\x90\xfd\x90\x82\x1b\xf6\x04E\x81\xff\x9eP!s\xd7\x01fj\x05h\xa5\xe2\x1b\x81\xfe\xaa#\x7f\x97\xe0\x95[\x16\xa3S\xfdd\x01K\x844}\x01֤)\x99v\x8f\x9dWO\xdf\xfeR\x83>2\xbas+\xf8AQ1kO{\xfa\xc9l0&\f\xca\xc7k1w)e_\x19\xc5gC\xa3\x02\xd8\a\xe9\f\xf3\x10W\x82\x85Z\xa7\r\xa7\xa6\x94-FO1\x10R5\x10\"\xedS\xbc\xef%\xc7\x1f_#\xb8z\x89\xf0*\xc9\x11y\x8d\x10뵂\xac\xa5a֒䍤㋯\x11l-\t\xb7\x16\xf9\x8c\xe9\xc7\x13_\xebX\xe2+\x84]g\a^\x8bH\x97z\xecpq\xf8\x950\xbe\x99c\x86'>Z\x02\xc8\xe8\xf1\xc2\xf1\x10,\x01\xe2ɱ\xc2\xd9 ,e\x1e\fôg\x1f\x12LNdZ\xb4\x9b\x9e\x9a\x84\x94\xb6\xd1=\x7f\xf8/\xf1\xd0_\xe26x\n\xf6\x89\x87\xfb\x96\x1f\xeaK\xa4\xf3\x99\xe1\xd9d\u05c9\x87\xf7\x16\x05hg\x86h\x93\x10\xa7\x0e\xebM\ai\xd3\vp\xc3Czg\xb8\x13\t\x12\x96Pe\xf9A\xbbgo\xc6(\x9d\x83\x9e\xdd\xd7Z\"γ\x82<\x88\xa3\xfa\xfd\x0fvt\u008d\xa8X\xab\xbbg\x16\xe3\xa8j\xee\x1d\xc9؟\x84\xf4\xbb\xf5(\xb8\x1d\x9f\xa4\xb7\xf1\xd6:L\xf1}\x9d\xd6K\xf5\x17\xab\xbb\x1d;\x03\x15״\x8f\xbf=\xba\xa4 \xb3f\x9fx\xb6oz\x88\x80\xa4~\xf7ܰ\x9d\xd2%\xb7\xec\xb2\xd9\n}\xe7:\xc0\xbf/\u05cc\xfd\xa4\x9a\xf4\x91νb\x11\xa8F\x94UqĈ\x89]v\xc1ti^\xd6ɂ\x88\xc5(\xd8\\\xf8\v\v\a\x97\n\xefTQ\xa8\xa73\x97.x%\xfe\x8b\xde1I[\x1b\xfbp\xbb\xa1\xeaA\xaa\xe8\r\x94&{\xae\x91\xb1-L+\xf4v\xe0\xe4zt\xa1\x8ed\xaf6\x7fN@\xa4W\x04\x82\x9f\xe1\xd5x\xa6P\x8b\xddn\x1c\x96k\x12,.\x8fL\xf9{\xe2\x85\xceW\x15\xd7\xd1M=\xe6\xe5\xc1\\\xf50\fv|n\x05kҬ\x9d\xbe\x8a\xd0-=\x9a\x87\a\x12h\xb3\xf7X\xf5\xb7щ\xd2\x1dz>\a\xa7\xe9\x83˳G\x96_\x01\xa7i\x97iET\x8c\xfc\x14M\xc7{\xf1\xd5C\xe3/\x91\xffE\x1d\xe0ct\x15\xb1\xffd\xc0\xa0\xc9H\x02]\x80:umz\x9b5\x17\xbf\xce\xfa\x052\xe2\x02*\xfe\xe2\xeb\x05\xe3\xf3-\xc6^i\xf0\xf7\x7f\a\xd8\x13\xb6\r\xa7\xec\xed7\n7\x1bu\xe9g\xb8\x0f&\xc3R\xe1\xe0\xaa\xd6\b\xc8\xd83\v/E-\xab4\x7f\x80\x9f\x95{\t#\x85Z\xfd\x16\xbd\xc7P\xbc3\x17\xb2\x89\xfd\\\x8b)s?\xb6!\xc0\xf6\x90\xc1\xc9u\xf4\x88홷\xed[[$\f\xee\xfe\xfeg7 +JX\x7f\xac]\x86\t\xea]\x03H\xe90P\xd7h\x1b\xd7N{\xf5D\xf7\xb9w\x9f\xab\xe8<\b\x04t\xa8\x81\xd2F\xcf\x1aM]\x15\x8a\xe7\xa0o\x94܉\x87\x84\x81\xfd\xb9\xd7``\xd93\xfa\xe8\a\x1b\xeccd`\xa1\xe7W\xcc\fA\x97\xad(\xa0\xf8I\x14`\x1c≊\xfe\xf6\xb4e\xa3\xf7\xebr\xeb\x1c\xd4\x1d\xfe\xd8t2aM\xddPiM\xbf\x02\x8d\x8e\xa0[\xfd\xafM\x10\xf0ib\xb0\x86\x8fBZx\x88$\xf8\xcch\xf8C\xef\xe9\x8f0IR\xd4ڷ\xf1\x96\x1do\xb93]\xa7\x92\x05\xd5.\n\x8b\x1b\xa32A\x0e6\xed\x8eЩ\x91\u05fbsz*P\x9a\xa0cm\xe0˓\x04\xfd5\xa8d\xb3\x91\xb1\xb76\xfa\xb3\xe6\xa4a\xf4\x9d\r\xabȭ\x1fT\x1f\v\xe7\xa4'\x90q\xaf\xb4\x84m\x1ea\x9a\xb7yNI73\xaf\xe2Z~\xdc'Y\x8d?\x87\xb3j^\xe8\xb9H\xa0\xac{\x85\xa6\x0fx\xfc\xf1%\xf7\\M\xc6+[\xeb\xa0rjM\x17p#\x10p\xf7S\x9f\xf7\xfcR\xfb\x9c\xdd\f/\xdb\a\xeeڅ\xef\xd9\xe7\xf4F\xf8\xd7<\xa0\x14}Qȅ\x8c\uee7b\x15\xc2?\x8f\x9d\xa3\xf3\x80.,\x9f\x19\xe9-\xd6iN\xfcyBS\xc3p\xd1\xf9]\f\xf5\xf1#\\+\xf6\x19NC\xb6\x15\xfb$q\x10\xa7\x9e\x9c;\xa7\x059-\x97\x8f==79\xc4Cӊ\x0eɍh\x8b\xbe\x9a\x1bT\x1f\xa4\xf0\xd2\xc35M\x15w n\x8c\xad\xff,vn/#\xc31\xfd\xcbI\x8d\xa8\xe2\x9aTZ1\x855:\xa5N>\x1a\xd0\az)&\b\x89\xf7ֺ_\xeam{\xef<\xfb\xeb\xdf.\xfe/\x00\x00\xff\xff\x89~\x02\xaf\x9ft\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Mw\x1c)\x92w\xfd\n\x9e\xf6\xe0\x99}\xaar{\xf7\xb2O7\xaf\xecޭ7ݶ\x9e\xa5\xf1i\x0fCeF\xa9heB\x0e\x90%\xd7Λ\xff\xbe/\x02ȯJ2ɲ\xb4\xdd=#.\xb6\xb2 \b\"\x82\xf8\x80\x00V\xab\xd5\x05\xaf\xc4W\xd0F(y\xcdx%\xe0\x9b\x05\x89\x7f\x99\xf5\xe3\x7f\x98\xb5Po\x0f\xef.\x1e\x85̯\xd9Mm\xac*\xbf\x80Q\xb5\xce\xe0\x03\xec\x84\x14V(yQ\x82\xe59\xb7\xfc\xfa\x821.\xa5\xb2\x1c?\x1b\xfc\x93\xb1LI\xabUQ\x80^=\x80\\?\xd6[\xd8֢\xc8A\x13\xf0\xd0\xf5\xe1\x87\xf5\xbb\x7f[\xffp\xc1\x98\xe4%\\3\x93\xed!\xaf\v0\xeb\x03\x14\xa0\xd5Z\xa8\vSA\x86@\x1f\xb4\xaa\xabk\xd6\xfe\xe0\x1a\xf9\x0e\x1d\xb2w\xbe=}*\x84\xb1\x7f\xea}\xfeI\x18K?UE\xady\xd1鏾\x1a!\x1f\xea\x82\xeb\xf6\xfb\x05c&S\x15\\\xb3O\xd8U\xc53\xc8/\x18\xf3\xf8S\xd7+\xc6\xf3\x9c(\u008b[-\xa4\x05}\xa3\x8a\xba\f\x94X\xb1\x1cL\xa6Eei\xc4w\x96\xdb\xda0\xb5cv\x0f\xdd~\xb0\xfcb\x94\xbc\xe5v\x7f\xcdֆ꭫=7\xe1WG\"\a\xc0\x7f\xb2G\xc4\xcdX-\xe4\xc3Xo\xefٍV\x92\xc1\xb7J\x83A\x94YN\f\x94\x0f\xeci\x0f\x92Y\xc5t-\t\x95\xff\xe4\xd9c]\x8d RA\xb6\x1e\xe0\xe91\xe9\x7f\x9c\xc3\xe5~\x0f\xac\xe0\xc62+J`\xdcwȞ\xb8!\x1cvJ3\xbb\x17f\x9e&\b\xa4\x87\xadC\xe7\xa7\xe1g\x87P\xce-xt:\xa0\x82\xf0\xae3\r$\xb7\xf7\xa2\x04cyه\xf9\xfe\x01\x12\x80\x11\x89*^\x1b\x12\x8e\xb6\xf5m\xf7\x93\x03\xb0U\xaa\x00./\xdaJ\x87wN\xf6\xb2=\x94\xfc\xdaWV\x15\xc8\xf7\xb7\x9b\xaf\xff~\xd7\xfb\xcc\x06\xb2\xe4)ńa\x9c}\xa5\x89\xc1\xb4\x9f\xa9\xcc\xee\xb9e\x1a\x90\xf3 -֨4\xac\x02u\xf3\x06$cJ\xb3\n\xb4P\xb9\xc8\x02W\xa8\xb1٫\xba\xc8\xd9\x16\x90A\xeb\xa6A\xa5U\x05ڊ0\xf5\\\xe9h\x94\xce\xd7\x01\xc6opP\xae\x96\x93D0$|~BA\xee\xe9\xe0\xe6\x870-\xfeĤ\x1e`\x86\x95\xb8dj\xfb\vdv\xcd\xee@#\x98\x80u\xa6\xe4\x014R S\x0fR\xfco\x03۠\xd4[\x12F\v^\x1f\xb4\x85&\xb0\xe4\x05;\xf0\xa2\x86+\xc6e\xceJ~d\x1a\xb0\x17V\xcb\x0e<\xaab\xd6\xecg\xa5\x81\t\xb9S\xd7lome\xae߾}\x106h\xd2L\x95e-\x85=\xbe%\xa5(\xb6\xb5Uڼ\xcd\xe1\x00\xc5[#\x1eV\\g{a!\xb3\xb5\x86\xb7\xbc\x12+B]\x926]\x97\xf9\xbf\x04\x8e\x9a7=\\O\xe6\x9b+\xa4\b'8\x80\x1a\xd1\t\x8ck\xeaF\xd1\x12\x1a?!u\xbe|\xbc\xbb\xef\n\x930C\xea\x13\xdd;\x12ֲ\x00\t&\xe4\x0e\xfc\x8c\xdeiU\x12L\x90y\xa5\x84\xb4\xf4GV\b\x90C\xf2\x9bz[\n\x8b|\xffk\r\xc6\"\xaf\xd6\xec\x86\xcc\v\xcaa]\xe1\f\xcc\xd7l#\xd9\r/\xa1\xb8\xe1\x06^\x9c\x01Hi\xb3B¦\xb1\xa0k\x19\x87\x95\x1d\xd5:?\x04\xf3\x16\xe1W\x98\xe3w\x15d\xbd)\x83\xed\xc4Nd41H{6*`\xa0A]\x19\x9f\xb5\xf4\v\xa9\xa9\xe1\xd7\x01\x1eN\x97\x85^\xc1\xa0\xfd\xb0{\xe2pk\xc6P\xae\x1c4\xd4)R\r\xb9;\xa6\x05;\x94\xf0Pf0\xe9k\xbdT\xfbv\x02\x93yU\xb7\x8e\xe0x\xc2UB\xf1QT\x9b\xb2\x84\\p\v\xc5q\x06\xd37w\xfd\xeac\xd4S\x04\x93m\x9d\xda\x15\xbb\x11<\xbb\xd4\xcdk`\xa2\x03\x91\xa6\xd6_B\x8dS\v\xf9\x17\xb2\xb6]\xc3\xd6-D\xa3.\xf8Z\xb6\xec\x13\xbb\xdeO\x12\x9e\xd6l\xb3cV\xa3Z\xdcv\rm\x0f\xa4(\n\x9c\xa98\xaa\n\xf2\x1e\xb2\xf1\xeeĎ\t\xeb\xc77\x02t\xcb\t'\xc9\xd6\xce\xfbY\xb7\xb6\xbe\xb1ۈ\xf2\x00_\xa7\xbd\x11\xa3\x11\x98(\x17\xdc2\t\xdfl\xdb\x0e\x89E\xa3\xdc\xf1\xc24\xc3t\x83\xf2*\xc8\x0fl\x04b\xd2P\xafض\xb6\x0e\xe0\x18\x06#`\x1b\x9c\xa0\xac\xec\xf1ʵݩ\xa2PO̐\xc2E[\xb7\x13\x0f\xb5v\xba\xe0\x0f9\xecx]\xd8k7\x8a?\xae\xdf,\x9a\x86\x16\xca\nM\xe3\x8cp\xdf\xfbj88\xd4\x01y\x13\x19\x04\xe76\xb8\x12\xca{\x10\xecĀSw{@\xddt\x10\xb9\xb7Уd\x88k/,\x99\x11w\x92Wf\xaf,ʃ\xaa\xedX\xad\xc1\x00n\xee6\x83F\x9d\xf9\x89X\x91\x9fJ\xe2i\x15{\xe2\xe2T\x9b\xb9\x82\xba\xf7\xe6nþ\xa2\xdb\x0f\x01&ss\x91\xd9ZKr9\xbe\x00Ϗ\xf7\xea\xcf\x06X^\x93\xe5\r\xbe\xe7U\x04\xf0\x16vh\xd84 \fl\x00Z\xa3\x9e7\x84\x9a\xaa횜j\xcfnoȅa\xef~`\xa5\x90\xb5\x85S\xddƦ\xf5\x1b\x11\x89[^\xaa\x03\xe8\x04\x1a~\xe0\x96\xff\x8cu\a\xa4C\x18\x8c\x80x\xf6\x13\x19\xb7\xc7\xc8@\xb1\x89\x9bj$\xe8-Ta\xd8\xe5%\xce\xecK\x17\xf6]^\xb9\xba\xb5(\xecJH\xea'\x02\xd3\xf5\x1e\xd4\x11\xf6\x7f\x1e5\x1cq\x1doͽ\xfa\xd18\xb1N!N\xa4\xe9\x88\x19\xa8T\xce\x0eT/&c\xa2\x00f\x8e\xc6B\x19\x94R\xeb\x9d\x13q\xc9\x03(\n\x0fư\xed1\xe0>>nY\x17\x05\xdf\x16pM\x1a}\x824\xe3Zb\x8c6_\xc0X\x91%P\xe6rH\x1a\xd7r\x840\x9a~\x88\x10e@\x01t\xeb\xf9#\x86\x96\x9eB\x18\x1f\x14E\x87\xb8\xf3Ta\xec\x7f$\xfb\x80.m\x86\x8e\xe6\xb5w`\x05\x14\xe44K\xc5\n%\x1f@\xbb\x1e\x83y!\x05\a(qcv\x01\vz\x93\x1a\r\x83\x90lW\xa3\xa3\xb9f\xa8\t\xa22\"\xa4\xb1\xc0\xf3\xf5\xe5K1\x0f\xbeeE\x9dC~S\xd4Ƃ\xbe\xcbT\x05yX\xe6\x19ղ\x03&~\x9c\x04\xe0C\x8cBd\x80|\xc8\\\xa5\x15\xad\xa6Ĉ\xd4F\x1b\xc7\n\xdc\xe2\x0e2\xd5cچ\x11\x1dUa\xc0b\x95\xcb\x7f\xbd\x8c)Q\x94\x80~\xef\xfd~\f\xe3\x1a\x1aj\xf44j\x04b\xa3g\xc9 \x8fˑ\xb0PF\x888\xabr\x16\xb0\x97k\xcdǔj\x18N\xb3ju>{c \x06\f\x96\xa1گ\xc4\xe2a\xff\xff\x8cL>\x8b\xad\x86\xd6j\xb9\x90\xc8\xceB\x18\xdb\xe3\xe60\xe8o0\xdbsK4E\xafXH\a\x13\x95[\x87y\xbfe\x9a\x9d3\x13b\xa2\xdfH\x9a\x17\xe7=\x8f\t\xd5\xef\x90`{\xa5\x1eS\x88\xf4\xdfX\xaf]\fb\x19m\x1b\xb0-\xec\xf9A(m\x86+\x8a\xf0\r\xb2\xdaF\xf5\x04\xb7,\x17\xbb\x1dh\x84E\x8b\xe0͚\xf9\x14\xb1\xa6\xc3\x04\xd6Q@\xd1\n\x83q\xb5LG\xe6\x115bC\xa1\xa0/\n\x95\x11\xe2\xe8œu\xcf\xc5A\xe45/\xc8\xd0s\x99\xb9\xf1\xf1\x06\xbf\x98{2#\x10'\xf8;w\"\x8c\x02\xb9\xd4[IR\x12н.\x95\x8ey\x9e\xae\x9c\x82\x89\x93\xa1\x89\xd6Ǘ]ڢ\xeb\x02\x8cG\xc59\xb0\xad\u07b9j9\xe5\xc2\xf8\x82o\xa1`\x06\nȬ\xd2q\xf2\xa4\b\x81+\xa9\xfa3B\xd9\x11M\xda\x0f\x82f\x95h[0\xc0܋l\xef\xdcM\x942\x82\xc5r\x05\x864\x06\xaf\xaa\"b\x85\xda2+\x19\xbe\xb39\xa5і\x04\xf51\x84\x1bS$mI\xd4\xc1m\x99\xd1\xc6}\xaa7b\xf3J\xf4\x1e\x9a\xf2\xbb\x84}s\xd2\xfc\xf9\x85\x1d\xc9-\xc0t\u05fa\x84\r_S\xa0\xf6\xfc@\xf3\x0fƸ\xf3f\xcbf\xd8\xfa\xd9g˳p\xadA\xe3\x1f\x84id\xac\uef2dZİ\x9f\xba-\xafh\x11\xde3,\xbfb;QX _j\x0eю\xa33˹\xe7$P\xaa\xed\xc5Rr\x9b\xed?6[7\t-\x06\xb4\x1a\x02p~y\x88a\x88\a\t Y\xe3TЮ\xa3\xd0P\xba\xdd\xcc{\x9a\x1f\xed\x17\xf2\x00\xdf\x7f\xfa\x10[I\xec\x97DI=\x19\xd4\xfb\x81\xa7\xd3E\x81\x06\x98\x04\xb23(rӚ\x18\xcf\xedY_1\xce\x1e\xe1\xe8<\xab\xd1塱\x82\xac\xe5\rH\r\xb4\x81Nj\xe4\x11\x8e\x04\xca\xef\x88'\xc1[\"*\xae<\xc2Ȏ[\xac\xf4\x88\x8a\xf8\xf9}\nG]\xfc@\xa3H\x99Jmi\x88\xea\xe7\x0e\xb3*m\xb0l\x99R\n%P\xfc\xcca7\f륁<\xc2\xf1\x8dq\xec\xc3Y\xb3\x17c\xfbx\xd1A(\f}\x81fX\xc8\x7f\xf8\xca\v\x917\x9d\xd1(0\x9f\x94\xa5//Jb7\x883\t\xec\x1aӴ\x94\xce, ]\x16\xf5\xdf\xe2\xe0\xb6W\xf7вM\x18\xb6\x91\x18\x9f9\xfa,a\xd3\x1e\x02r\x0e\xad\xb26\xb4})\x95\\\xb9%-\xdf\xdb\x02\xa0]\xbc<\xab\x94\xeeq\xeaj!\xc4Q\x14=z\xf7h\xad\xdc/'\xb9'SECU\xf0\f\xf2\xb0\xcbF\x89.\xdc\u0083\xc8X\t\xfa\x01X\x85v#]\xa8\x16hrWΐ\xc2t\xd7\"\x14o\x16F\xf26\xc6\xca\ng}b\xcd\xc0\xe6\xa4ꑬ\x96\xe9\xeai\xa3$\xf3N\xfeP\x12\xf5\xbbi\x98\xcb,\xcbB~\x9d\xfa \x0eI\xe7~\x94\x9c6\x9e\xfe\x86\xe6\x95\xc4\xfb\xefi\u0590\vm\xd6\xec=%\xa1\x16\xd0m\x1fV\t;]%\x81DL\x84a('\a^\xa0\xfb\x80\xca[2(\x9c3\xa1v'\x1eT\x9a\x8ay\xda+\xe3l~\xb31v\xf9\bG\xbf9\xdb\xd5\x12\x97\x1b\x19]\xb5\xef\x17\xd4\xf9'J\xab\xf1Z\x94,\x8e\xec\x92~\xbb$\xc7l\xc9\x149\xc3y[ \xd5\v\xaa~[=\xd6[\xd0\x12,\x98Uɫ\x95\x9f\rV\x95\xd1=NW(UtI\x18\x81qz\xf0x\xb0q\x93P\x89\xee\xff\x1c\x05\x92\xe7C\xa5L$\xd3\"\x82֭2\xd6-\x1e\xf6\\\xf5\x91\xd5Ŕ\xc8ѯ82\xbe\xb3\xa0\x99\xb1J\x87\xe4ETك\xc5u\x94\x1a3/7n\x9fȯd:\xc0\x18\xa0^\xb6\xda\xc5كK\xb7W\x85\xff\x9f\x87\x99\x91\xa3E\xb0+\xad20\xd1l\x84\xb6$Z\x9d\x99\xc5\xdef\xa1\x97\xbb\xc0o\x97\xa4\xd6S\x96\xa1CY\xe6\xc6#i\xcf\b\x8a>~\xeb\xacY\xa3\nÿSD\xf9\x1c\x1c\x19\x9d_(K>L\xa4MF\xf7Ƶ\x0e\x13\xd0\x03s\xc1\x96~\xa8I!-\xf3\xb9\xbdH\xfe֜\x96R\xc8\ru\xc4\u07bd\x98\xa3Â\x19\x88e$\x8d\x95\x01;|\xfb\x96!͇\xd4ؗ\x85T5E\xfb<\x1az\x9c=\xdd\x05I\xe7\x14CG\\*\xdb]\xe8\xf1=\xbd1l'\xb4\xb1-\xc2\v\xa0\n3\x91\xf54:\xbc3\xe2S\xf9Q\xeb\xb3\xc3\xd3ϮugIr\xaf\x9e|\x12\xf3\x92\xa0<\x10\x7f\xcf\x0f\xe0SIAf\xaa\x96\xb4X\x86\xea\x02\xbbY\x00\xd11\xd1\x19\x93D\x9b\xd9i,\xeb2\x9d +\x92N!gWֺM~\xe4\"me\x8b\x9d\xc7V;\x95D9V\xfa\x99\xa1>\x9b\xb2\x9b\xad^\xf2o\xa2\xacK\xc6Kd˒\x98s\xe7\xf20Cj\xbb\xe3\xf5\x13\x17֟\x18r\x9b\xb2˴i\xa6ʪ\x00\v!\xc32S҈\x1c\x1a\xf7\xc1\xf3\x7f4_5V8\xdbqQ\xd4z\x81\x8e^̙\xa51\x9fWO\xcf\x1fȥ#\xb2\"b&.\xd8/p\xb8\xe7\xedG\xa5\x97\xb9̷\x1a\x9e\xdf5\xad\xb4P.\xcb\x7f\xda;\x9d\x85I\xdek\xdf;\xf5\xc2\xcb\xe51\xe6\x9e\xceB%L^\xddӦ\xbc\xba\xa7\xaf\xee\xe9\xab{:(\xaf\xee\xe9\xab{\xfaꞎ\x97W\xf7\xb4S^\xdd\xd3d\xfb\x91\x82\xe1\x8aVn'*$a\x95\x98\xbe1\x87\xf6L_>Kɟ\x05Y\x92]\xbd\x19o9r\x16h\xd1\x19\x12\xd31zM\xba5N\xc90\x99\xdcY\xc9\x04/\xfc\x19\xce\xda\x04\x04\xce>k\xb3\x99\x04\xf0\x8cgm<\xa6õ\xf3gG\x92S\xcf\xdfĜ\xf3\xb8n\xeb\x10S\xf7\xce{\xfc\xb6ii\xa1\xfc\\yK\x96~\xee}3\xd2\xec;N\xbess\x94\xd9^+\xa9j\xe3Wx\xb0\x87\xf7\x99\xbb\n td\x96(\x83wl\xaf\xea\xc8\x19\x8f\x19\xba&d\xde\xc6\xf3m}\x06\aX~x\xb7\xee\xffb\x95Ͼ\x8d`\xfd$\xec\xde\xdd\xfa\xc0\xf3\x1c\x1d\xf5\xce\x11\x9f0y\xfd\xbdCC\xc1\x8b@T\x9aIQ8\xa9\f\x10\xfa\x06\xf4s\xe5\x96\xfc\xce\xf6[\xe6\x17\x9e\xd2st\x97f\xe66\xb9\x94\xf3^\xf2w\xe4\xe3.;,5\x9b{\x9b\x824Kɸ\x1dϥ\x9d\x81\xba$\xcf6uM1!\xa76=\x936\x8d<\x8cn\vK͟M\x8eBSse_&C61/\xb6\x93\xed:\v\xf2\xccl\xd8d\x82\xa5e\xbe&\xe7\xbbv\xb2X\xe7\xa95\x91\xe5:\x9e\xbb:\vr,\xb75%c5\t\xd7\xe4<\xd5&\xfbt~g仲S\x9f\xff\x1c\xccs\xae[L\xe7\x9a&e\x98&\xadm\xcc㜔C\xba4s4\x89\xaaK\xb3D\x9b\fЉ\x8e\x93rCO\xf3>\xa7\x862\x9b\x11\x1a\xcf\xf6\x9c\x02;\x96\a\x9a\x90\xe39\x01\xb2\x9b\xfd\xb9\xd8\r\x98\x95\xa6\xd9\nKs7\xc7\xef\x00\fe\xde:\x17\xbf\x86\xcc~/\x99\x94\xee9\xcd)\xc1\xdd\xe7A\x13\x94\x96\xe0'\x8e9\xe2\xf1Pٹ\xe7g8\xe2\x11\x90\x9b\x1d+\xeb\u008a\xaa\xe8\\Pf\xf7pl\xae\xfc\xf9E\xd1\xc1\xf5푠}\xfe҈|\fd?\xa4\xe0\x86=AQ\xe0\xbf'T\xc8ܕ\x97\x99Z\x01Z\xa9\xf8F\xa0\xbf\xea\xc8ߗy\xe5\x96\xc5\xe8T?Y\xc0\x12!M_\x805iJ\xa6\xddc\xe7\xd5ӷ\xbf֠\x8f\x8c\xee\xdc\n~PT\xcc\xdaӞ~2\x1b\x8c\t\x83\xf2\xf1Z\xcc]\xbc\xdaWF\xf1\xd9Ш\x00\xf6^:\xc3<ĕ`\xa1\xd6ié)e\x8b\xd1S\f\x84T\r\x84H\xfb\x14\xef{\xc9\xf1Ǘ\b\xae\x9e#\xbcJrD^\"\xc4z\xa9 ki\x98\xb5$y#\xe9\xf8\xe2K\x04[K\u00adE>c\xfa\xf1ė:\x96\xf8\x02a\xd7ف\xd7\"ҥ\x1e;\\\x1c~%\x8co\xe6\x98\u124f\x96\x002z\xbcp<\x04K\x80xr\xacp6\bK\x99\a\xc30\xed\xbb\x0f\t&'2-\xdaMOMBJ\xdb\xe8\x9e?\xfc\x97x\xe8/q\x1b<\x05\xfb\xc4\xc3}\xcb\x0f\xf5%\xd2\xf9\xcc\xf0l\xb2\xeb\xc4\xc3{\x8b\x02\xb43C\xb4I\x88S\x87\xf5\xa6\x83\xb4\xe9\x05\xb8\xe1!\xbd3܉\x04\tK\xa8\xb2\xfc\xa0\xddwo\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\x8f\a\xb8\x1d;\x03\x15״\x8f\xbf=\xba\xa4 \xb3f\x1fy\xb6oz\x88\x80\xa4~\xf7ܰ\x9d\xd2%\xb7\xec\xb2\xd9\n}\xeb:\xc0\xbf/\u05cc\xfd\xa8\x9a\xf4\x91νb\x11\xa8F\x94UqĈ\x89]v\xc1|\x9f\xe0D\x056\xe0s\xab\n\x91E<\xc4\xd1+\xe9\\\x83\x93\v}\xe8Ҽ\xac\x93\x05\x11\x8bQ\xb0\xb9\xf0\x17\x16\x0e.\x15v\u05cb\x9f\xb9t\xc1+\xf1_\xf4VO\xda\xda\xd8\xfb\xdb\rU\x0fRE\xef\xfc4\xd9s\x8d\x8cmaZ\xa1\xb7\x03'ף\vu${\xb5\xf9s\x02\"\xbd\x94\x11\xfc\f\xaf\xc63\x85Z\xecv\xe3\xb0\\\x93`qydʿ\x85 t\xbe\xaa\xb8\x8en\xea1/\x0f檇a\xb0\xe3s+X\x93f\xed\xf4\xe5\x8fn\xe9\xd1<<\x02B\x9b\xbdǪ\xbf\x8dN\x94\xee\xd0\xf3{p\x9a>\xb8<{d\xf9\x05p\x9av\x99VD\xc5\xc8O\xd1t\xbcg_=4\xfe\x12\xf9\x9f\xd5\x01>DW\x11\xfb\xcfb\f\x9a\x8c$\xd0\x05\xa8Sצ\xb7Ys\xf1묟!#.\xa0\xe2/\xbe^0>\xdfb\xfc-\r\xba\xff;\xc0\x9e\xb0m8eo\xbfR\xb8٨K?\xc3}0\x19\x96\n\aW\xb5F@ƞ\x12y.jY\xa5\xf9\x03\xfc\xa4\xdck/)\xd4\xea\xb7\xe8=\xf8㝹\x90M\xec\xe7ZL\x99\xfb\xb1\r\x01\xb6\x87\fN\xae\xa3Glϼm\xdf\xda\"ap\xf7\xf7?\xb9\x01\xd1\xd3\x18\x1f\xfc\xbb\x17\xa8w\r \xa5\xc3@]\xa3m\\;\xed\xd5\x13\xdd\xe7\xde}\xae\xa2\xf3\xe8\x15С\x06J\x1b=k4uU(\x9e\x83\xbe\xa1\xe79\x12\x06\xf6\xe7^\x83\x81e\xef?\xf2\xe1\xedcd`\xa1\xe7\x17\xcc\fA\x97\xad(\xa0\xf8Q\x14`\x1c≊\xfe\xf6\xb4e\xa3\xf7\xebr\xeb\x1c\xd4\x1d\xfe\xd8t2aM\xddPiM\xbf\x02\x8d\x8e\xa0[\xfd\xafM\x10\xf0ib\xb0\x86\x8fBZx\x88$\xf8\xcch\xf8C\xef\xe9\x8f0IR\xd4\xda\xd7\xf1\x96\x1do\xb93]\xa7\x92\x05\xd5.\n\x8b\x1b\xa32A\x0e6\xed\x8eЩ\x91\x97\xbbsz*P\x9a\xa0cm\xe0\xf3\x93\x04\xfd%\xa8d\xb3\x91\xb1\xb76\xfa\xb3\xe6\xa4a\xf4\x9d\r\xabȭ\x1fT\x1f\v\xe7\xa4'\x90q\xaf\xb4\x84m\x1ea\x9a\xf7\xa7NI73\xaf\xe2Z~\xdc'Y\x8d?\x87\xb3j^\xe8\xb9H\xa0\xac{\x85\xa6\x0fx\xfc\x811\xf7\\M\xc6+[\xeb\xa0rjM\x17p#\x10p\xf7S\x9f\xf7\xc4X\xfb \xd5\f/ۇ\x9dڅ\xef\xd9'#G\xf8\xd7<\x12\x16}Qȅ\x8c\xeeI\xc7\x15\xc2?\x8f\x9d\xa3\xf3\x00q\xbes\xefD%\x8c\xf7.<\x95u:\xe0f\x188\xe4\xd8\xcbS/9\x12\xbaz}f\f\xb7X\xa79\xbb\xe8E\x86\x1a\x86+\xdb\xefbL\x18?\x8c\xb6b\x9f\xe04\xf8\\\xb1\x8f\x12\aqJ\x00w\xe2\frZ\xf8\x1f{(rr\x88\x87\xa6\x15\x1d\xf7\x1b\xd1{}\x85=\xa8>HF\xa6'x\x9a*\xeehߘ\x80\xfeA\xecܮL\x86c\xfa\xe3I\x8d\xa8\n\x9eT\xbf1\xd5;\xaa\x1cN>\xd2kbyGH\xbc\xdf\xd9\xfdRo\xdb\x1b\xf4\xd9\xdf\xfe~\xf1\x7f\x01\x00\x00\xff\xff=3w\xbfMx\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/pkg/apis/velero/v1/schedule_types.go b/pkg/apis/velero/v1/schedule_types.go index 6cb553b9a85..6a5f885abce 100644 --- a/pkg/apis/velero/v1/schedule_types.go +++ b/pkg/apis/velero/v1/schedule_types.go @@ -42,6 +42,13 @@ type ScheduleSpec struct { // Paused specifies whether the schedule is paused or not // +optional Paused bool `json:"paused,omitempty"` + + // SkipImmediately specifies whether to skip backup if schedule is due immediately from `schedule.status.lastBackup` timestamp when schedule is unpaused or if schedule is new. + // If true, backup will be skipped immediately when schedule is unpaused if it is due based on .Status.LastBackupTimestamp or schedule is new, and will run at next schedule time. + // If false, backup will not be skipped immediately when schedule is unpaused, but will run at next schedule time. + // If empty, will follow server configuration (default: false). + // +optional + SkipImmediately *bool `json:"skipImmediately,omitempty"` } // SchedulePhase is a string representation of the lifecycle phase @@ -75,6 +82,11 @@ type ScheduleStatus struct { // +nullable LastBackup *metav1.Time `json:"lastBackup,omitempty"` + // LastSkipped is the last time a Schedule was skipped + // +optional + // +nullable + LastSkipped *metav1.Time `json:"lastSkipped,omitempty"` + // ValidationErrors is a slice of all validation errors (if // applicable) // +optional diff --git a/pkg/apis/velero/v1/zz_generated.deepcopy.go b/pkg/apis/velero/v1/zz_generated.deepcopy.go index 7688fc0252e..5f80894fe34 100644 --- a/pkg/apis/velero/v1/zz_generated.deepcopy.go +++ b/pkg/apis/velero/v1/zz_generated.deepcopy.go @@ -1492,6 +1492,11 @@ func (in *ScheduleSpec) DeepCopyInto(out *ScheduleSpec) { *out = new(bool) **out = **in } + if in.SkipImmediately != nil { + in, out := &in.SkipImmediately, &out.SkipImmediately + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduleSpec. @@ -1511,6 +1516,10 @@ func (in *ScheduleStatus) DeepCopyInto(out *ScheduleStatus) { in, out := &in.LastBackup, &out.LastBackup *out = (*in).DeepCopy() } + if in.LastSkipped != nil { + in, out := &in.LastSkipped, &out.LastSkipped + *out = (*in).DeepCopy() + } if in.ValidationErrors != nil { in, out := &in.ValidationErrors, &out.ValidationErrors *out = make([]string, len(*in)) diff --git a/pkg/builder/schedule_builder.go b/pkg/builder/schedule_builder.go index 136487ec54a..7c9a9b1d941 100644 --- a/pkg/builder/schedule_builder.go +++ b/pkg/builder/schedule_builder.go @@ -89,3 +89,9 @@ func (b *ScheduleBuilder) Template(spec velerov1api.BackupSpec) *ScheduleBuilder b.object.Spec.Template = spec return b } + +// SkipImmediately sets the Schedule's SkipImmediately. +func (b *ScheduleBuilder) SkipImmediately(skip *bool) *ScheduleBuilder { + b.object.Spec.SkipImmediately = skip + return b +} diff --git a/pkg/cmd/cli/install/install.go b/pkg/cmd/cli/install/install.go index fc5784082de..5d2ccd28a5f 100644 --- a/pkg/cmd/cli/install/install.go +++ b/pkg/cmd/cli/install/install.go @@ -83,6 +83,7 @@ type Options struct { UploaderType string DefaultSnapshotMoveData bool DisableInformerCache bool + ScheduleSkipImmediately bool } // BindFlags adds command line values to the options struct. @@ -126,6 +127,7 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.UploaderType, "uploader-type", o.UploaderType, fmt.Sprintf("The type of uploader to transfer the data of pod volumes, the supported values are '%s', '%s'", uploader.ResticType, uploader.KopiaType)) flags.BoolVar(&o.DefaultSnapshotMoveData, "default-snapshot-move-data", o.DefaultSnapshotMoveData, "Bool flag to configure Velero server to move data by default for all snapshots supporting data movement. Optional.") flags.BoolVar(&o.DisableInformerCache, "disable-informer-cache", o.DisableInformerCache, "Disable informer cache for Get calls on restore. With this enabled, it will speed up restore in cases where there are backup resources which already exist in the cluster, but for very large clusters this will increase velero memory usage. Default is false (don't disable). Optional.") + flags.BoolVar(&o.ScheduleSkipImmediately, "schedule-skip-immediately", o.ScheduleSkipImmediately, "Skip the first scheduled backup immediately after creating a schedule. Default is false (don't skip).") } // NewInstallOptions instantiates a new, default InstallOptions struct. @@ -154,6 +156,7 @@ func NewInstallOptions() *Options { UploaderType: uploader.KopiaType, DefaultSnapshotMoveData: false, DisableInformerCache: true, + ScheduleSkipImmediately: false, } } @@ -220,6 +223,7 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) { UploaderType: o.UploaderType, DefaultSnapshotMoveData: o.DefaultSnapshotMoveData, DisableInformerCache: o.DisableInformerCache, + ScheduleSkipImmediately: o.ScheduleSkipImmediately, }, nil } diff --git a/pkg/cmd/cli/schedule/create.go b/pkg/cmd/cli/schedule/create.go index 85f53acc328..32f277136ae 100644 --- a/pkg/cmd/cli/schedule/create.go +++ b/pkg/cmd/cli/schedule/create.go @@ -82,6 +82,7 @@ example: "@every 2h30m".`, type CreateOptions struct { BackupOptions *backup.CreateOptions + SkipOptions *SkipOptions Schedule string UseOwnerReferencesInBackup bool Paused bool @@ -90,11 +91,13 @@ type CreateOptions struct { func NewCreateOptions() *CreateOptions { return &CreateOptions{ BackupOptions: backup.NewCreateOptions(), + SkipOptions: NewSkipOptions(), } } func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) { o.BackupOptions.BindFlags(flags) + o.SkipOptions.BindFlags(flags) flags.StringVar(&o.Schedule, "schedule", o.Schedule, "A cron expression specifying a recurring schedule for this backup to run") flags.BoolVar(&o.UseOwnerReferencesInBackup, "use-owner-references-in-backup", o.UseOwnerReferencesInBackup, "Specifies whether to use OwnerReferences on backups created by this Schedule. Notice: if set to true, when schedule is deleted, backups will be deleted too.") flags.BoolVar(&o.Paused, "paused", o.Paused, "Specifies whether the newly created schedule is paused or not.") @@ -160,6 +163,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { Schedule: o.Schedule, UseOwnerReferencesInBackup: &o.UseOwnerReferencesInBackup, Paused: o.Paused, + SkipImmediately: o.SkipOptions.SkipImmediately.Value, }, } diff --git a/pkg/cmd/cli/schedule/pause.go b/pkg/cmd/cli/schedule/pause.go index decda82605c..820e887a7d8 100644 --- a/pkg/cmd/cli/schedule/pause.go +++ b/pkg/cmd/cli/schedule/pause.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" kubeerrs "k8s.io/apimachinery/pkg/util/errors" @@ -36,6 +37,7 @@ import ( // NewPauseCommand creates the command for pause func NewPauseCommand(f client.Factory, use string) *cobra.Command { o := cli.NewSelectOptions("pause", "schedule") + pauseOpts := NewPauseOptions() c := &cobra.Command{ Use: use, @@ -54,16 +56,31 @@ func NewPauseCommand(f client.Factory, use string) *cobra.Command { Run: func(c *cobra.Command, args []string) { cmd.CheckError(o.Complete(args)) cmd.CheckError(o.Validate()) - cmd.CheckError(runPause(f, o, true)) + cmd.CheckError(runPause(f, o, true, pauseOpts.SkipOptions.SkipImmediately.Value)) }, } o.BindFlags(c.Flags()) + pauseOpts.BindFlags(c.Flags()) return c } -func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error { +type PauseOptions struct { + SkipOptions *SkipOptions +} + +func NewPauseOptions() *PauseOptions { + return &PauseOptions{ + SkipOptions: NewSkipOptions(), + } +} + +func (o *PauseOptions) BindFlags(flags *pflag.FlagSet) { + o.SkipOptions.BindFlags(flags) +} + +func runPause(f client.Factory, o *cli.SelectOptions, paused bool, skipImmediately *bool) error { crClient, err := f.KubebuilderClient() if err != nil { return err @@ -120,6 +137,7 @@ func runPause(f client.Factory, o *cli.SelectOptions, paused bool) error { continue } schedule.Spec.Paused = paused + schedule.Spec.SkipImmediately = skipImmediately if err := crClient.Update(context.TODO(), schedule); err != nil { return errors.Wrapf(err, "failed to update schedule %s", schedule.Name) } diff --git a/pkg/cmd/cli/schedule/skip_options.go b/pkg/cmd/cli/schedule/skip_options.go new file mode 100644 index 00000000000..14ea1b4ce1f --- /dev/null +++ b/pkg/cmd/cli/schedule/skip_options.go @@ -0,0 +1,20 @@ +package schedule + +import ( + "github.com/spf13/pflag" + + "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" +) + +type SkipOptions struct { + SkipImmediately flag.OptionalBool +} + +func NewSkipOptions() *SkipOptions { + return &SkipOptions{} +} + +func (o *SkipOptions) BindFlags(flags *pflag.FlagSet) { + f := flags.VarPF(&o.SkipImmediately, "skip-immediately", "", "Skip the next scheduled backup immediately") + f.NoOptDefVal = "" // default to nil so server options can take precedence +} diff --git a/pkg/cmd/cli/schedule/unpause.go b/pkg/cmd/cli/schedule/unpause.go index 6b9ff2a0a73..72197a93492 100644 --- a/pkg/cmd/cli/schedule/unpause.go +++ b/pkg/cmd/cli/schedule/unpause.go @@ -27,7 +27,7 @@ import ( // NewUnpauseCommand creates the command for unpause func NewUnpauseCommand(f client.Factory, use string) *cobra.Command { o := cli.NewSelectOptions("pause", "schedule") - + pauseOpts := NewPauseOptions() c := &cobra.Command{ Use: use, Short: "Unpause schedules", @@ -45,11 +45,12 @@ func NewUnpauseCommand(f client.Factory, use string) *cobra.Command { Run: func(c *cobra.Command, args []string) { cmd.CheckError(o.Complete(args)) cmd.CheckError(o.Validate()) - cmd.CheckError(runPause(f, o, false)) + cmd.CheckError(runPause(f, o, false, pauseOpts.SkipOptions.SkipImmediately.Value)) }, } o.BindFlags(c.Flags()) + pauseOpts.BindFlags(c.Flags()) return c } diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 5098a7b13f5..eb5db9ed9dd 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -133,6 +133,7 @@ type serverConfig struct { maxConcurrentK8SConnections int defaultSnapshotMoveData bool disableInformerCache bool + scheduleSkipImmediately bool } func NewCommand(f client.Factory) *cobra.Command { @@ -163,6 +164,7 @@ func NewCommand(f client.Factory) *cobra.Command { maxConcurrentK8SConnections: defaultMaxConcurrentK8SConnections, defaultSnapshotMoveData: false, disableInformerCache: defaultDisableInformerCache, + scheduleSkipImmediately: false, } ) @@ -235,6 +237,7 @@ func NewCommand(f client.Factory) *cobra.Command { command.Flags().IntVar(&config.maxConcurrentK8SConnections, "max-concurrent-k8s-connections", config.maxConcurrentK8SConnections, "Max concurrent connections number that Velero can create with kube-apiserver. Default is 30.") command.Flags().BoolVar(&config.defaultSnapshotMoveData, "default-snapshot-move-data", config.defaultSnapshotMoveData, "Move data by default for all snapshots supporting data movement.") command.Flags().BoolVar(&config.disableInformerCache, "disable-informer-cache", config.disableInformerCache, "Disable informer cache for Get calls on restore. With this enabled, it will speed up restore in cases where there are backup resources which already exist in the cluster, but for very large clusters this will increase velero memory usage. Default is false (don't disable).") + command.Flags().BoolVar(&config.scheduleSkipImmediately, "schedule-skip-immediately", config.scheduleSkipImmediately, "Skip the first scheduled backup immediately after creating a schedule. Default is false (don't skip).") return command } @@ -915,7 +918,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string } if _, ok := enabledRuntimeControllers[controller.Schedule]; ok { - if err := controller.NewScheduleReconciler(s.namespace, s.logger, s.mgr.GetClient(), s.metrics).SetupWithManager(s.mgr); err != nil { + if err := controller.NewScheduleReconciler(s.namespace, s.logger, s.mgr.GetClient(), s.metrics, s.config.scheduleSkipImmediately).SetupWithManager(s.mgr); err != nil { s.logger.Fatal(err, "unable to create controller", "controller", controller.Schedule) } } diff --git a/pkg/controller/schedule_controller.go b/pkg/controller/schedule_controller.go index ed36896eee3..a1d2889fff0 100644 --- a/pkg/controller/schedule_controller.go +++ b/pkg/controller/schedule_controller.go @@ -44,10 +44,11 @@ const ( type scheduleReconciler struct { client.Client - namespace string - logger logrus.FieldLogger - clock clocks.WithTickerAndDelayedExecution - metrics *metrics.ServerMetrics + namespace string + logger logrus.FieldLogger + clock clocks.WithTickerAndDelayedExecution + metrics *metrics.ServerMetrics + skipImmediately bool } func NewScheduleReconciler( @@ -55,13 +56,15 @@ func NewScheduleReconciler( logger logrus.FieldLogger, client client.Client, metrics *metrics.ServerMetrics, + skipImmediately bool, ) *scheduleReconciler { return &scheduleReconciler{ - Client: client, - namespace: namespace, - logger: logger, - clock: clocks.RealClock{}, - metrics: metrics, + Client: client, + namespace: namespace, + logger: logger, + clock: clocks.RealClock{}, + metrics: metrics, + skipImmediately: skipImmediately, } } @@ -99,11 +102,18 @@ func (c *scheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } return ctrl.Result{}, errors.Wrapf(err, "error getting schedule %s", req.String()) } - c.metrics.InitSchedule(schedule.Name) original := schedule.DeepCopy() + if schedule.Spec.SkipImmediately == nil { + schedule.Spec.SkipImmediately = &c.skipImmediately + } + if schedule.Spec.SkipImmediately != nil && *schedule.Spec.SkipImmediately { + *schedule.Spec.SkipImmediately = false + schedule.Status.LastSkipped = &metav1.Time{Time: c.clock.Now()} + } + // validation - even if the item is Enabled, we can't trust it // so re-validate currentPhase := schedule.Status.Phase @@ -116,10 +126,25 @@ func (c *scheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c schedule.Status.Phase = velerov1.SchedulePhaseEnabled } - // update status if it's changed + scheduleNeedsPatch := false + errStringArr := make([]string, 0) if currentPhase != schedule.Status.Phase { + scheduleNeedsPatch = true + errStringArr = append(errStringArr, fmt.Sprintf("phase to %s", schedule.Status.Phase)) + } + // update spec.SkipImmediately if it's changed + if original.Spec.SkipImmediately != schedule.Spec.SkipImmediately { + scheduleNeedsPatch = true + errStringArr = append(errStringArr, fmt.Sprintf("spec.skipImmediately to %v", schedule.Spec.SkipImmediately)) + } + // update status if it's changed + if original.Status.LastSkipped != schedule.Status.LastSkipped { + scheduleNeedsPatch = true + errStringArr = append(errStringArr, fmt.Sprintf("last skipped to %v", schedule.Status.LastSkipped)) + } + if scheduleNeedsPatch { if err := c.Patch(ctx, schedule, client.MergeFrom(original)); err != nil { - return ctrl.Result{}, errors.Wrapf(err, "error updating phase of schedule %s to %s", req.String(), schedule.Status.Phase) + return ctrl.Result{}, errors.Wrapf(err, "error updating %v for schedule %s", errStringArr, req.String()) } } @@ -132,13 +157,15 @@ func (c *scheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // If there are backup created by this schedule still in New or InProgress state, // skip current backup creation to avoid running overlap backups. // As the schedule must be validated before checking whether it's due, we cannot put the checking log in Predicate - if c.ifDue(schedule, cronSchedule) && !c.checkIfBackupInNewOrProgress(schedule) { + due, nextRunTime := c.ifDue(schedule, cronSchedule) + durationTillNextRun := nextRunTime.Sub(c.clock.Now()) + if due && !c.checkIfBackupInNewOrProgress(schedule) { if err := c.submitBackup(ctx, schedule); err != nil { - return ctrl.Result{}, errors.Wrapf(err, "error submit backup for schedule %s", req.String()) + return ctrl.Result{RequeueAfter: durationTillNextRun}, errors.Wrapf(err, "error submit backup for schedule %s", req.String()) } } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: durationTillNextRun}, nil } func parseCronSchedule(itm *velerov1.Schedule, logger logrus.FieldLogger) (cron.Schedule, []string) { @@ -208,16 +235,16 @@ func (c *scheduleReconciler) checkIfBackupInNewOrProgress(schedule *velerov1.Sch } // ifDue check whether schedule is due to create a new backup. -func (c *scheduleReconciler) ifDue(schedule *velerov1.Schedule, cronSchedule cron.Schedule) bool { +func (c *scheduleReconciler) ifDue(schedule *velerov1.Schedule, cronSchedule cron.Schedule) (bool, time.Time) { isDue, nextRunTime := getNextRunTime(schedule, cronSchedule, c.clock.Now()) log := c.logger.WithField("schedule", kube.NamespaceAndName(schedule)) if !isDue { log.WithField("nextRunTime", nextRunTime).Debug("Schedule is not due, skipping") - return false + return false, nextRunTime } - return true + return true, nextRunTime } // submitBackup create a backup from schedule. @@ -249,6 +276,9 @@ func getNextRunTime(schedule *velerov1.Schedule, cronSchedule cron.Schedule, asO } else { lastBackupTime = schedule.CreationTimestamp.Time } + if schedule.Status.LastSkipped != nil && schedule.Status.LastSkipped.After(lastBackupTime) { + lastBackupTime = schedule.Status.LastSkipped.Time + } nextRunTime := cronSchedule.Next(lastBackupTime) diff --git a/pkg/controller/schedule_controller_test.go b/pkg/controller/schedule_controller_test.go index 44e728d7785..91389ade0cd 100644 --- a/pkg/controller/schedule_controller_test.go +++ b/pkg/controller/schedule_controller_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" testclocks "k8s.io/utils/clock/testing" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -36,6 +37,7 @@ import ( velerotest "github.com/vmware-tanzu/velero/pkg/test" ) +// Test reconcile function of schedule controller. Pause is not covered as event filter will not allow it through func TestReconcileOfSchedule(t *testing.T) { require.Nil(t, velerov1.AddToScheme(scheme.Scheme)) @@ -44,15 +46,17 @@ func TestReconcileOfSchedule(t *testing.T) { } tests := []struct { - name string - scheduleKey string - schedule *velerov1.Schedule - fakeClockTime string - expectedPhase string - expectedValidationErrors []string - expectedBackupCreate *velerov1.Backup - expectedLastBackup string - backup *velerov1.Backup + name string + scheduleKey string + schedule *velerov1.Schedule + fakeClockTime string + expectedPhase string + expectedValidationErrors []string + expectedBackupCreate *velerov1.Backup + expectedLastBackup string + expectedLastSkipped string + backup *velerov1.Backup + reconcilerSkipImmediately bool }{ { name: "missing schedule triggers no backup", @@ -88,6 +92,13 @@ func TestReconcileOfSchedule(t *testing.T) { expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1.ScheduleNameLabel, "name")).Result(), expectedLastBackup: "2017-01-01 12:00:00", }, + { + name: "schedule with phase New and SkipImmediately gets validated and does not trigger a backup", + schedule: newScheduleBuilder(velerov1.SchedulePhaseNew).CronSchedule("@every 5m").SkipImmediately(pointer.Bool(true)).Result(), + fakeClockTime: "2017-01-01 12:00:00", + expectedPhase: string(velerov1.SchedulePhaseEnabled), + expectedLastSkipped: "2017-01-01 12:00:00", + }, { name: "schedule with phase Enabled gets re-validated and triggers a backup if valid", schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").Result(), @@ -103,6 +114,35 @@ func TestReconcileOfSchedule(t *testing.T) { expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1.ScheduleNameLabel, "name")).Result(), expectedLastBackup: "2017-01-01 12:00:00", }, + { + name: "schedule that's already run but has SkippedImmediately=nil gets LastBackup updated", + schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").SkipImmediately(nil).Result(), + fakeClockTime: "2017-01-01 12:00:00", + expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1.ScheduleNameLabel, "name")).Result(), + expectedLastBackup: "2017-01-01 12:00:00", + }, + { + name: "schedule that's already run but has SkippedImmediately=false gets LastBackup updated", + schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").SkipImmediately(pointer.Bool(false)).Result(), + fakeClockTime: "2017-01-01 12:00:00", + expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1.ScheduleNameLabel, "name")).Result(), + expectedLastBackup: "2017-01-01 12:00:00", + }, + { + name: "schedule that's already run, server has skipImmediately set to true, and Schedule has SkippedImmediately=nil do not get LastBackup updated", + schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").SkipImmediately(nil).Result(), + fakeClockTime: "2017-01-01 12:00:00", + expectedLastBackup: "2000-01-01 00:00:00", + expectedLastSkipped: "2017-01-01 12:00:00", + reconcilerSkipImmediately: true, + }, + { + name: "schedule that's already run but has SkippedImmediately=true do not get LastBackup updated", + schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").SkipImmediately(pointer.Bool(true)).Result(), + fakeClockTime: "2017-01-01 12:00:00", + expectedLastBackup: "2000-01-01 00:00:00", + expectedLastSkipped: "2017-01-01 12:00:00", + }, { name: "schedule already has backup in New state.", schedule: newScheduleBuilder(velerov1.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").Result(), @@ -119,8 +159,8 @@ func TestReconcileOfSchedule(t *testing.T) { testTime time.Time err error ) - - reconciler := NewScheduleReconciler("namespace", logger, client, metrics.NewServerMetrics()) + name := t.Name() + reconciler := NewScheduleReconciler("namespace"+name, logger, client, metrics.NewServerMetrics(), test.reconcilerSkipImmediately) if test.fakeClockTime != "" { testTime, err = time.Parse("2006-01-02 15:04:05", test.fakeClockTime) @@ -151,8 +191,14 @@ func TestReconcileOfSchedule(t *testing.T) { } if len(test.expectedLastBackup) > 0 { require.Nil(t, err) + require.NotNil(t, schedule.Status.LastBackup) assert.Equal(t, parseTime(test.expectedLastBackup).Unix(), schedule.Status.LastBackup.Unix()) } + if len(test.expectedLastSkipped) > 0 { + require.Nil(t, err) + require.NotNil(t, schedule.Status.LastSkipped) + assert.Equal(t, parseTime(test.expectedLastSkipped).Unix(), schedule.Status.LastSkipped.Unix()) + } backups := &velerov1.BackupList{} require.Nil(t, client.List(ctx, backups)) @@ -403,7 +449,7 @@ func TestCheckIfBackupInNewOrProgress(t *testing.T) { err = client.Create(ctx, newBackup) require.NoError(t, err, "fail to create backup in New phase in TestCheckIfBackupInNewOrProgress: %v", err) - reconciler := NewScheduleReconciler("ns", logger, client, metrics.NewServerMetrics()) + reconciler := NewScheduleReconciler("ns", logger, client, metrics.NewServerMetrics(), false) result := reconciler.checkIfBackupInNewOrProgress(testSchedule) assert.True(t, result) @@ -418,7 +464,7 @@ func TestCheckIfBackupInNewOrProgress(t *testing.T) { err = client.Create(ctx, inProgressBackup) require.NoError(t, err, "fail to create backup in InProgress phase in TestCheckIfBackupInNewOrProgress: %v", err) - reconciler = NewScheduleReconciler("namespace", logger, client, metrics.NewServerMetrics()) + reconciler = NewScheduleReconciler("namespace", logger, client, metrics.NewServerMetrics(), false) result = reconciler.checkIfBackupInNewOrProgress(testSchedule) assert.True(t, result) } diff --git a/pkg/install/deployment.go b/pkg/install/deployment.go index 7c1bd7a81f7..383b40856a9 100644 --- a/pkg/install/deployment.go +++ b/pkg/install/deployment.go @@ -50,6 +50,7 @@ type podTemplateConfig struct { defaultSnapshotMoveData bool privilegedNodeAgent bool disableInformerCache bool + scheduleSkipImmediately bool } func WithImage(image string) podTemplateOption { @@ -170,6 +171,12 @@ func WithPrivilegedNodeAgent() podTemplateOption { } } +func WithScheduleSkipImmediately(b bool) podTemplateOption { + return func(c *podTemplateConfig) { + c.scheduleSkipImmediately = b + } +} + func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment { // TODO: Add support for server args c := &podTemplateConfig{ @@ -203,6 +210,10 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment args = append(args, "--disable-informer-cache=true") } + if c.scheduleSkipImmediately { + args = append(args, "--schedule-skip-immediately=true") + } + if len(c.uploaderType) > 0 { args = append(args, fmt.Sprintf("--uploader-type=%s", c.uploaderType)) } diff --git a/pkg/install/resources.go b/pkg/install/resources.go index 2e9e1bc3e23..700b7b89c66 100644 --- a/pkg/install/resources.go +++ b/pkg/install/resources.go @@ -255,6 +255,7 @@ type VeleroOptions struct { UploaderType string DefaultSnapshotMoveData bool DisableInformerCache bool + ScheduleSkipImmediately bool } func AllCRDs() *unstructured.UnstructuredList { @@ -338,6 +339,7 @@ func AllResources(o *VeleroOptions) *unstructured.UnstructuredList { WithGarbageCollectionFrequency(o.GarbageCollectionFrequency), WithPodVolumeOperationTimeout(o.PodVolumeOperationTimeout), WithUploaderType(o.UploaderType), + WithScheduleSkipImmediately(o.ScheduleSkipImmediately), } if len(o.Features) > 0 {