diff --git a/.github/workflows/warehouses_e2e_tests.yml b/.github/workflows/warehouses_e2e_tests.yml index 97b9b5db..8c66b6a5 100644 --- a/.github/workflows/warehouses_e2e_tests.yml +++ b/.github/workflows/warehouses_e2e_tests.yml @@ -5,12 +5,14 @@ on: - "main" paths: - ".github/workflows/warehouses_e2e_tests.yml" + - "infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py" - "elt-common/**" - "warehouses/accelerator/extract_load/opralogweb/**" pull_request: types: [opened, synchronize, reopened] paths: - ".github/workflows/warehouses_e2e_tests.yml" + - "infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py" - "elt-common/**" - "warehouses/accelerator/extract_load/opralogweb/**" diff --git a/docs-devel/deployment/index.md b/docs-devel/deployment/index.md index 72361c4b..b5016ca9 100644 --- a/docs-devel/deployment/index.md +++ b/docs-devel/deployment/index.md @@ -28,12 +28,14 @@ Deploy the services using Ansible: ```bash > cd infra/ansible -> ansible-playbook -i inventories//inventory.ini site.yml +> ansible-playbook -i inventories//inventory.ini site.yml -e lakekeeper_admin_user= ``` +The `-e lakekeeper_admin_user=` argument is only required the first time Lakekeeper is deployed. + Once deployed the services are available at: -- Keycloak: /iceberg> -- Lakekeeper: /authn> +- Keycloak: /auth> +- Lakekeeper: /iceberg> - Superset instances: - /workspace/accelerator> diff --git a/infra/ansible/group_vars/all/all.yml b/infra/ansible/group_vars/all/all.yml index cbf92446..d2860580 100644 --- a/infra/ansible/group_vars/all/all.yml +++ b/infra/ansible/group_vars/all/all.yml @@ -20,7 +20,7 @@ logrotate_frequency: weekly logrotate_keep: 30 logrotate_compress: true -keycloak_base_path: /authn +keycloak_base_path: /auth keycloak_http_port: 8080 keycloak_http_management_port: 9000 keycloak_url: "https://{{ top_level_domain }}{{ keycloak_base_path }}" @@ -33,7 +33,7 @@ lakekeeper_base_path: /iceberg lakekeeper_http_port: 8181 lakekeeper_project_name: "ISIS Analytics Data Platform" lakekeeper_catalog: - uri: "https://{{ top_level_domain }}{{ lakekeeper_base_path }}/catalog" + catalog_uri: "https://{{ top_level_domain }}{{ lakekeeper_base_path }}/catalog" warehouses: accelerator: storage: diff --git a/infra/ansible/group_vars/keycloak.yml b/infra/ansible/group_vars/keycloak.yml index 8921ff47..1b9b442d 100644 --- a/infra/ansible/group_vars/keycloak.yml +++ b/infra/ansible/group_vars/keycloak.yml @@ -55,8 +55,8 @@ keycloak_clients: attributes: access.token.lifespan: 600 # public clients - - client_id: lakekeeper-ui - name: "Lakekeeper catalog UI" + - client_id: lakekeeper-api + name: "Lakekeeper API" protocol: "openid-connect" public_client: true redirect_uris: diff --git a/infra/ansible/inventories/dev/group_vars/datastore.yml b/infra/ansible/inventories/dev/group_vars/datastore.yml index c9a93d12..1e477c59 100644 --- a/infra/ansible/inventories/dev/group_vars/datastore.yml +++ b/infra/ansible/inventories/dev/group_vars/datastore.yml @@ -3,6 +3,7 @@ postgres_db_user: "{{ vault_postgres_db_user }}" postgres_db_passwd: "{{ vault_postgres_db_passwd }}" postgres_db_names: - keycloak + - openfga - lakekeeper - superset_farm diff --git a/infra/ansible/inventories/dev/group_vars/lakekeeper.yml b/infra/ansible/inventories/dev/group_vars/lakekeeper.yml index 90aaebf7..e8739b3e 100644 --- a/infra/ansible/inventories/dev/group_vars/lakekeeper.yml +++ b/infra/ansible/inventories/dev/group_vars/lakekeeper.yml @@ -1,4 +1,10 @@ --- +openfga_database_host: "{{ groups['datastore'][0] }}" +openfga_database_port: 5432 +openfga_database_name: openfga +openfga_database_user: "{{ vault_postgres_db_user }}" +openfga_database_passwd: "{{ vault_postgres_db_passwd }}" + lakekeeper_metadb_host: "{{ groups['datastore'][0] }}" lakekeeper_metadb_name: lakekeeper lakekeeper_metadb_user: "{{ vault_postgres_db_user }}" diff --git a/infra/ansible/inventories/qa/group_vars/all/vault.yml b/infra/ansible/inventories/qa/group_vars/all/vault.yml index 8a15ae1e..93a17afc 100644 --- a/infra/ansible/inventories/qa/group_vars/all/vault.yml +++ b/infra/ansible/inventories/qa/group_vars/all/vault.yml @@ -1,718 +1,725 @@ $ANSIBLE_VAULT;1.1;AES256 -39356562306463383636633232663133363663623131303830336332616338303064613032383333 -3531303030626563643136346336353533393165386637390a306130383636323166316364623538 -36616561356564333266353132306562323564303233356562383162363961393162336633633936 -3761393736353666620a326437646563306262383830393033353964343364303664383663393139 -37393665646366666563363663366332646561303530366464373738626330353530383338663634 -65313866303838623662353831343038663439353439333863316437666564643662653132393665 -32626431616631613838306237383966636661646130353932626466336435373265663862613836 -39343135333532653165616531653933316161373563356232643461326664616465623739643836 -30653165643438303239393135616364343934323334326263666466343062646536333331363639 -39353663333038333838633963653035303766626565316631613637633933356530383938393736 -32613937653136623464663539356437313033376636303135393762383437636339353164656665 -36363461653532316438306338346336303266383062393863666330663937326361613234303131 -31303530623266643464306239656565333864663962353432386265353534393862343834343962 -62623931656363363962313263353632313030333465373438616163356437346532356231626361 -63373161353635356231366433393439663239363137313633616638666235643862353933306566 -66636631313433633939626161386239386165653364643265313835643961386532336431313961 -31393134346132393138383136633631666666383461303933316532613134636633303335313865 -31316533303936313665373566363437386331356466666632623338303464666532633131393563 -39643136343636353232356364663233636563363563366165393038356136303831633335313035 -36343836356636323535373261666534623966623439393261663762653361373836656566663539 -31363433646364316435393739313136643331316464383531386331666361313134336634336232 -34666362613166393064656631326133666161383239623037386639623231616434663730346463 -64353466636339643936333663643831336337313633306166316164373537653564383530393735 -39623136383433656166306539666163373862396335333035626535366464626635646335636633 -34336237303439363935643133306466333338396438636334323530323334613864313765373061 -34393364646334663865396161303432303138313962376438313962376133393931393232363862 -37636138356635386133666363336136383863633437383030633166383136343231383063656664 -63396432303236366463663838303939373537646435613031366530306565333335346634626339 -37623334666631313138343666646564393839323538333734323964306261303263303865633765 -63346633656336383861393236373031666661663835303439333932636262623833343538653766 -39356238626137623731643630623866313435383666363662653531613337363464646461356232 -34313936663137376365343664366666353936303735666366636330633434643030613761353730 -32616532343031613531643335333130313138313663303537326366343631343831343131663634 -38373330366162316636396438333933303962653562623766366233633039643239316634306336 -35633964616661633764343938363838663133306636313431363635643732343532363234326334 -63356361356131653964356639323035303339393530636639623233333263386333636561343865 -62643466366561313939346537616438303562353462333366326239333732343865353366663464 -34393431656638626437343365316232643164386432393038303538313935353937626464383838 -39643532353065396265663636393232323365613535306464356330356463633436383037333139 -65626563333661623031323335396130373436383837303661653636633330323038613766616431 -31653232653462313132623533376466663537363038303034616332373066663562303535383631 -63316566346566333538386363373464333163653166616232393766656534353763633366366234 -34366363366334346162653866616363393837306531616162383536386135356132336530633664 -38396161316565373738303632613162356332643334346137343866356431383339316235333234 -36653533376130626662343263646631383865663866636630303731386230633962343632373138 -35623466353739623530613962616339613361396537326339663733353162656363383365383235 -61356338336332633533663062316534623663356433663136333832646537643661623337386662 -64353964363436613631646562366633333064633064353730333862383834386133613365393931 -34636265316435333734383762663838323238303165333239376565313763353932366437343236 -66616438623930396338646432303931316465613961393363636235396663346431316466393966 -66376261333635656266623462336239663937343662393366353136313932623764613863353033 -33643364346131323132663338313566666464343733303331616132653238633039303932323234 -65313432303132376130316131303833386166343330323237666361653064653361336237303862 -61656638373934623865666336363862316563666331626436323766396435666634613739303735 -34323338316133323738313035633764396232653439343265373364646162646161613637383235 -65346364393337613461346435363733346330336161343432633561663631663531373733346435 -64326465616461376337613233656330613934393139303665353231613533396436393138663038 -30666533313738653634613765333331633932313234383064316638633361303933363339343862 -30646332633030663561633034663163643931623463633231393064663133336631646237396337 -32353261623934306437396562316664386238643135346239656333646262363433356339383738 -38663433376365633333636664356266323135333965356166633336373838383639346261343538 -66386664613235326261353837396264636462353034393930346639643336626536316431353035 -39316162653838353836333934313632343737653366393730613162383563323531393137343238 -39643036663539366265353732633930343537633135633265623236643062373238633233383930 -62383136396335363732663238326566366630343032613535653437643236396635373334356564 -62386466343661643839333561363466396362663438633238393033363632393732626437323537 -66643837393766343230653339336430373438613939393132313030323837313764653234306166 -30306663653533303766323835383333626331353061366333653831626262323363343935363166 -36616561306138353734626362376437393835623934376337323033326666646239326133323433 -66633532333136373366353663373432373464323966636162386364656531633034626434373339 -65646130663839643030373563626366323262316532623136383133303335376230366161626163 -66326438663030353630356339396433366137653964656336623937303739663534393266383436 -63363339613666313933643935346162643231653933353030316439346133633733356632333136 -33363564666164316365363136313763336566306232383362306332373234623539313861646131 -64643439393436633937306435663037363366386332393434626564306266303861313738613938 -66313433316565393666393262393435653565333532386631323230613330623037363230373332 -38386239333662333239376330383139313433303934623033343463643465353031623530613262 -36623365623735393034386363343366663631366439643263346637366638323762323163393430 -61653134656538636434623936393936333233313931396463323532636231666162323766323338 -38633939663738633933613136623266626130623364303566656530386335316163343232353039 -35366537613163326534323663646432613838353166346565346262376236626134383465616631 -32343837363632366131613433323637623465393132323662353764653431326463623234353438 -37363730393062303234383363636639616432363631356636346164363538373838303663356632 -61303730396362313439636338323937333361356663366535303934373730376132393361303234 -30633833353234643665353937373038393434633635316362313636633066333238386662333466 -32623633653236396564383865303532636565386463626265653730656262643264303533643761 -31666339666133393238303131303964306630623338376632343236626435623939343933666431 -31363264383065333730346337313939303266666135383565396665313037353565323337373161 -30373539623861313136326364313131663463626136303064656563646333316337336537353232 -37376162383162633232653262376435313464326631333663303138316234306663323037613636 -34373837653434356364333033643164303262383334636332353037303530353161623034326334 -32393338643031666337383530316563633966666132326137383437333861623836343865646536 -61386331323539346666396136616438623235633531323162623830323539323936646335373266 -38623239633765373231613430663338376131616562663866656532323238393865366338353237 -66636566373962316665616137666235623061333430343161323531316462373966613031353432 -64383833316166363662616439393161333832373161363964633730306563373261653336336264 -39656663366463623038646437326638363234623435363836663539363661383433323736613737 -34663739313364323838633333343965393838616337383934396239336164303837333963326335 -32643163623630663731386630663936306637633036303832383064353130623032376562326234 -32383430306439633533636630653133303563323734666163393639623136643363633539353731 -38653866626163313262613866303533343861646534363865626439646562636134663036616563 -36636532366331306637393534303738646264333762626235386137666232366462313262353331 -62326665336562393936396230666435643866313237356537376432393063623866613762366661 -36663537396535326365386433613561646438306230343035383262303462396264316666623162 -36303432323232306431373938306462333564306666383661383630386264383934343836356331 -31643361393937393063646532323530373663306361376237656565353739373963333937613037 -33653131393330333033373063653462366637326131303734636438613162303238356165613563 -31306138303733643265353636646564333361316531643133316466656466666365653664643563 -30663336326138333133383365333065343030653937636466366239346132386462393330633165 -64383034353036313434396262613961386161306134613334396239633636386564613338386363 -35663466663131316237396238336438383431373964653235616335646665633830336630616464 -63663534613361656161633439623330333234623635373037386230363338373633313030613737 -61343738383162323734373036623663623138333435666538633163623863336330653562373431 -66346632333435376566343431626562393563393065366434306638616137373437373834646637 -33306534393332316662636663313261383135636135323262386661303733663736626436616532 -38306137623861653937353935333937636563353437306434326136623165343333636261376231 -38383431313935343939363563636562383937646466623336623830643531303663343633666639 -39656166633038353135616262336261613261303663343434666534633130396166626138653431 -35613335626466393135373464383834316662626337653563316132653866613139613565383961 -62323661356363303535663430666263666665393032366266353731643165623666373639666233 -31633063663235346232333265353033373735393730363337333138663963636436633035633863 -31373032343363316333626131616666333666333764363066653866663936346539663862376661 -35343435313461346434656162626538643763386339383332336133306334393764313864393566 -63323739356163626661666136323663376365623264653831313763653331323365633835656166 -33313535376134356337306166356436336562393664643863386536653935663235616534343565 -38333431613935383766333462663836366164333063636537333164343539333064633433326662 -35643531353361383533363330643764326638323537613037653132386137373439383661663535 -61306134313066623062613938626136633932393335376239353063633436633532633839343932 -63343332326536376639333336343164373066386466363238626337363731373332646337353236 -63626238326531386464663633653366343666623332633165343061666232613561663635356636 -34373632616132653565646131616132626362303739336337313565393035356463323638313935 -39366632373365626333666238383836366265623161326432373864633434626637353861636233 -35663266643334316433653333366434306337633361303937363939346339383132376439663465 -66363431376332346433646662343435653536356438383936666234383163383432303934393662 -65346165303239643735663665623137633636346263643736613865663432656632636464326265 -31383034333766613463306134626365646233366437333365353838303964666564646131393265 -63343666623963626630653631376132663838646434663730386637316564313535636439306336 -30646364303866363264616230653533326333646434666532366165626466363631336231646632 -35353164616662646637613739626439616265623830353562333830356136653062303432653263 -34336464303838633939333338363633653264353336376632653034643438326663633262373138 -61363038396637653166393862343635373335333566393837303135333530613233646664343134 -62313763666364363866346564323065656132306632363431383364623064323631306133636564 -33326433366231313233313362613536363836323835353866393639333230353437303362663633 -39353133366539396239303437643431653834323337623634323962633766376661386331396463 -30396461613431316565653231653432393438313335326637633661343839616262316562353866 -65323064636366353433343264313663363339393130646564646463613033623536383539636563 -33633134653331323064643031616635313163626537626230346166386461623132636238643931 -31313665623930663935646163376638623539396536316465323666633638346666323632363131 -61656535383834656431616432633265333961383764616634373161313831643964303363643937 -39303633376438386136343561363234336539316133333734666231643635326364393761613863 -34333233636235633934376661646137336263353534363865386236643964343365663332663463 -66646134653461386164663539353433343233343331653539356230393732313833646163633766 -66363439383266393638366565373232383437333639336336346139396431343466356338633237 -38653936393362646161323239663535363638326561636263613031383065613361393034646338 -34633234323231303235666464373661636632336232313265623261323437666163616463616636 -39626531656631666532303434366132333264396138613733363931353139363436313062623231 -33373536663366366330616530303764366230633734306539323235623735313063656638333361 -33396331373064633138663038346435636436393138656561316264373562616433346664366665 -35306532363033663539633062643832343532623163373638656235393865383331663131326138 -62643166666161666263396365376237373137626233633666313236303232343666393830393131 -31383664653065653836656134313837656435396632363864333761623334346636616265393737 -32353735623061343438393638323262643431333161353364383938336164623732333732316365 -62613762393832656562626662373366363931633633376665313830386230616263393861383531 -34663561643730643463373863363932613930613662333139633834393333663162393539666539 -38346437373239313039646534666262616437353534646263353237656131306434366437663162 -39613638373937623063343336383064386633356365366638396530633261313264383139336634 -63363361373766396464663263396464633663643337306137343761643766356165626537326530 -39333633346465663734663230323933643432343066323537396362303138643336363866653066 -35316336376566633431363438306530633037663836383831313133616635643837646161376334 -34303763613237666634363934623332356431323432363237656539643734346366383731616435 -31353536656562353732643831366235336133616561353434343864383333633534313432353361 -33363433643233393731376232616338313861623637616635393939376664343365643562633732 -64326636373463616530636664623265316466313237363934306436393536616335366332656236 -36643234383435373766363235313466336363343366383733623866323836316230353234663935 -37393163623863613834626331373231353466343134306237633962626665326264656661343930 -63313162343461303363653039323734656432353333323662663365313232346162303039336461 -35316637383966326436346138386161626533633839646232336363653137386463316139316361 -34333165363037323830643731343539376633366163333063643039303166346336313765323566 -66646561353432356562343139333266643163313934383738333933366131323537373236363639 -66666235653063613365383937346564363761646464643432633165386161393064316437383631 -34356536396539623766383134343536393736636138613631326630353233333432313433326536 -34393365363162313264636533383664623466653230616466656264653331336131313330343932 -34633936623730313039636233333033663364303765316461613265333337633433336332643363 -30623830393134363138663237363061353636333639666634303566346231643732633338356365 -64623161343766386265326536376233343364363665323661393536363136353939656536366233 -66306234666264613865643937303533313731333538613066636662333036373732623863333762 -31343933343336363831393232393263613039663661386338353462333362313730336431343734 -63353465363435346166343861616435303435666633616532303164623939376432376461333836 -32663538396634373334613936636230336134633664653237383466633937316262356565636633 -63363231663864656634313336666231633736633338303738343832383036326338643337313166 -35643662663538353562653230363733373061333332396337663631343638333265636538636161 -39376164316561313537336436316362656538303838373939633062393931633366383533303164 -39613436343462616362633335366533313062356432326464376162323535623735353465373935 -37313764323735313838393731626265656533653033363761326630356663316631653264643130 -34643930646634626265663733393861333363336666326433373336646565663533356432626566 -62323637376261393339343761653436356632353561323139336637386464656331393335323635 -31366330636334393231346238366564646233656639346433393765663362613865346634663830 -64643835393566633137353061323333313362363263323132663639303933383162323637303262 -34366232653139656262386336646531376435393530663964343632613031373466316265353337 -39656564653263386462333534343639333566323361343931376133373362313237633833313833 -33666631343937303863323132386231366433396533333030636631613337643965376131333730 -34373531393133353237343662373932633634356562383365386238373533663239353833653463 -30343866316562353433393331383432353364333634666563653761626530663434616462373361 -30393433663937646533383738383335353532353162353561343163386238353532356339323332 -64306563316237383335323530356439663932646134343661633339356265393262363730353436 -38353234383439313230616537326634353564396463343465393766346437643834636264383462 -32333366313361626633366530353761333537353339396434363438383034666630373361386163 -63363764386461333036333563666339376134313638333864323061323637626265646363386636 -61386330613661636434356664636164623633396137353733333162353366616662396435363736 -65623461626237326635633534343361346633663530393762316133383062323433303165386631 -36626634663161313764636231363435653231646634623038656166636136333965343164653764 -63383465333731643062393330643963376434386332393166373266633833663866663236633163 -62613239663331343536336633396336626338373638613464363331343762353938613438633738 -31633362336631343033653662323666323965373164343763666465383439383162326634376130 -64333730383139616661666665373562666535303664653264383965316465633531313339646634 -32643262653530633634666166333930636364376438306533356665346431656336623238663662 -62636262396165323137353932353737346439613437613166623938643863613937653563316562 -32316336343564356534313431373366303633353163346238323738346337326630366263643338 -34326430666332306265386632666230343935653531666665663432313738346661643436636638 -34646434633035363932343764303135346536383636323566333163613133303934333237626339 -66383939343162623239393930346233626466663464383734363734626434323765313036353836 -30366466613863393663643833663332326164613536616635316131383531386462313931643266 -65326263666130616437373237323833333934623666346531656131633565383266653931316630 -39313033333334623035636163663531613837393236366336646536383939633632353838313333 -35383161353235393531323961356163666630373837663330336361643562343562386237306665 -36623230313063636661313062633431383130323464616466616264316236366333333662366131 -30373739653066326338353438366535316337646365613364613334666465376462373063653966 -63346534393131323337373932343233666161356435326433656638656663313038633561346437 -65306565316462353637356233636339613131633734666464663266643033356538653935306539 -36636230643633626662363436653231323266303663373861643930363261353236363534313764 -36613536656561373065376136383133343635633538373637643034376238393833653161353961 -62646564616436323939343261643538313764356565613133353464636330336533643331396536 -33353132363239623830613962623434333364373233646333333033393738306337653061373337 -33343937333863323937653163333337346630313934623533333538373331376631313437343034 -36306666653665383630653137653835653535616530616166616466383763336162353734316234 -34333630396639373636376131346435616666343238393037306632663435646666323164326633 -34303661343033373365646231343937343232336238663763666562396363323563623736636435 -38306461636238656164376437356237643165326266373366663563623234663961656330613534 -32613235363535356639386163303635306535373433666666353962306632373534643234613663 -32386630313663636132383235633336303134633431313839656336303734386163353838353939 -62353031613631663338636237366135656130306162656634613833623263633034383730393833 -66643937333230356635343731633833323030373832323731643635346138376261626564623966 -31356133376165333865303737363636613863363662373066643363343537386235623035666435 -38333539326164623165383030653162383336663463366530383332326366656166643833656438 -34633430643163366439306464313036653234373265386533303238353466653564313039363535 -31643831636366656137613136613138303861393334663939303665623034303437666534306337 -63303963393961666531396434643739373934653430636539313261333131303539643962643662 -39396566643539633139323363393534353133613865356436396433653639363565396635346164 -62653537373335316638336138313830353464366532373763396632326463383830323138623834 -32626232386638386333626566633534613935336638343464626563396335373965393738383363 -34326532396265346337643561663163363234626563666634326133326562343234646439646661 -35333533316532326230313236373137666137346233633935623130663833623037663034306530 -31343334646261343333363134316333303435613034646661313866303930653435646238616430 -62643537386431323266376365363834316631386233356135353136303037303434656137636632 -61646164336231313464666438633933323635643636313332336437623032633064386130303332 -33653135616664316133623430346139636637393061343266666532393964643563373034366666 -38663366656336376638333261363037663135623438393464396331663130643236303732323763 -30333839346431303263346239613733313632626436383434336563383038366461653866616637 -32316366643062633865653738323032636165353166643639306430393965333332303236396335 -32333935336235313961343662376532616430626164346363316530663065393032633030386563 -63313564353263663066323539653463373761633662366332353262393433393437613563346164 -33646232623566376534316238653031643962613866383764306333393536616534336333636437 -34636466363132323764623830653330346264313938386231383937303432363232393563313862 -34326339343333656262333333343030636339653137623863333963396165396437303637376239 -32373633336535346332646661346631383437366237373030353964616238663961323235363865 -39636139313238653735363865396138363335623530386535333735313433633034373862623935 -36616566633566653563643261383463623061396133353363303033333731306662383862636638 -36663336383334616662653937313934356333333365306364613335373234656364373438666532 -34613936306534386535356232666335626366313034666233363530613465653830383165366530 -62333830653565366162353431666662643163373063333236666238363833656630623031326265 -36363830646266353562303532373935646539613433366137356131656363396633623261626134 -63653930393934316534623464616466313139626436356438633931373732666538303138626434 -37323463666364613337376663303466366561643535396662663063343436646136356662663431 -37363733333234366331386561363737343662373261633766613162366338343639313262623030 -62613431636335386264663138623662643865623432343137616439323335396534623165373062 -34333336636362643063613136333532653662633863633635333037653231323332396230366132 -65663639666331633933613837323264376630613730646636626435346265373761326433343838 -38643364623236326461363235336662383236313163653736313161353961313734303339366535 -30626437346631646238396561313738623938313263393964393838663961343963633034326138 -36336361643733663234353762313331346333613731636462316232376464626139653862633539 -35373061363361633831363162306636303566336465656464386133623966626137373161633030 -64663361306537663431623533343739353865383335316462636565626462333035363862353066 -39623534653166366133613563343933326237663563303938393336386436326661663838313766 -34616237633230373832383133326235633066383366663164393932336332393066333561333133 -63326165666630316439336666393866646666303231623763623064303265373163386138656266 -38333665303664363862373337623737663937356235393765636230636235363465666131316461 -61303133393534333932363038396138323035633166316637613936623664646261393730663435 -30623333613161343334393237623132633433616132636565383339303961303962626264663135 -62353231373463663465323232643062356365643030656430663131323539333331326238663564 -34323237646637653338393762336165343661323936396234376430346363353632366664343761 -65616337396262643035643562346639383562386538363231346663353061323333313464313138 -61366630623163303566383562646231623661336130663939623435653131663233363139663561 -35383531663666346533326465633066393062613331653434376138633261613232613133643363 -62623064363635636330313732613664363635386235363333386131333866633365313638303834 -37333638356165633363653137343533636564623238366332666139333031316438656334663335 -65333430363632643636373862313861303763383463383461643865326535376339643936653136 -61633733323938623665623062353933323131343034363765393633623335313364653965656465 -38636165623336393033336336396164663032613365383232653537653863363538656164383630 -36313037346331383766316535383930333738396261343231323535613962333630376361306161 -35343730383438643430336433303064666132316235396262633430646462613438306462666537 -33363664613165373264363136393538303736343930376364623532343665383761616536643731 -35343337666138333461393838343539383962366563323235333139646463333231393263373833 -37303466636335646630383563326365663261386433323332656436376361653665333061616438 -33323131306235316264666562633139646465656135343036373364383036353337356664373935 -39353666663461626334376532343433623062653161646430363330343835303232613032386133 -32306661653161396238653364613938396463616530343337356431353563376132396539666362 -35623864353531376636616637363330623362353634346465346663346461323832633165343239 -64313362643635643837316431353961346165653934643065643738646436343061653837336637 -66316535623832346336636531353233323831633237663764386636356161376266616464343038 -32336533373366663931626436383836313561313365636336303962653336653565373463366434 -32323564343163373337643965333564383432653063656234313237636264653261383963376631 -37363631613738316631636666303038643331303931343530663565313733623530323235663335 -61626664653331653661626464316533643566333837303836626235343133343661346238343130 -30623266356332623739393030646564653434303239333939366432383037623965646333643963 -62343135636465663065373363656238643631303137663965386531306136636535653365313535 -39303965303135616336643035376433386330373736646663353539633235383434636130343065 -61393733623333363362396365646539336435323063383734303466363837313230336233333662 -37383564353766363261656434323566663537363335393265353464336264373764306135643332 -36653333396435376463316137396564656265376239313532623936613839333861366534623865 -32653961366561383037663131336564333534343037656264633864376338636331333239643762 -37373565303635626138346233666465376632386333623435633632313163646638663461353066 -30343332653261633765383463366137303736363565363135316231346364373266396533303830 -30383162656136626431313434306536313439653162306530336334363164303962333166616530 -66343537386432626465343630626634396338663262333330333432383063613333376433663662 -35643732656638643438346438623332613564373630643161393261643961383538643461653932 -35616164373166376266306565393332653963373339313036366466333235313930643830326135 -31653063643431343439383361363761336263616562623862643333623662386535393030383864 -61633163653963303462616430633737313539323736623864656437333761623733393439633135 -63653664373836396366626137363937333666333464656339636434633032396637303739643932 -36613336346464366237633661393630343365656237333033376333333534643361383630303066 -32393563396165363361643262366462613938346231363438366163346239356332393066623831 -65343334376239303264633365663565643863353537356164393333346536366562636164646166 -39343733366235343935643266386238623038326561613838656438316237663535656637373634 -30643466646232313636653136336438356135323163636161356234326564306239633263626436 -65643365653266653862646136653464343731633638636337663562643939313732636639646232 -32393563343364636131323432333463643237653337613961303135306238386564316335353362 -34323535313436393161303639366165346339616234396163303166393933383731373764343266 -35383335366263653862333462363137626161313266616664313531623037613965656637343262 -37653361646239323137373334633338356230303632316435363631313639396239656565633637 -32303434393961366466303135333765356364326232663232363936343034393663626462336664 -37376237663932323962343263626462346237623564346233666330376234303334363133623435 -64656533376162373239353766613136393662396165363763616463393035346363336361646336 -66616662633539356662396336316331636163653061633136326436376666626461646631363861 -39353537326334396162386439643865633534653163616231336664343062653034323638393161 -64303165376536376662336237313435356436653531346431346336383731626334626665323130 -62653431663839393236613066326562353566353561643838386662303634653239356164666433 -37623563653465363030666362646632643965613764663663383061333336336261356164333666 -63386430303763393265663433333763366266316135646331623961613431663032346662613366 -62653531646563646338313761656436303865613339373735316536623538616433366133616361 -62623239343763376530353234356139373739376434333864636238306535656361303162653438 -63376162373734636263306666613166383035366663636432366338323763373636333535613530 -36373631323961643138613232353233323838393364353035373030666231386239316262393935 -34356663656266316666396230663761633762616461363138356165653363613436663935636138 -30333439666630636565303335393334656533353662353430316334376365623533353166656163 -31623039623731376165623865313534383237663137353739313735643936376265336531623239 -65366639313265653433393964363832376261616538383838366664373130393331633335633633 -33333737626431336362323632336563616461353434383638316461333537376431373864336136 -33663963316435646636306630323831666437316161333934656633646632663131656532333263 -30663162353462306230323431633532643264343361386636363435356264633634346235333236 -33306338393230633263643932623830613139366531623864643639393863333064653037393631 -61656164373864313366363530663831393862643834303965333434626666363432343833643966 -61656139393433653365653231653763353934343637313261613736363565323665666335373634 -36613962623236623236636661333539323338626334653532383333646239666662336665313766 -38366461666634653734363034396163643632643436643031303764616537653937373466393965 -34316430313665383265326439303666383339316231303933303266393635333030633730623432 -61323962366137353231363634346138373032623137383864646530366133363435333163326662 -34353632636332613432396165623530366363623838613536626664616435313231636663336633 -65666338353732643830663034663236333635396337613263633466613136396638626338363631 -62303466343238633964366364343861303038393661336164323631613664643463333566653531 -66636137336535373434393935636333353030303966373562343739333835326162323163386366 -32343839353732636464326239323839646632366435323237626334613931366266636562313436 -35633163643131613136646235613262623065376134396562356561396162313437323031393861 -61646263316330373832636439623766663736653964373034333839366330316338633837393234 -64303038626363383130653032643530323838636230616534386262626537303039323466613564 -38366139323731636231666661373335323664653334323538636563396466393338346535323162 -33353435356264306538326361393534346332333032316566326334343238666435623030623431 -35333734616566323962303965646432613761333534303330363239306232356331656362626130 -33643366633739373737626236336662343631343465306338396532333965383865356565323835 -35613838613766346438313964393432303263613937313138633036636333383433356234366432 -62663932346139613133636335356331316562303733376334623330343438633936366235343930 -61616231313434383061353137363864656663653966376432343865386435646239356464633739 -61366236666330333563393365313534633465333630633364613736393530643133343831346331 -38366434656539326565393835333964343639323930623535353334353032316536303134333865 -32396536623831616166333232346464386330336638616164643739303231666232393263666663 -35643236393834353738646130353863303838383364306161303039343030333365393439383939 -32633035613532623262393530373038633832613665313165663932306562613938333437303164 -37623233386636303064653034363437303934623435396332643933653036623033666465623166 -34323136383038323234343033383537636361643234656363623439336337643736666565353932 -61633934633539353362373330643663323662666263323066623564663938343036363066663964 -66343562336135323038333363366236386666633030663139663566336632633861326565616663 -62363161353234653864646437613533373162353638396137323639666133356538333730303533 -64376563336266393035353662323566336239356134313538313730336635613832643930363337 -64613636363838376166386662626136616135323963306437343533323838383038343136396339 -32373466653032303066626463323137353939653236613739303035363738306637666535643438 -33373763373133313131646363383161373838663465373631653662393035336461653336653938 -61616239366161303062376262383661353331663763306635313865653838383236633532653464 -32663233656239356163656165326136373036383838386565313263633265373065396339623961 -65386233306333653637383138313331316665643139663666326333633138633331663131356331 -36333836393665303763386163363563346464613066313730363332363161313231306334333330 -32323137313639323165376464306432616330646332353161663164336661373261646139613330 -62643232663238343961666339386632326131363039633963636638323331653833323262626136 -36366431383431346538346264353731373765303664373766633036333733386363376361396531 -37666664343132666564396438343932663033393532626134306230343030326238613936363230 -33643664643066316534316566653732346566666133353730633738373539653466633664363439 -31636630323961356266373530616432353836393661346437633964343763663239303231346337 -64383331656431353730353630346665383061313462646365373463626464306236336263363862 -65613533633634643137653934666666373833633465323562306536313238336237653539393932 -33386636646235353132643933623163323232356431343962306361343432636433653836636663 -33336434643431306535306139303333656533646437613031396564383036383263376239643532 -64373335373565333935323262633662613064643361636462623831616263666561383661616164 -62613061343431373539343633636632326338633131643638623631313462303364613437353830 -64656430666530373738386663343335303361303333636661663334656330336462663131346263 -35333435393639356230313764333437663135633834336534626665306565366266646437613336 -37356461636532333162376337323166663430636634353430376537326663633636323761353865 -65363134326631303862666264363434326435396539613766333639663733383933636630623563 -65663032666437386338613532643064346262353530346531613936323932626636656661393931 -31616539336166656238393162353065383463343734373732613863353563643731356532356631 -35373034383166653637376131396335633434336230396130343165633137393537643861303437 -64623866313435363864393736666331613339373330323437383836656234306431623566353136 -37663439303331353131313635303366633232373135336161343064393237623836613566353630 -37633339653235303231663034343966316365653230666562393464356630366365306236373235 -38323638366635333030656337623837643861383734376339343864643434613335366136633861 -37646237346339656539666631373466623632653764313637386238346236633865623938336166 -61666438333638636466666232366435363732323866643364633732373331306661393431336663 -36623839666135363230346336613065306365663866623132356165633431373539306534646531 -38373061383462646630323536653839383537613363393839376435386131653038376666656563 -33663133643638393265653435353436366364386632303162356630616465613333306565663661 -62343664356631613066396130366665353963316332343736633332376665373932313466356437 -39333439383036393861373366663334626131313230326134333264633737333666326330313936 -33376238613565313136393966316663316361303033383838313534663534313132336538613163 -65636339616234626431326364623231656231343031333936376364386638623233303836356130 -31383463613864613634336536643539363962626435363736393534336633313431306338316330 -37626235636564306632376634303638636231323830353134363031386462396335363762353166 -33623234626531343630356666306338393863633665663537646539316334626263626665306565 -32363431643261386561346533353834366535653139346139373733396538306264663264613666 -33386164613866373632643332366338393134306363303762363834306666653966616331623032 -65306633333564363766343165653764356633313834616633306134373933336162623935393732 -31303661373434653638363162373730323765353666643132653732323831323633356337646136 -34306537666261646238393436353839643534616536373537646564646262333764383566653230 -64613362346237393336613133343831306138393638353537303936363062343433306361653934 -65373363363634306166616338343337303533323262376533316664306133383663633030636238 -62656230626234666466663963353039626163346366613166633836613766313766383030383634 -30356533313536646631313531633436396331393463336461623037303439336162666264333931 -31343336306233346565333763623330393538373563343131343934643033626564343030633837 -37656432643832343662343836346631333766303665326165643937396430313661633261346535 -33623639633032643930363561613531383964336339356463366631343964636365393564666265 -31356232633666393839393766663535383062656338633731353035383566396532323335386661 -61373861333636393432633237376533633865623139663135666565613734653064616436333864 -37306631633934306266336562326164623733653834616434393939333266336666613133396130 -35323835646232666536396130303135653636326332336330363365336139656363643633326132 -35326139393332323366386163633137366634346262633531383732376433343132653739626664 -37663231333362643135353435313830333937616331303032323231303766323166633262303932 -32393736353131666332633139636664663837336639633262393232393936353838653862373764 -66613039613835323736373133356535396561393461656361346434626566643936653133666661 -61653437336537643034646131613437646163356461623963626531303433383762303534303964 -39316262353135353630306539376139366364306535343534326262643163323332616664646666 -33346136333664656233643031373833303836306162666161656134383636323063303363393961 -33316234653933323434343362616536323835303630666230323265363634623963356661646434 -63356633653730353933353234613034626464616235303135363962303663653138616434316237 -31333565613938336532663235646532663039373363323964313962373663353239303938363061 -38316133633239316137343237623939633463376438323236323535303532653762303261633337 -63323431393364333635626265323966326435653733323031353538356564396237633834306338 -39366564646332666263653364623132623034643862363439306665646337313262663236616133 -62323162613537313463653866626631376432636661323337396236306564393034323465643839 -38353766396564653335646365653362303463326531646465666262643866356433326262343461 -39383635633834313561363366376230346666656465383462313762666632646536373338613732 -31646532323337336530356164616264363464363733663536623861666665336363313639663334 -39363734643035393764666330393861643663633564643735343661336233383237396162646365 -61656539313961623539613961663263323062666532643062383930353439313336366532363935 -33363137383661373436313736316465623834613832623032303036633731303138336138383164 -66393965353136626461373538356465326264653732633830636261626162626434303264313532 -31623739383831396462646238316137366563386466326566346562343634333739363031376632 -65346662353039323264313535316235663839326334643535646430396666333238623433373335 -30323463326562313761633631383639636434653939613266613238373532326638353433626138 -33633234623431316638326661303031393734316166363339356430303330326162633036353664 -64383463393532373237343636313536393965396164313962383434306465306466306136303864 -32336633653933633633326539326365323161333064336231643838343030343561396231353461 -64333534656234343765356436656163323665353331666563313063623534323037326562633730 -39383538663666373665353234393038336165376233643832666536393332343139376330663364 -34343832656235636230666135386431613337393635373066393031623761303365363461613335 -63633532303565386166393238336334663039653035373364623938376265613834323635343061 -61636533313862613334623833613834356662303862313232346338353637373330356534663161 -31383035303962346638646230616166313239613866636234346537303535343934613336343961 -64653964396562373132396662646430396165346536316136623733666631343132636233663530 -35333762656537623032363965616539626164343339313832323266356330626230623533313338 -66316161386332666433656135613634326261333966613239366637646131653565373162353664 -35373735373431323339613163313635343663666665396262353535616230336437653566333938 -34376534623631373630396664623134393161376664303332636635613435656331333566613530 -30626566376365663835333066343933376237363833653939353336653434316431303434623736 -63383932636132663636353763643938656662386237646530356164343131616465623538633763 -36333466623562663261353966623033623932636338313965343930623463666139393334666535 -30666630393965653232613838663263383062313366626135623333666132396666363766326666 -33623739313463656632363234376166313333393964623365653935353631636533643136326563 -61623339653935376434386432386437663061333262393364353530373630666531366431393536 -38653733616463353032353962343135373232616635333966613030613932333865666334656539 -31333333623431366432633334616630613462326638623532396336303236646438343761353237 -63326431663733653834656364626163313336386539386330653530646634336339313463656662 -31363031306535633234366535373431306333366163316665646662663265386361366536613538 -36383633643931633038346632663761333235323132663563326230653262373465376463633537 -64643863323035313865666537326432343435663536666234623462363935396266346235303536 -65653136643266356364396162616264303861653763333461643839646466316663636534633332 -34393437373334613636646230336634353636666263363636303833663835666134393038326261 -30613032363432353333313731353863613332643563363830333637646437363434666665396431 -61333231383063663266396436353331353332333136646163336237306566333164663439333834 -65656237303132643639303661303664376464656639333164316338383531333231333434363862 -37613337653362633931636162633637316232333635323839636165396466373432386332316361 -65363332616161613335363862376266626166393233386230326566373866393335313735633262 -33393738633137666635363861316639336161636165363161313432653037633762626666336236 -37633766336533336363383534626365333936316330613763643966636436356561643438663435 -63313938636439666163366333636432613539326233653630626535363666333239666265323337 -36366633373266333162306432343034326331316165313033303639333861646466323364633165 -32346565343436623631636132366166613235343739303761386631613465393938336432356233 -63313164373037636464396138663636363237323331623463316638633335656334323936663339 -38373335353462306537343236646135363464633031616334383164313931323466373865303433 -65636533613533656337633861363531326165306137633963666230353739633033373039386530 -32326561313166623736326432656462343762323134343964343533323038333038306634663237 -65373038316636363434303633626434376230353130336466303735353863643162346662663839 -66613532636234336361323037623231386566323136396562316661333537633362393062613962 -66653938656536643061636363393737663166623865313339303339373064643832346163353665 -62343465343335393931303161373032666266636335653832333831633265383635653764663662 -38393364626332303762653964363337366134613530633730353032663234323864643432353432 -35653932323332316264613230353637353435663663613862616563353133386363356463373933 -65656261313837363339656331363231376630316162326130376536373262643864363164376364 -39386130366531333161333266313237376538396334386337386332386463386531393930323065 -39366138346662663831326234363237656165393163303162396236653035323436346565353938 -64383162356531626535663065623763636265353236386464613337363937636637343263366561 -35313038313166623762653164663565643862623530623732306130626437623931376364666431 -63363130663962323834313730396261623138363837353837346337333064666332363639356166 -63326439376133316439316635346165663933383739313633356334623035396663336263613337 -39303233346335616233633235363531653364336336666164656133623063346336366465323461 -61326230333934363461323265666430613833663238623735646432626563356630633132663037 -33383230363135613439303862316264616636353630646163333030613837376235636433373030 -36366665313364363165633562616437373931366366633936636561616237626662653731636639 -36653738383333636562313131336335353632653633653436343731346532306537643364356438 -62656332373062356339363032643730303936663432663537323732356565393865663961333134 -65646233666538326635373563393838663462336437366364303765303764336535356534313333 -62643961643266613766363336383933333537323666346638323065306338333433643339393833 -66323035313930396231643632383636326233356137653134633864326366343666613838646662 -32343961366661623232633930346235353033303464316561646264653737336330353737353837 -61306639643433396264616132326230656132306530666637396432346465343762393461383161 -34373466386661333962363866376335303331376561333037313262633638656362343861303630 -37393864323534616339613639353630333363656438303437656664366266363037643361316530 -39653336383062333061396565323965353531396532323064396234316336363338353237356565 -36636136336632336664396234353933663932356363393234633735653935313763306634616433 -61393436343035626263356438396338623131336564633431383739376133613933636134363839 -63343736653434633330613161353462636238643934396438633737366461356133333232653534 -35653765326663373133646136643234353330366636613638613066323438393561643738653336 -39623561336566313863333438366438396230653830376230343465356664373862303433633665 -35343935666631393637633961356461666432393866643336323239323262356630636438363535 -39316662376165323437613365666566326339616561613162303839633864363163306136396333 -34393066383166316530633934383039356437626336643364343239383236376661653838366632 -37613933356464383031376662383435633836643665356566363364396165656332656134393662 -37303339656633333538303736393165663131393030326332636339313235623132313461343939 -63303265613335656633303536346166643235623039666562616135653834343237393833326361 -31646631393336333764316434356665363831366538363063303537636437636336376335663032 -32363938333763356662363433356465646365376562643737663538656565393236363032663461 -34366464353334323731323162666463626639643964666535366439653534343332656630316263 -62333663336439326533396233303035343637303439386634303931396532633335313863356337 -61346633313966623031303530613032326462326634653331323561303237663061663735623466 -62383835303163323762313231643865373430316338316230666232323437326565343334633933 -36323331633964376139326135336366636131646438363061376635613038316138343164376163 -39646335366466323333396530396363333330363462396238653735353230646134353330393938 -31316133313434323931316661366239646239653463393762303532383338623431363661353132 -38636335383331363037616139383935313835616461333065376133643965393733393539393365 -62373862663439633133653636616534323437373563666163666539616263393635353437613434 -37393137323662336163616130326634613433623964373061373265313166353461306365373363 -66376238343934623837313761613164653735373038323462353335643833333130396132613335 -37366366653261353161356637353036653562336161356333383761636534373432633065626561 -63656238663165333334373233363630333039393661666337313135616164396261346437343435 -34653463333331656434326535363561306461393634616461633130303264366361666233643130 -32386633303839313330653961646537663934316631626666303964353035353632613239353863 -37306537376463353937353164396562396437353237306231306531636332626630383939643262 -34303634343965336664633936643933333265653232643136313637616362306636393131336432 -33303438336531323861636433643136343061393632616165393465316165356665633438336338 -38363537353432363361396332646564323861323334616636323331323031306233623031333832 -65333338356135633866313031656431616637633133383061633462333138366665623364313165 -38373536323062313134353432636662316234346232366639393332663432373265666264623138 -32373762353437326338636235336237663230663534376162306363343632383139663266386232 -62626338386366666265303038373664393630306265303838313561376637303964623030316539 -37633464663939626437663633653531623334373631633163646239666235346132396239376238 -39346165393934353462353937646664313137336439306531336537323536626438333531386631 -62373866633664363561343636653538636466363265353966643138366238303335376439613063 -32333536653861383161316466383762376239393835353063313636626563643361363936653631 -39613131386334326439356538383337346462303964316462383535386635306462353731343237 -63623934346534656566313162336435336134663130663238396361623865383365363239623338 -33383534316165306161646563326337643062663434306333376133633138353636313634343730 -66346637633265616537373366326166333265336464613638373634386134626235303937666634 -33373461373262656162653639376633346265306631653638623730363764623137633833336631 -30656466626538376165633039636465376137653861396138616336653736373362383931323934 -31363738383361333432343738643735313065633430326661623434326462366631363164333930 -39306534356435376262616335633032383361303864323933636266376632386434653533333261 -61643632316533386266623731656334376631343165353032366237646630383033396339663666 -64616337326135643763613736393237383935363931303239393538393530313435333734393031 -64366639393239376166646133303932653566323331313765373730393834333933666133396137 -34616266626336313362663464316439353062376663363130643466613837656236346366386430 -65303564653961323235353364313963316230383135316331386532303332373933333264303265 -33616562626638333430316535346661393632306130643366656332386466663834353830383964 -32373561633265633332353464383564643636396531623066613631653130343830346432376461 -37336637613434626239633538396336666365616437623363363662373831366139626134616530 -33613665306230323535646365306164656436326466643337663431656261376366616434373565 -31383063333166323165316131313831623137653663343637306464663335666634623439356139 -64396439633233613039373239343034356430646330633663616635306332663030383230386334 -63376561613265353264346463636634333764356435653930363236663537373939613533376135 -39383030386164336234396434646636663837303266323631643966383562633832316632363363 -34316561653564616434396531336364376564386538303135653237303735653264353062613337 -35353665316633336165353139303138626261343237363734303662303037363033356531366433 -62616435326664633734326463383464383061383266633432386633326337356431326634366366 -66653465313532323761323935316630346438316561643133363639313565653639623366363630 -31333636633030363663316632646338626230616334383537313431313834336536393031623337 -32343964376564343234396237623433323436346239326133373162633661336238306438313161 -38386661303663393633656164613931333666636166643034646138353539333536326565323035 -32356331313931643635323466633365353332306433623530383031666565373831393136303334 -34346665316666326330323335653336376266343337343035663437626438663138383637313938 -64353265393030303433626538323933343661643666356362376662373765316635303164306132 -34303836626639346138646138383931323330623032326163396530376433313738353364346164 -33616332333830646166643931623831386165313463663130343039373831326162316130623666 -33613264316363626563623266386638653065643464313739626664366533303666616664353735 -34333566616262313730333935643637323634663038643937303264366137383231613531313833 -36666362656530623635346361326131343630653730626464393531306339343334653631323664 -65353464623234663363336432343863366237326336313861396466333030613131346462613633 -35383035336336623664366262363765666564386135396434383563333636616566653164353966 -64633537393364313939623933313765323433373936323137376634626261643331643861373161 -36363562333063383638616563656336306631343463333965306461323034643931306432343264 -32303062373063323761653136653530323732393463626339393034303766636161336666616634 -61656138316164366466316333643835326132313864663732316437663865303962656137336261 -39623334666363316237313232316433623566356564636135353736633736656461633934306538 -66643138323138356231336631356538626364373765353263623034353564386563363866663062 -62633138386531623434313464353262393762646365623761663438623833333063616361386166 -64636561343162323966346165313639386436326266353963636437363362343635666161613163 -65366565373264366165353333666264623035326533363935626439626334663835396561356264 -33303066323166393132656335346266386432396663663937346432373536303034623163653962 -62386339316236333365643464356132653734343132323366383131353963626533363462383061 -36636631363234303535326436613631646263626135303363356235616231663863646466646435 -38393039376237613433363461663634393236653232643833363666626663623639643138346661 -38613763626664653066633764356134643961636135373236653639396134333432633038653138 -64653966306231323836353433663964383436373233366233343764346434356532383464373066 -62613661666133383337313461386531313265646136313531343637663231336432663434626338 -38636563333834363364653362623061393032336531326438343830623862396336383662363366 -61633461303031326261323933303834636665303061373033376635343937326465353230656664 -39393362636161353339653635303532383365663737383937363761363765333738343133393535 -32653365393031626562633265363736316238396436323131643365343936356438656438306639 -34316438636239613363376230396662336534393464353264373036376230303762376439623336 -34643761613366653866356535376230336335663461383562623561393232383661393839343833 -30623962613336366336626336323537633861316230323965376130316238303662353465303632 -30356439386265323331366662383833636662353036303633646134333934623236376235653865 -31663138353638663038376363646231636537306261316561326165323937656362653831646539 -61386165623430643462363438613764383139613733353338636236303464313131313063306637 -38623237656136646333643461623563303632333963306539363063666564316261623536373961 -61356532643033303863386262393764653139643933643064376462363765373039613862636631 -34663632373238636632383236663131633033323662626530323166646139353436656632313338 -38333337313764383235626265656237616630633139366633663333613866313030656366313231 -62633165613333383335633236383864313731333631663338626339356465663763313836666165 -39393763643638333038313063303434383665376161383665656236373665333864626338643263 -61643931356430363162643265396666316538333430303933316537626130396262366662306263 -64663732363365643133353535366638333263343065366332356230623639616434393031383930 -37393861396633666335336432343762313331306136663162383165646166323833323262623839 -31323036623338396636396461303838333137393531306639653832653935663534663265316230 -64636138393464636336373665326230653039393433326232396462623434613335386665306565 -33376361386339663536653237306361666130666266366539313166323439323030383065613331 -34396633623838653033623132303163383864623963633034623364663062373233323461656237 -32313863643033633435376365633465646263333137636332626531623930363033396437353965 -62303536623764303338376533623936636631336638623332653761373164666630386331313032 -64323561363961366135383564333663653263656638303461633036306364653432646166396430 -34393639343130623533663435616165663132303064643763313133343037313836366231353834 -64633834343866396236663935613536393937616235633936633432616231373161303934353536 -66313434323064646137626639353739333836633833666630366530383438663330306565316661 -66636566333130343566336634636531646338376463323339303138326637343734613734336366 -62376639623734643133363534366462646333353863613137623738316438336234313238626236 -63353335643964636362383239383664396139386632313434633766626237353762353039303363 -36316362383964303637326166393266383266343132373763653631343031643661313064646536 -39346261323362366463303633356135336130343437383031373066373931323734613338653739 -39323230346238663262616564363936646163386538323232613036333934323661343961633936 -36623335643733666164663837366362333435326165633835636161663435393235666563633263 -37366565393263386538643534666630313031393838363366633736386162353464336438653862 -38343036376432633462333764393461353665313739633261396634323662323638366563386433 -63643737643463393532663837333034626539313737653132363438306137353432623236626537 -66323432346239326438313131396135383066393236316363646237373535346263653165346361 -30633163663838356237616637616630373539303764303531623836356131663536643734343430 -65353833333538363863383364333661323164303963613762653466366365633038363761636630 -37366464623930633861643639353162636334373533663439313037323737303031333435343635 -34636432393562313530373065636639383035303539626364646461376461613162623165643231 -33343439373834376164363530356165613037653339643861316266373535356635383030653463 -34623937313864613434653835326336623662643030326162353830356238643430613730313839 -66336232316536343733396533396432333839383334366234313130333733363134346434346365 -63313634313632346262616336353662373238336637373439373833623536396535343431306338 -37343032356439643061363234666263653238336563396334616135326534336333366631616265 -31646232363034613930633965653335633631343330303539663435363939336434386137343138 -62656665343864356561663731643937623665303733353333633634323635323463376266626134 -38623738646663336566316631643039313534313163396363303661326539336366303236383231 -62393361623866343635383665313733323632633866323661376464656366636530373032643466 -64363462653432376332656637316361393139633834303962336564383137326466356630313534 -34666133353237326162633231633162353236313933383864343464373538313964663333303736 -39663930363561393864636438393637323062343236626163613937383662633562353037353864 -32656231643664366130653637333964653962363039393339356465356263396134613731323531 -61333731666666393339613164636366396262653662316165376161326437613537373937353237 -63333234383434393437346236346133666330383962356135386265363233333163636130663466 -65353230313630646232343362643434636166356537393632303133643561626262373435343030 -62343837343263386230643334376432653035343066343131313836633532336432343139303335 -62396238663936616639373564373533353132323439393330326362373466313636373262323935 -64633463333966633031643465636237356638333739393437323235336136653135623366396231 -30616464653663353733666135633763623666333366303336393530653136343430326563306265 -34626366613833376562353365626562343133303166373365653231393863393837373461613333 -66353963373235373135353963616363323830613430623164353466653832663532343261383939 -30616661383764346139666130306439353065393231383065643932666161393438626338633566 -62636331333063636565303464616537393264333165626330643864636236306136663466643638 -39663966333362626564376264633432336330386637323463626132613237653433626139373030 -38393134306264326333326332363862353733356264666361303462303631663631383865623431 -36626137666139366164643337323836346436396332636230663265393339313264656362663263 -38333166313334323065643834646432626664623265643532316339636265623734363937383731 -64623435333936346536316161353531313063643733303635363031326566383733636636333561 -32323538646463346662613832636136653664333531363233353964656563306664303862383466 -30613738626636656363323665623261353635613066623037303036383230393837626335353133 -39613163343265306565343939386433383361336539316339373966616130643462333535656332 -65376536356230386531383939663364373334363938363062626631356536396635346534356334 -65396531383533613365376564346331383835646464313735373562366661633231303233636638 -31363230336533383435663335383565333632343463383038643735613863396337656230653930 -3438 +30626163666633316132313135613663343433613436376631383834346131386332633038656363 +3865346435306237623661323363336565336464383732640a323433326633633764366261623430 +30373331663130383330613630356435313134656338626566656462323361323330386631356235 +3562306438613134610a623432313861386362393237623633383032346236666333346564333634 +63396532653232343262363239313366366663373937653933643466383932613362616366316366 +37386363353263613663303939396563306463383638356661333733633638396265346337346461 +31386336323532393365376663313430393632393565363233393162383262323530316165663236 +66383032613963376464616666386539323630333065316630633666343161663038636464626634 +39666438363262343836343935386661336634643537356661303961376630366237393263613536 +39313932316239623930623532616134356439623933326134333762616438386362393439613836 +64653666353966663133343237373562336261323331323037303765633563373536353832343063 +36343931336562613433373139326532663332343139653061333963626232366332653839613963 +64346335336439616637383635353130653862316434386432373837336432646137333966363934 +31663536626137613335633635363564306664373938643265653235623633396337313730333937 +30643737383537633637303738393964636335646232633030366661343566336231663561643065 +61643836306234616431393138373036373464646630613061343863376434363936343638393534 +31356630306563653239666636643530663832383062393263366564326533333963336438666637 +33323035663235633166326562613235333639613435373437316137303231633766313436373934 +39663737363038666238353539613231333035613864386630303333333363313939333631303365 +65623835366266333364663630386663363739303933313037323739333064353966633232333966 +65663337613466373365353262366164623965373735666262643136313731376230373863396236 +63386634623535376534303561666465393762353334613233336238623131326234656233323731 +30663837666562623030383965353736643562313739326630356537376438333735623333313736 +37313234323565646362313238623665643036343838353937376636353162333266393731306331 +34386133393430636631346163643665313962363765666335666563613761336233313364666130 +62643931636236323165306638376133636436616438336562633163623439326632303464626434 +61613866333665613764303538396635323731376539653239353239666332313439613239663538 +63653431626432626563643737343265646337313465353431323464326266333131363737353036 +65313764613364356365333932323962643565343535663337613863636339363462663637646232 +65383164663661643763616436653232333231386262343233633238333734643332636662313038 +66373131306538356166363164383339616334616231663730333631396335326134653235346435 +37346661383262313436313334343135646562373730343231346633343863626638613938346237 +30326165386663396565323439643632623230623037306364316431633064663961386235643031 +39366363623336616633653862383763373630343465343937633937666233333734316437376564 +34383263623861373731613234313264656262366364386333363232346162343136333333393839 +37643664373430346162353764663464303538636231303339306138333133323063313062616431 +35393432336163383862343561623035363836303632636334623432393566393061346462393436 +62656237643334336133383231323062653365643862353730343238663635396439653833306133 +33393833313236356238646162316366313963613737366439616264353463383432636664366465 +39306235376536623464343835376461363838336166663362323237376337346461323230333262 +65363365613739323931356238363562626139393734656564343431363334643562643231396338 +38653037383464653832633734323861306539346434373266616537356635323163326465333831 +39383266386164363563616461636439643333333637643932363531656638386661376237346530 +33383831323462393933383361313832353463353561383331353165636138653738313835386537 +36366363333864316465376436613033343238306561376535633163323239333361366164333736 +37313230333837316236666532393338383734353363323139663139303634386363343031633363 +65626336633135363030303661353264363336326331333338623163323531646238366632613731 +32306531333438323037343265643231353136663538316630626131303331303263303266666430 +61626362303864363266353838303135316631623235663138663836636531336262383138613564 +39353735376436313434613930613730366234313132613530323065366566323066663064643333 +63383334626530303031303565613033306437633165336436376634393661323865356138383961 +33333236333066346239316535393339313837386638303464363161626165303537396462666537 +62336437366430653564643432383061633365393665666238373737626461353132356263323365 +39353833326236663833336634323464626361326338313737623136646132626164313565336534 +36616664626263656439376332313534356365336663633738356137633663366365626330383163 +39313936303663653636303162333237386530353132333637313765356233396161643636333262 +37376433633935653138323336316666613539353436373265313962346332373330373163333137 +66323537333736653333643239613065663637323662613165303738303864633032343030656463 +38656262383135646439316161323863343763653036306234613939353564663533336164326163 +64626363633831313830353266343334373632343363383631303239336438386130656534376133 +32333338623761663039313466653437336136623332376334616534376138306534663334653165 +66323634623463613561303437336237353365643266343937656439396366353531336665396435 +38373135306237376331383737623164306532613464633830633563666566363439623765336431 +63323730396135376636306364643863666362343733643037636466363763343666663735383762 +30316632356434383338613065323237303238363731346530323732646364613864663238663965 +61336437646266633833656536356464616334616563656361616263663036373132313064396537 +32633565303662623938396234636534643236383233313662636134373564326638626631396261 +61353133336135373434386466353536616135366365303432363763336538363462613036396233 +32623461613239303438323662393461633161316235653933643838333965613532636334343165 +33386331626237643138356561356263386261343037336639653138326235383331333961653931 +38373961333362623665363938656231616332323632316530376163326436643432623638636466 +36323932663030373333633038633531323932666364386263363464363630323532343330303133 +62393134326463346433623266633166313236316264643462303232333461636237343332383032 +31383436666365626432666663643431376537626361353563336231616563373763376539373663 +38303566326331653763336262646462333635663266336263393137666537373964346164623034 +65616463613039363631373462313438323533363062666333626436366432646531333338653230 +61366330643766666336363962313935303138623162363433326538363930323037386638376430 +33666337353037396361306234353436306331306563333339336361393033363838323832336163 +38643466363236363934323337356531346130653461653830653665653436363364383962626461 +32353764303130653031313534316363626230356639626236633339383137346338353464333839 +61363337373462383936353139653963636164663036343537333639396239613063316431653265 +38343633383164646665666131623965303162643537363631666133336630663736323863613435 +37383833626138653731666138343831623662313139663730646265343237396666646662346166 +30313865643763633336613630623566356235366664306261616234373762306139653962303964 +38666334393364626466366564663033323436353963376662396339613038353765353236303539 +61643464326332396231363237333065343031393734386336393933656661376430353861333430 +63343638643766373534653135393932303435323535306461303031666139313665626532373262 +34366461623036386339383533646563386435656136646334613037623964633561633263613834 +30386339633165376532613862356438323532356635313538373361376234313637336534336330 +35396666396439646533393863333631633931636335353632646465376633613138613933396539 +33333462643636373735653838303332383738633362306538373037646366396130376463613865 +61323139316365623363376663356366353831353266353932666536336437363435356365663832 +37653833376161363439663063633264643338383534303463386633373833643466663338373564 +65313134643035346532636534663238376463336261663333396161316334313734303062616363 +64326661353564326634616230646430383063613339383334356166323635346136303065373765 +64326335353534646366626632663136656236666634613466383034383266323831323366383462 +66316663613863326537646536646463333130373366663931666166366165383839633566386530 +64386533623361393662383136306264323166346432333230633664333034343064333633623734 +35356537343631323065353930626565346232383134353534336164633963363238383339303833 +31396333643261663139663233616238313261613965623737343033343632306536646365633733 +64343637643235343832393962346635643338366431636238336535333361616439333034303531 +66663261373865643961616335666538396130376338373766623636633264663237303839626434 +61613463623439383438383532353838363638633166643161313065616361326237666539393334 +65613238343132613062626564643030646261366363663763363439306663366364643139656538 +38343534326665636262393466303635373231363135363464646134376236623562666162366638 +61636563393934323065323637396239653537316234316564386433656532373539386163313634 +30663562343766353334306636653431373065613962366334633764346461653733313630386663 +32623061393134333935306335623839663764333033356561636161623966356435393862616534 +62636338373534633431643435656437353536343836653833626166366164333431633566353561 +66353238363464383061366534356465333466626164643539363535376365306364653134396339 +64653163343762666632353337646131623738333261353663333262316635666337636661626465 +33376138636562376539633762626661353330616564663962353331666335373335386134626335 +34306431633138363337303333316162656361633634333234356332343765616564326663396132 +30303438346537333134386330336562373433626236636565326564373833396237396665373739 +64613837346539356462393364366539383839666634613631656562323466363135653836376366 +36316439393731346633623533346139653866353131643833646635643765666531663038343032 +33343639363763333762636662373631313664663737366139633833383534663639323538306436 +30323135303231663938363134626164386465343662383566356235663739393462613635346439 +38623233393235663331393765323736393164306364626136393166636533363363636161633332 +61306333646131306638633861653665386262653032316538643938636165333432343432376232 +35303535303239386531383164663130613233353531386234396333303665306539633637326634 +35613961343338306363616136636164656134663762613733653835643766636634323132343265 +65646533393962383534623164666261343130383063643761616431336631396161316132626235 +32336164666536376165613832653866343435613131613436393262656333346331646136303866 +61346630636163353866386666646330303830643739353961623231306665316235643465643339 +65663536623535616534616638363865663832303132383238393731393831386661373831613461 +37383639343333376166333232353538376233636665653733633238663664613236386237363766 +39303135633961633734653535623233663039376133643234333138363138303431303939376235 +30346539363539353139306430303339333930316335353935633465393761613462303736316230 +35646366323833353262653530393337386165373937303331613035383263336637363036636363 +62646530356434313631613530636361333534373635333665626161393761666139623931343462 +37323233323263353065393064383362643966663835343763643136326138643031663962613435 +39313530633834353162643532666437316437343965613961633134626666393731376330336564 +31623137643037383533383730336662613364383636333232626334643537316235353663613362 +35303730616537326234633366636232633836383938666364356533633039653332643137383237 +32663263663130356461363432346366303233376461643636653764666434373166653632393236 +33313563373234663962316434653262663339306335626665656336346632643834666636616631 +62653832393363383133633763656530363338383163636463363266303062313330393164613735 +64313566303465323266313564636137626364343363633031353538363761323365303665353636 +34343533656434643731393836636537386263383262653633633839656561383962356439383033 +65326531363338353135353532386438396635326337653964333737313663666433353564353661 +61626538663835373033626631366665616330353738626233643839306334623039326636643333 +61346130666566623539366465306235333466356165356636653064616362663463643434343961 +64613632306338656564346437323636663133663665373864633763306234376436396137666663 +38646261646539386264396530653536366564653735643836316362653634303961396331613330 +61316163336466333537303136356137383563366464663966396539643231303330313634376337 +61356363356339313534653039343032356236343430373436646330343465353630363235306462 +35313362333562383032393333313333626237363563326539643839643937383335373166336136 +33653731633733653139333432636561306639333132623632393235656136313036376563376533 +36646630323936386437323831653737633536623961313733323339653832393931663934376531 +31386266356231666233373933646433623935333864653434383863336433376163306464363737 +61313438633434386335323037636539643630306438663938393561393666326236396133613733 +37323135646430316636393735643234383337643631303630366635643032383935313838616564 +36646537366164306466663239616162623131333735366564363931376562346532396438633462 +65613234363764393865646333353134336433313734363031303261336230633936376438333637 +32333531363864363431646661616231653937323465373062656261636234633138333131313931 +37376233353330623439373739333133393864393533653630313731376265323864663630616164 +35633036343665633934613036346134393261303037333838303633366365343531323338626161 +61316530663739336632633364356330363432663438643138373136343636653133646266636535 +62393335323734613962653562383063356563393565636531626465386361666362363638306462 +34336530313435386134363637353166626535313262643033353536653738333230336361643234 +34623637353536646264646164643561363531363166346334623531633564363834623534346263 +63623530303034363134623865373733376135326130613635663431653866666437386637663036 +38386561393232303331316632623837663965663130623964623430306136356366333438343965 +63323262636265623963663238333934396338653230386536306632623234323534326462646636 +30626532396663633265366532646439613534316366386261313731386164396133313666623730 +34383536326266623236313163343530346437643831396635386662326237313766363031373539 +34313563643166323434633233373861393535636564303930393934636230636338353638376231 +61653263646530663630393131623830656135623236663939343634666637643233653438336432 +37646330663137666234376161343861356435666333343462613030383936396333356330376166 +64386266626431646438306435323862363634663039666666633935633634326666376331623665 +35636337333566306131363666336433663336346239396334393261346363306466303833663738 +38613634326336353234313230373036303963343732616364306165386464626465396164373037 +38356439373136656364666339356166623834626565373638353061383064666331626136666232 +63626166663934366630336266353132303830653936323634316539373430383464643536636265 +33653637313138636639636231323563636164326339336137643039353962376132373738623361 +61393931636638356538646139363330653737666436333638636138343735646362326338613130 +61396238363263626436313932636165376163636165323933313130666636656531306164346265 +34393231353966326134616632656461633431616338373839333336393261346532313564653531 +64626430343864306334666539356562313334666462663639343063653435313235643737656137 +62336332323034373733336339303731646230393038333632333662303531366162323733336631 +36336262623830366161646662323166383833373932316663643066643663653266386330653266 +36323966366362383831663736323065373338363335633336663763376136663136363661343566 +65303432336432346162663463363465333635376136636363643864383864303534623835636162 +66316438346639303033633064376338646637623965323866326237383330663834333331303838 +64386466356265636564383832623061613663666464303431393838653739393837656463396436 +65653763623937626566656430383830393935386138383733363835323161643765656636366230 +39613835326663646536663335343935613138326236323265643035343138353861626661353436 +32636238396434636266653833336136316439363136346334663833303236613365316438643963 +61623062366264396466653837383666313630333266353638326334613631346639663631623839 +37663033656466663039343064323437396535393933333361323463623966623236646639656165 +39626464333665646464663332383839303666353939343231393331626437653463366537656535 +61613638313666613135343862303633653462323937343462303365393536323730326636396131 +64643062666336373934356664316564663735343339346165313930643566363239366265666235 +66646235386665383135623032306663303336353032643839323837303034336662376333626466 +36363330663964326532633132306139343230396130633462303565363532323237306662383939 +32376236373337636231643361326563626364336664626536633731353461306637353833623766 +30613265363163313933363735383961636139383566643133333963343032333036333832353235 +39353762336332333231303766346265336130303364366535393130626639613434383561633934 +36363531663539333661373265363134346566326136346435356261613431366363356332346466 +34663365646662616334666336353531326365656662663839633034636230383161376166353034 +65623635643061616130353365336539616537616661663334636663363732346432663638396166 +64376561643962363432373561663737333930326335616366663134303561333263366635393834 +65303834653163396233333539616633613037373162663333353734393433646363313138613935 +37353965616366343039393565346231343638313731643364636165623738336132633763616262 +30323931333031366364303530633836613262643865396435376530313538366264613961663263 +38396332326336303762323438366663396334613962353332636639323662393336316238363031 +32396265363664643136323263613962323336353034363634316538323761346336306137633365 +38376165623638623830646531616465623639663064323138323330613539353163363264626166 +64313132653833373665646539303665633937613036396264646233626139626463643464386331 +66636163656564633439663662353165653039613536376263356639626561393466356637643565 +34393930373832343563653562313865326430346262373331376338393264353936623565323062 +34393833323936626665633164333834633064353638643933336334303530633939383562643536 +34386363323330393936633637396566356435646139613761393737396130363235353931323830 +31373137633365346166336435393033623235656438633038313936386333326638316535643030 +33323133373564623830613732306638656364393435653364663062333938373431613335363939 +61623834623337393063326231356664343830383537643661316537333666313166343861363331 +64393464613532393339623035323439376638383338373933363739313134633537343435326135 +36343062393039366531366233616439333662383336376434366331633366613064633431313738 +31306330306534323663663063613463663165346662336433373131376132636435653030346237 +30643236313834646334303934343033333764613063313237646338303033396161303961343665 +34326361626362303037643862373733663664343563653761346665313461316437316233366265 +33323639373238323134313537323337616562306635373666356635383939373062636561323361 +35383532663134616133313261373830363937313862323537343835306264386131613536653764 +30313462646130376366626231306265373165323636373562646139613461346632313931663339 +35363738333835353336633463313064343365633463393134646134346434353535343632363636 +65623764666238626662343434313639333832396336373938323362303162346566663633323232 +34666463653533353934343531653962303333643666383166663861323761623232303663346431 +34336239653230306361626532313665316261363138353135353533343763623534336636343237 +32343965633865393832323334336266376364383138623432346534366161663332376136313562 +32626264653836623266646238323765386636363138643166326365373362336534353538333234 +63356436643336653135646634633038623464326661393132366466333436613935393565656532 +36396631346638306235633163386466643730383562393631343139393666306333353831633363 +35336535383865303435343636643364663936656466356261316136373239333530373862353731 +62653131643231366233643530656238346634313132363835303638383132326233336339366135 +61306232316235396163623966643933616233663639613662613761326135323230313632346536 +38653263646631383636303836663761613163653138393064623638666265636364333431366434 +37323663613865366134353563623830643031336337653037663737396131396530636266363330 +64663936363061306332616462323239623461306132636433633030323930373831373164313665 +63663364666336333035373038613431663034666365346436643335343733636335326132613134 +39623136383037323537633937316565663431303532363734376536613930663035376635636431 +61623138616264636266323232326136613330313461386264363235326335373032613861643431 +32326330666637633830343031616337653134393638343365643930656434653838663637376637 +34623434373734656135343362643230363331336635313436386166366332363634393236633166 +30356639643832636637643537323832386363316436613463616231303261643030653462346130 +30343262356535343964306337636665383330336563653138363565633365643261636334643835 +64666463616432633661363135626533633165663165633632343464323335336539663639656334 +30663766636139656239333463643539373533356464666635316132643161313138383636346566 +37653231373232636436643836303238656461643164376364353866363931396134323362633866 +35393839323035623664326336663136306438323461383262373036393261653964626361326233 +66633561303936386462336461326238356237633537643839613162643637643366376133333438 +35396634616337303664626364333564333239333635636466633233326639306365373366353438 +63613563636237336338333637646639316439353638646431306364656431643336396562663132 +37343636333139343264306231333231386264316639366436636263336338666163653233623235 +61613834363566636262643530643665663837633738636463366330323734363237316233656566 +34333134636432626233663636373865653237396364303565643962633638396333376334346532 +35633364643637393861653762346132306430356362303039626663356365393134393733666163 +66373761653035663631663238616638666235396561383137646336346261636338376664623036 +37393265333664333139366566373637336536323065643533363330653834636563656238616335 +33663864303534356235643532303937306231653066633037363734366664386539343733666639 +34396266363666316531633133373633353764356364323632333161643435623065333137346665 +36613532316666663731613036656133626333306265653938323161323037666161393936373038 +30626330353232633539636365323461356666393533626136343131653833366566306663313936 +35643830316361663136313065396533613362353231393831326265613537643263343832383837 +38386438303234366339633233663830363139376135653861306632393561383565323862343737 +65356666353865316534633939643263616636643362323963633231316532633538643464323530 +30666136633961353361353963336664623935346561363363396563663862633831663334326465 +33653836663538353837383639616164363166373464346537633661316436386230353733363364 +64303835333665343636656231663432643837633463376266663831343063626532623733363833 +31323364303230366630623532666435393633363061356238303030366564356632366562386264 +65326262313635656663393961386565616537646333643262613834323063383437396666306463 +37633966616630393531623061373139386261353135343266613331616134383562383333666161 +31633134623038636163333738396262383130306362326464643161653262336364646535663538 +31396235356338616638316665366261333238363166633731346536333662306437343363633662 +63373839303832393936623631343862386334653235623637396331613966626665353861356161 +30333130303234353165656536636664636564393061303862626434363639643332636239383136 +33373362383131366139643432616534353934633136383738643737373264353731633339326132 +36633732313331323963353734643662353633646536626666366237383739346339613362366333 +65346165633362386531316632663834303937633738623231616164653332383239633765323538 +62316665343438373965616364376438636366656531646365663138353730306366393539313535 +64306433376136313565666261363930333431373036333262306263653564626332356232613930 +32613330336165316365393735366132333933303133623533666263333931306530333637343839 +31306534306166373263356234633434303763643861373863313737316230613439363531633937 +61346361333538326132663034356230626131653434353161393938663933643934333165373935 +39373837633831373266313932326465373264346638386566333634316266653137326633663065 +61366438353035343034373432326238613166616134353831366237646461663732653734623936 +35323333666536663266396662313466316134653039663766303762343964636464373039633461 +61633366373966336135373662313431363435393163626530353935333034313565633232653230 +35383835623531353631343534343761323366346463646430346136333532383361326439353832 +39643039663133333736633165303131396461636665363937343834663730336266386537393130 +62633139323633626133343764656632666539383432313337646263373935613733346238376533 +66646433373539343334353837303661386165366365386139643264623766363231643338636662 +66333365323634396231636566613834366538363162316239353562633362323231346536343630 +63376233636636616532653630313533303433316163313835306337343763653337393633363931 +32323332323264313830356238386233663033303464633662653135336430646333383964303534 +34323035616264623433316534316664623133333665396539636265633062333962323233383432 +64616635643931383737353766643338653061336631313932646433633437373536343266373931 +64633535356138326562326437616163326537306330373563623562333335366263343135333935 +38333034386462363130393737323432313266326366336330383235653130663062656362313265 +39393361373333396135373462663139323766366138636137316539656636303030383733393666 +36653361656332326265336433363963376262386338386336396334326238656335383034366263 +66373332663733393631643234356635663536333561663631356131333830616636313634333432 +66363564653462623138353263353365613165376636356361643364613864313534373466633263 +35333866333934636231613966653535613564336561313034366362636430333331623164383063 +32623737613566373636366335363766336166646532643733393664376431366330316336303630 +32616235323430313564323764613839346335373763383030653939653232363866346232396263 +65653266303133343637393064306265656532623935663833636166663638373363333362313930 +65653733323338666362653133383563396365643539303531386130643162376263343431383063 +38316631623062636133333835623231376231376464373738633936633635343461636565653066 +37626133633964643535393035356366383731616630623438303139383762366664383433623037 +34396461616634613234616431396432323531653761343537353232656435666262333766353633 +61643936376463366634623733386635386664303934386536323163363766316165323930363438 +34373361386133373432616638623230383035643038313662373439663465386633386263633761 +62333465633761356333363862323263383464636236333530343431303264353333666530663666 +35616331616535656336653932326132633230626533616264636136663366653330393364323166 +36663939653861323737343164386135303430303435663664623532336661616334353266393238 +65303731333831623036663165333735643839306530366237393534653166333164393532383733 +64396238633130356237643832646539313839626438656232353063353832313166316338366564 +30386530383464363531646532326135376564353663326162336339633537646230343765386461 +32613833663163623561393564646539323935383835636633353832623336353565386435396337 +65326465643837613265313436666136623162623765366362376233616639303561356361363233 +63363065316434326330663331363865353166333633633333346439633133323261306166663637 +66356134353031623661663838303539323134343836363635633139373138636236383965326666 +66333366313032366533353962366532646364613336393133633061616135366239613839363135 +36643830373061323131383030393036653736396632333062366364346231656439333234616261 +65343232373566626530656436373864656431613761616235343739363131396238336662343265 +66343732636533353566636164373262353231616535366661376136653739373637313431666263 +30663832356565613764336233633031376634316233633432326361353735626233323834383030 +65636633336239303830643133306437353564616333663531626665343065363635623439373332 +39373234356264613737653966363963633165323865363765633866363430303436663365373230 +64376339656536373433366364373031353565336664333765356531336163613834383333393336 +61613730616263333430383562613939353463386632646432656339383838313331653237303265 +61396137633136396530303131373135346163653135663338336337663630333466353634393736 +39393035336436616430613737373166373334356234363461313264626132373361333466306532 +39643438353261306632626664376439623163306464386632363937366138353334343537643430 +38653538386162663832366164323832613733353132343139396266343838363137626334393464 +63306461393436383331623865613132616265313135616532633965656337643435663338656132 +38313839626134343834306538383436653239376466376437363766643738373734303435303530 +64663731386164323837653962666438346139613839626461666462643639653734353837313637 +39396330643236626164333330363362386262623361313661343863666335393639646232363337 +34393335653163326432333939353532353065353066323835336436383231663866623637303532 +35356231353631323739643965303534653537303565336130366464343765353764393364346164 +66616165356137626336643461313535613830386134386566366634333630373364303365663237 +37376463386165383661396431623133623834616238646533353038346336643534333663653731 +61333362303566323933666339633661363133313564346365636564346532383763303437623732 +32666531383662376266306432666634663433313831333165333063306232623539663834373065 +33366431613039623765366662643033643564633332613562623131393931356432323138653139 +33323833383039666162613761343461313630646363333336363563323061396534363564303635 +35666635616439316131326161343434336430626365663362383264316263376166633438383637 +39643637393830646164313665616630616439313434646335323161363639373464393437393066 +61663361623264383463306138666130623862383538393136323830306134633933653931336630 +61326230306234623337646138386535626435643039373538656138386432633364333830306633 +32343065363735663930376237656439386332623664366234303138336534356339623438353961 +66353930643639376163356434383262643434653538353238353862313866626336323238666531 +64613438383137623935303839396161373733623735343637363164353565613037646661333132 +66386333386530303161656130636563346164386336363932623138393464366661303165373761 +61393832316631306666363661373336383664373064376532616232356332626239663234663333 +65643833613539663533653830343062363762653663663333643265393238643535393231663062 +66316535623638316262646233626330663636663561313131383239393831386366323764303732 +39626439313136616330643332333537623135393164376562633132613962336662623836313337 +31396261353430666135323936333433363539326434346463386134336135383630363632363039 +30663065333961303966613465396339656336653235623333316133396361313862636331633935 +31633962366132346365316434383231323434616235353433613561646631386466323266396261 +36333231373230373734333530653632663931346239323837663131363865303963376664366162 +64336464393934353537346533366138333163383139663861646163323438623962666131326366 +64353936356431656432633038386630656632643838316635613164626536313835353435383265 +30393331323334396139383838306662623730623464393562353434306331386234633765326635 +31656330353735666439666365633336373266356635333161383863663033643236323461663163 +34373433323037386332376266343837636462303230343035383364663833633930313364343231 +37666135306336363936366235383265666666393831316339643832366463393165646236356164 +66363565623133623130363738626137393031366237376261663536623866653233363038613232 +35323839336632656463626437333931356335383931393031376132393834366337663837396135 +38373433396363613930386265666264333836653066653034366562333165666332643839613236 +61363432373432393765393538623562613830373631666438303930633432663061353438393532 +39653864326362666464373466613535653238633631363231356666306365383263656235653866 +61373132333738393263326662653966366462396132653866346439643637616435613864326231 +39303036666535623635346636353530613131373861356165353062663935336636383663396538 +62623031613235386265633631656332656536303066393433613336326639666463306432303962 +31343765383838366133636439326337613139343263363266346632383636633261616165326234 +38313030336336333638666339653561383938323630613761326131323732656561663534393532 +66343932386538306435376433656562323032316164663031383736343837303130626633333731 +30323537633661356231396338346439333765616639386433333535623239373932343064353366 +35343030653233653734373566666532626164366265313930626466663833333639383539643232 +63316633343165306637653263303832363062383633653336313161633562343730363538613139 +61656432333565303664396335323866373666323738376263666430653666396666333037633138 +38393439616337346337353564613933333935633639356265313630366566386534643732316438 +66633061303031326133643764623233306539396263396662636361393637633433346633663134 +62346661393838363934623730323639633036346262663831396638656562313761366136636465 +39663537353935316662346130653230386261623438643439303430303232373238623732376638 +30666539303461303036396164323835383930643136323533336232656637303461376561303661 +36313361356663306631633861656563383766393833636536326339313239356432653037633436 +65366163653366316164306366613437336263396363343237623937353164373532333565643965 +61666665316337633361356136366365623239303332613066373438306632613636356535323632 +34366566626364633966313332636664383463626439616131353234393132343939636333306635 +66623435666430623362346538663836336135326365313434323464346631633033663834353237 +31386431376631393361356361646565333765623965333330333333653032663330353632343062 +34353034383863306234366563303534656436326633306331623363643864646161303662666561 +38356263633162613232316235626538353661353962353838303033633138316363643237666435 +65666338376463326665373932623763313636646135363034666432643930613336316237636662 +36386531613833313839386161353632356162656431333637363033363464383533333336633036 +32656166643631633133326232616165366465386564333739323163653733653031663838653737 +66303363626338386130363230646232373833343837363938323861663861393235343139353834 +61653532313665323930353637393433653738383936386434336563643862383066363762333733 +61613437386531633734323766323131386262353734633733663333323139336131373932353930 +30306134376433316630366639666437636135646239396661616436333538356439373565373238 +65343936613433376134353632663639323262303738396661666135333062656139666562653139 +63336536323463326462623637626332336662373131393939353934343762653933623364616330 +35613562343963663135316534653137386366613131393462303136626463663336306132626336 +32363662333032616434363333646261663565653131306637363461396262316430356462666131 +66346462363462383332326239643732363336316130343264623339323839343164633565356636 +62653239373838303731393130373462626462313261353637623463353166313163313831646637 +37306433343733373538623565613861313264353638363339333437346232323961363132306233 +36643138396262343237353435333365373864353330323461333331346434313862656465663361 +39333435646435333238633234366133623133313363313433666530346337646635356362653435 +64373335636366646430633433396634363537336433306637343435363162373539626663343337 +38396634663365306434646663643233626334663834346166633235633761383466616438396562 +32666235643566653532663961666665623939323164666266633635386161336237653731366132 +64316237666562613266363635613833643562356331363238656436346263323631356635353163 +66646136633533396437653237653630666664666331313361353733643363393562336233306461 +35366263386332343263626432646466393462663932613535303830366230316466393836356431 +66383466613161353136303634356166646432353862396639393131666266346436663862393239 +37343335666337663331373332656361316166393836646565366431336534643532636430316163 +61656135373962346535393633396464336534613161363939636434396362356464636435636661 +32373333343935313430366639626266636533643739376433646462636239383863396166356532 +61376535373264343635343965623836663964623464636335373165373037643436626631653237 +30373364643739326133643939343236313965353235336635326633323464616331623963643166 +66633937383861623731343234613235356261613566316565393961316262663137303134326436 +63613566613137383339636239613463643966656265353138323933363737376636373337633463 +35303362663033623931343464626566633332316666316330666362336637353237383736643738 +64356632626661366263363930376139623233633034326138316531656534663465393834333565 +35653165396664333463333062656465613961363261353836303833343839383034343634653331 +64313566663939303630643266333938646433363966306435613934393365346635333562333531 +37666331663836313637336433393431656465666536393161643962393432383036653261346263 +31636334623961323663333932666432353537303739366564353833666562356138613136326438 +30326666626166303437323839623662386261353161326662396434643332623635626637373233 +30323365663031316163363563363037663638313232306164663562313438303736386136653432 +30343931346162396235366435656138616234613562383033393561353234336336346339313166 +64366431633363373662656665393266373832623733643037633262343931613439333839373765 +62336664383333326135643434613936303563303939663663376633636462356535633534356135 +37363966383835663137313866643062353638646566383766616337336332663136303538356134 +36633865396338636536333034346661356536643531346362613563653730303863663761366539 +36623565316632643330643830643131633065633064646132353431353664306365643266343064 +38303066313564356435373938336461626263633462613833313435396534656535623738396138 +66623764343737386436643331636531616365343761333237386663646638386666353865363038 +30306265373434653762373961653330396232653237343765623433643637323339633235383964 +66633334373730373536323734303932386633323935666466326534393533646261353361633764 +61356139616665313735333533653538326232626437376334383032336134356665323664666233 +64636335623131663761356663636133633137323332353737633964386261663139363931333666 +31613634333331353339633837333864616261623032366438346330383932316438663830393361 +37326665363039346636373061323162303433326338353064386535653339383261626632643339 +36663962386537356135643063383166356334373932636239616535393766323362343662636337 +65393930363039623332343464646436613633333266643534626562393639383236656233646135 +35653232396538336234616338343666303131653930376431353439336666353764646461613834 +39393338336339386633626636326333373965336365613432653530333833373138393065373036 +62323737623539623535663738316330303062346633373734353935373936616366333736666436 +64646635343639346462633265653362353936343165383737616166396230383938373430623739 +38636535343063323666643535373261653637373437326661383534643763323362386338363731 +36613236643039623265613536373430326635323432303133306566383838336630323362303032 +33323336633361343763353265383864623730633866626635613634663964633964363436666339 +33373161643463653261653363653033326233313663313562376565633932393930353363316630 +39326565653737666634366437366162353436353639653837396261643234383766646466346262 +64646535373838313562393632383832343438393864376563616465326263396163373738383162 +31386432353465363762316263646562383634653438643162373263363163663461386565373736 +66366666646635613035336565396536313037303261663665306238313130643063363233336164 +38396634643164313461643464363762623730356162366662313634323365626364313066643735 +62356461376334383265373832363862306666306261616362646130343132633965333530313333 +33663633323537363533353861323231323466356535666431636337376537343432316438623034 +65303137383865373965373835663838393564643165346539336465613162303863626536633966 +36663233643665333562356663663334386162663434343061633935333931666633646432633465 +39393964323234386531653864643661616135613264373266303535333630316163303265656466 +37613762643736303636623133313661333939623338323039666164306337636139303636643561 +33656433396661373965383439343366633264626266633738373635356531386137666538383036 +39653764303438346536623932626238316639326565323033316131303534666638323865356664 +32326336613336613731643631653938316131376239626331326136373037306163323033326331 +30623830613239353632666563343439626630346461393264623465353830666330623833363565 +33653430653332313339326433386133626339386332663634613861616137623866663631343035 +35363762353038313662653463646435336136643335393961633036393334626435656337366466 +36613763356334373762643137343439323034626332653937323834616663613762376232653330 +37636263336437343532393835623931663536643966396662336164383866353531656436396237 +37383861663261616634356635336639633464323539313166656138376335616563393036366135 +62653632313234373166636163396230323938353066643339356136366235313237626332626235 +37343133613461616462336637333761396331633764646330343065306135653134656333613534 +31643939633134353537363863393235663337623031633431373733343439646638633431383765 +30383837323937663630363964313764626663303136323566383637663232626230373638656231 +37613362633266383936323433613865323939396530633763393036643264393034393838323433 +63366662353162663064373261656434323734633561316564616636303934306363666631663433 +34613237656134343537323934383233383766623038373937343365363536366464633166303432 +39326165306234343165336361386566633062366163323837643434663231353261393630636566 +39316165313366663938383866326534333539376230663261663965613362386466613232383535 +62363764323336313932636431303132323363336163383861396262613666666638373634383064 +34303532353563313765376537623137643135383032316463636635633231353037646563316133 +62386365366634383661623339333361366532366330353735613132343331306338306265643363 +66393639646465633962393033303161393963623439383731393737663330336266633265633032 +30306339336433336366396264363133373438353838313136663364346261643937373937616530 +36343463633263363638343035653733336536373931396262383939333532643734376566346437 +35333963626561626264333764366139396464643464653635343261633833643962356535643465 +34316531626139643463353934343034636635386433613635363633653139636138323063386266 +37376537343639376631313765343463666232323131643737343665663930333565363330663065 +30313431653465323631386264363037643338643133323532666238313862323335343036306363 +39353736633832373966653930376463646639383234613065363035333937653731643933656137 +31626139323432333834313838623736373466316339643835623738383733353138383166326635 +35333632643836376431626135303537363866613632303334653666393462323466636430336437 +36303338316338366435663866393830313933636263303430613335393365316465333263326435 +34626266346438663566303630643562333539613061346362376334343666316636336661646136 +61636464366336313338313866353562363534666566666163336233363264333362653766336436 +66616430653837666637666534616330306639646462346232373765326562663264666562366236 +66373361643032383038363331643161663939356635626233623837323831376236306566623538 +30313966316434313032663933356335343262356132336130343338323363626438303462656435 +39656537333666393435653762616265363037373438663261343230336234306462343132663261 +37626134356333343265633535616230376632626633306336313136393665636436323930636565 +36313336303934373432383238633034333330336461646536633266356435343363643564616563 +37653636393739653039346631623765663861316566346135343038316535333835316663636339 +36633031376565633238333337616336656261393661313339633934353339636631333865636639 +31663162373133363834323430646632666538373965386536303732663162396666303266646437 +61306561623334316439386335386639643136623430663463363337653633353039623064323461 +33393636643939666164366230386433646130363461616366656239633963393265386236343632 +39303233656330356339613630353135346239663766646530396665396361393836366465316235 +61613764396261386563653462343638626630666661343565343338396162623236653236353737 +66383735643263313635393836333361313266366530656663303732363031626663373039363264 +35326634633634313164343664623334656238356662306534383664323137363635623235363766 +63373465613062383163383939323233623531613731353363643731363562323937663231376530 +38363265656463656664356261623337333737383662333638303335633763313263383230323163 +30363630663939653961323937656135613132323265333132636236623632376161336136376632 +31646363346536646532626336663239393231636634646466316161613431663464613161353734 +62353435376435373433613861336430353762313439383933363236386230336464336134363536 +64323533383862306631363139326665666265353264363030393538376563326162336237323361 +64316235316161383437306166356161316264306565383238656466363637633939313533383536 +38613731376266346631643766366434323234346466373663306232366331613434613561303136 +38623864393431623031363830333862373231623433393563626331336634366331613733306538 +34333337346361663434643534616535373432663235353536333661386332343266376436303733 +66366565656465653331326437393863653763633866613638663763313837623938343666386534 +64636336343831616534663337303466353339336137316363333132306134323531303564343539 +31363363353630646432616630376136653230333331653263333332383562626161663863353463 +30636366333631373432623037636239353338663835653162633336353732363664366232356535 +38666334343566663931653430636666653734303961636636313664323165643032363738306363 +62303438646434323633356464346233663437633363383739653266393834323038346430393035 +31653135656538376435316131613138633065613037643135623336636538333834616164356664 +39626563343963666166653737663839383337366164396132356239336166303566343762626638 +32376164623361366461616639346361643537653362346539333239366539643563313461386133 +39356532626365373935333066396636366363383761343662623134353230643432666536383033 +38333163316365633132613738373935666133653462316236363765666339636561376530323135 +32613739316430393663643833643035616234316339663962333764323730663763353437343536 +64316166643565373334393838616337393966363137623964633762616330343663653464303035 +61353138343437336338363635366564323135333434366634346334633261393133306132633031 +30663561356239346665323131383961666566636666363133393464633433356437323163363435 +31313564306435613064303631643430376237653064663963316138353866333532666633613538 +32386630313530383931623037636465336231316165373931653965353132663962663239653932 +61383162303961343635343535313264316466666635363665313732643430616162623939646433 +33643935653334633564356438363566393139356434666338306237633163626532616336303437 +39626631356432636339616262626233623032636137633564343333383634373830653037346661 +64353066643566353162666263396161656236333832386366346538656231666137623739633664 +39316332646131386465383230646432306635393432313334633364626263323164346637616661 +66393234633138633233363363323736653138386435663363383932613264366462633263613035 +39636336313038663334306135363432353634353736376530613162323865643938646333393437 +62303838346562396638303533623462363662613835356363643232336663623633646635353630 +35663732666464643034663737613137666164646537376139663961653831616334613333326638 +38343338663839366634333961336337313130613138346262363535323465313966623633303738 +35326535396630386465353136393365636635636564383865313362383532336165663430396232 +63333333373565316533663064646633363038653265636663313733393065656434613430663963 +36393733376163623635623266663831326361623864326466653638383630613561383036306238 +34343163373766613935383634643235373164303433343934393437323439373635376463366265 +37353166663433653837333364626537396264333461346435303361623366643437383366343664 +37633062626132343139393530626234353864373534656633333636636666636565353064396638 +61356463303762316334663238313434636534316530653434383831353961626462663066363539 +35623433653961393239323839616262383135356538316364306335666631316463303861323736 +34613661333163356661383065363266336264313462343335653338353730653433396638393337 +36636566616637393165633964303932396263383033306466323564616462396237303435663539 +34373939646435613431646663356432323138313835373764393436356231373033366437643762 +62653434623131303637666231323264303739333237386131303436386330613634653838313666 +66323036383333303861626136366236653838386562313466373864666539643831656364396435 +34333663623939643861326632663732653261643436303865353138306432653762336163366135 +39393836633039343735613637343030396263366238393564626533343566393661653730363535 +30346363386361346330393864663731333032643636653662623332663237666137363362366233 +65633366393331656466303832346436653139336161643466383035316234326637656462396131 +63336633666537353262373761346337343535653635343133613037383834313036346566646262 +61636633646466656438613534373464376339663935376564626633363765323639373565333938 +30336562343837373730613434373464633539353365363332653866323130386136623764393337 +33303634616438323863396661373032366437396632636133356431343333343361333464373737 +63626433363230336132626262313036626133623731623932623465366664613130396366336236 +30656330333732653434613566323532626631303866636630643434323639326638326639383764 +39663531656539613037666431386538373031343537313236383165626231633062663939663831 +37386237306231353062396563373534656133333865363731633331346162623832653465633737 +33303431343534383462316561333436613331303834633662303564376431383965623764376530 +61393564613539663435653336626262303039393762383937623532383565303238363836653862 +37343037326236333434636462623964373230663636383966306139336664333038303230636161 +36383537366662393262366638373562666330343437633132313838353166323134303936636561 +30666136303466383461616166653964666434636262376334386535323163353939303234356563 +36643134343138663666383962323137333036663762623834623030663862396438393063623062 +30646436656431396432353734643762316437653066383435626535323662366435303337373935 +30613832356663366334623065636361303961616532636630363130326530643835313139356466 +66313166616235336639633935323738383730363264303036643235363934353562383961333732 +62636136616136323762323933386432663437393664656133663434613363333532616531613230 +61386635396637363137613135646630366132343361353933323330656636363636393065633934 +34386532363439303264386437636530303433333434353030613363646236303364356664303131 +63623238386466393432336261653832323731333939613934613534303035633761613833336161 +37643234393262616338343662623037353263626565316362326231623439636264643731386562 +30656238343833393635343164393632343434383936363962666339653734346366636431356534 +65323936353832616563326461356437316463666463643934313638316532316330343762636264 +65616536636430303561376261653066356331653532336137336661396539393837646538633166 +34613736616362633536653361303434633130656237666635383630306637613964643965393837 +65323335373833383931373966383532363234386538643063313566306637633532623130346431 +39336634316531323633303634313938373234656636393963363832386437636366306630613138 +38393839323137366135396631373933373836396436333637386630626665636538366236323331 +36373662303636386566656139386631653436633130663031373139666635363835376566313662 +37393932353736623536623163656434636162353632643036666161646161613336633034383938 +63373739343166613765316336356364303238306334353632636435653338633634346434623239 +35346466633732366430623164363765636233623630643566653265373832373662323465633237 +65326630353261303037373632386363663838393638333137336433653362383334623431303434 +37616235666666323534333936623161613334656534613931336263356664373036353337393034 +66366565366632383664623334363536356136363538393264393265613066343031323865613966 +63346334313139353032303664653264663639366565313038653637343266376165356635386662 +36643833373033316365623931636262666239633336383061663636656561643161313264333664 +37373831643136653162663538366366383966373765323662373031346363623235666430313238 +31356137626566323231363239643839616234343231313066636561356434663066623862303534 +66643131656230366636303630303134386237313536663336363538323061663564303634373866 +39373235643531316436396465376332303032666162313762323336366662626262343362653661 +62636633306264393561393936346239363864346164356338636161393166313461343364316130 +66343463333266626231623536626564366338323738653966373937623665636531653437306366 +39613031383335316466633264313335313930663333303335666236626461623337666565663733 +35323832613330643332313236623463616437383266306137646639653731613131373435653666 +38313432653032656339633964306664346537623932613632363730303035386234303736353564 +65303633663764306261663037366536613262643031303337393261316134666431336461323236 +37333332373930313133333731623838643765313766363834626465356533306637366330653865 +37376136373431326433343337363737343963356335376563336432393032303134373335333462 +31383034393366306539313930306430363338653962613437663236393737383535383762646466 +65376534363662646237636635376132643030396261633862396334313066623033376165386631 +34396363623964316534663433393938373432343364303739616363366134646133313136326637 +32343234623464366438356630663735303161316364363161396333626563343535363830666336 +31306663636133653863643336323337353332656131306234613765663330303835343232326361 +33313831343863633633663135306661333131363061346164666563663435343536656364313963 +66366333616232383032333866623135306563386339383133663163336436343235323830373466 +30376665313565633430656438376138303534376135346461636565646134633934396536656637 +64343139356263653964326163366665626563666332363136616330633234663965623762363161 +38373431333465333631636133353337663966386137616231616538613430633738633730363534 +37303630666633343739643237623962366164633861626130616664303631326665393663323039 +32613333663362343936326133396439383364346464326535616334643463623539346633376463 +62663035616561633033303964346465613432313131313064636538393233393134303431363135 +35643233393263346631336634663866393232346364663637323665343833373832626666306237 +35393062373861663561373330313937363866643565613166633866396561343366313036363532 +33646137646233313062643930666633373631363237373937613161616234633362323962336664 +33396337613139653563363365336264393139303362333937353861373130376234366333643562 +37613563363564643461376164356133303834303665373933653361393732646662373939313564 +37373262393636306166656661373265633161636161356365303962646333656234336435643539 +31656131616232633632666334386232396164653939363039386635383163363130373932353562 +66303366616166623237653865323235303336303263313938663435613166363664633766363363 +66363863613039613539306538663337333366643631396564396533306532396263646235323164 +66396531393035373836316637386633316333383536303633666463316365623630303532373539 +62623938386364353131646566616234343132343432623861373730656237383433326465386234 +32373866353636333835646137643233396265343833363566303365343461303532626563343535 +63663263616265333137346231643866613737373835373366366437616338333336316530333039 +35636164633438396263636364393830613435613132363732623461636465313066333237356237 +66346361613730313466326563643933373462323739343130386136316666333936636565333732 +38356563333935333661626235346430383133396331336434316436383864386436643661666639 +35636335663637366333633332613836646439333233633662323561323634393730666463323265 +33383634363064336261346365306333656263613865373264343164666466353338363361333437 +62323232323166303638616230646437366335323730613633643364396633383238393461316665 +34633861376233306235366431623964373263386665646361356363633639313637383130366236 +63623862396530633663663231336636666532653363313266393030316133363830636533383232 +38633635326537353633323631616239626639353363376436343061306231633462376336393266 +30626637356432333766333230383237346131306439646137626135383836316636393036333339 +35643366313137323465346632306330656564306635353536663531376436623564353537386661 +63343733313737613730323163323765613837666130366336613839336235383931346261626639 +37393239336332333165336639363432396465613566313838353363363438613862626536373735 +32306132363331373136366263316137313437616139653533373261376632636566396436373437 +39393738336462313864613234313065356332313738376431346366653735333339313435323337 +61313363653062323939376336663931643539386433633330666664656138313737613661623138 +65343564323463356636346133363463313363383139653035633338343132383663396366616463 +34333665366432326135393532656631356462313161363939633731303437323832633338613230 +37353865356632386539383464353632396332303839333734656539396533643036633864346335 +39383835313937323338333538356264346433616338636135646630343764363937343331363333 +38303466616435366335666130323061626531363137363761393562633666373365366665306330 +35306437343130396539663038316136326661393432326361343965623230376235633061343164 +32303539663937346264656639656665333232343030343662393966383963663838373136353334 +34326437636462333632613933396633663136316333326566643364336361376430383436646336 +37386238313537653161356639366337356130333764393065643764323630346231363966383062 +38303662303637623966646164376432323263353737653764303135633363326461363432636431 +30383035313162386637636538383463623264313535333639613134633333346432663437333863 +34393164383432373463353233613436343331633063313536666461333034633734313638323638 +38333464386534373366666337353239663466343766366637343832313763623265643433376264 +62376363656166326564333032306137353964353938623136613461363230393161613237343039 +32306534326661633662303432653362326535346664616331623137363232336439623235383662 +61613339373532373039646236383833393334623231373730383139623534393932646465363238 +32343231313864633763653931663430663637666137323839333264633137653637663533313335 +33366137653035303565626464393231636530353038303036353335323630633132633734656336 +32333632316366366362643234333531353532336232623761633630613163613265326631393363 +31353236313138666463333939656564326365646339636633353730386639616239313338386438 +33626137383238323438376533363333643466383239643638373230316466656333386364366165 +31653638386331666633643666356561656631363636636536373136396338653432346539323664 +36313337393566323635363934306533666364343732616537656535376166666238333438636165 +62656636373037353637303137616137366664326161663631373136616338646336363239323139 +30616161366636336665626566343465323734616335663630373136646132616664303066643763 +31346665643234396238663332636661303938326436616266393537356634303761646430393039 +32653933663731643431353261653433313662653732383661353761613237396438393736636431 +38363135396163653266623230393037306266616131653839306335653036396432626538663539 +61626162373331633637323964306561333463393337353961306638623737643464383966663763 +31613832323461623461613932336132653631383034386635356564396166346331633334346663 +38333033613066636365643964393931336530656632323632303333356363323961663637653861 +31313832323832376235323038616539663731356664643566306461366539366432353130316664 +32386139336439616562326637623934653239363031613263306537623136396634653831643161 +63303831386538313466623138636539613734303031636533386163663236336335303864656331 +34663332356230356164643637656235636666323665663366366139363461343062653464386330 +37663763303266313833616131383332383537353061666231363834353039316239353636356632 +65386561656236356264386634613565383265626333363031313964643530353662613735313862 +35313362303734656633326261643066346465363666383533623163633239366237386366366530 +39656630366334353335346566346631333638343939336462663661646235396130656264316163 +38633262636663323930663462386534386531636262343230343739333434653330326461613538 +66393133376466366364373131343930313838383133383665643465643830613264303730646265 +65646365346535623538633562633738613439633465303565386434333032346334323739313731 +32366666383961653834636135303736653536376430653564383837623932303334633039646233 +61333062303365343638376637343437646333306430623765663236356333303333346336623462 +32323538396166323931393264613661656231633436356238666664666434363735626664343266 +37323663623065616335313733306465663861383834646663646134326230623337316438636363 +31393966323236636565633863343833316233373062303837336533653636376437316539343437 +65353033326563386466363865646236643763646332613436363838333433373837336161396334 +39616639656135663432346365343431376535643063646163626266626235626535376434613637 +34313336383338663164353734626536613762663235333662376363336236386639343936353037 +31336130633366383039346334623136343662353037333137303063393538383636383361633562 +62313661303966326365343661366666303761396238343338623230616433376130653634393939 +32313062613332313530336333363137316264663430636462646132636261623239346265666530 +36613461333964366335373531386363393363363666656532643664333736366265396238626330 +30363037643930353534666536623334393832626238363235653438643230376138636364663263 +37383634653130633635613137636436366331333630623862383462343331626530636663313864 +36623239376233626331393732383930386232343763613866663939616537623162376335326330 +61616134356339336263 diff --git a/infra/ansible/inventories/qa/group_vars/lakekeeper.yml b/infra/ansible/inventories/qa/group_vars/lakekeeper.yml index 1c37993e..4a50aaf8 100644 --- a/infra/ansible/inventories/qa/group_vars/lakekeeper.yml +++ b/infra/ansible/inventories/qa/group_vars/lakekeeper.yml @@ -1,4 +1,10 @@ --- +openfga_database_host: "{{ vault_db_host }}" +openfga_database_port: "{{ vault_db_port }}" +openfga_database_name: "{{ vault_openfga_database_name }}" +openfga_database_user: "{{ vault_openfga_database_user }}" +openfga_database_passwd: "{{ vault_openfga_database_passwd }}" + lakekeeper_metadb_host: "{{ vault_db_host }}" lakekeeper_metadb_name: "{{ vault_lakekeeper_metadb_name }}" lakekeeper_metadb_user: "{{ vault_lakekeeper_metadb_user }}" diff --git a/infra/ansible/roles/elt/templates/secrets/envvars.j2 b/infra/ansible/roles/elt/templates/secrets/envvars.j2 index 6a18225e..6e69950c 100644 --- a/infra/ansible/roles/elt/templates/secrets/envvars.j2 +++ b/infra/ansible/roles/elt/templates/secrets/envvars.j2 @@ -11,7 +11,7 @@ OPRALOGWEB__SOURCES__CREDENTIALS__USERNAME={{ vault_elt_opralogweb_user }} OPRALOGWEB__SOURCES__CREDENTIALS__PASSWORD={{ vault_elt_opralogweb_passwd }} DESTINATION__PYICEBERG__BUCKET_URL=s3://{{ lakekeeper_catalog.warehouses[warehouse.name].storage.bucket_name }} -DESTINATION__PYICEBERG__CREDENTIALS__URI={{ lakekeeper_catalog.uri }} +DESTINATION__PYICEBERG__CREDENTIALS__URI={{ lakekeeper_catalog.catalog_uri }} DESTINATION__PYICEBERG__CREDENTIALS__WAREHOUSE={{ warehouse.name }} DESTINATION__PYICEBERG__CREDENTIALS__ACCESS_DELEGATION=remote-signing DESTINATION__PYICEBERG__CREDENTIALS__OAUTH2_SERVER_URI={{ keycloak_realm_url }}/protocol/openid-connect/token diff --git a/infra/ansible/roles/keycloak/tasks/setup-realm.yml b/infra/ansible/roles/keycloak/tasks/setup-realm.yml index 4651823c..425146e4 100644 --- a/infra/ansible/roles/keycloak/tasks/setup-realm.yml +++ b/infra/ansible/roles/keycloak/tasks/setup-realm.yml @@ -50,6 +50,16 @@ loop_control: label: "{{ item.client_id }}" -- ansible.builtin.import_tasks: setup-ldap.yml +- name: Grant machine user permission to view users in the realm + community.general.keycloak_user_rolemapping: + <<: *keycloak_auth_vars + realm: "{{ keycloak_realm.name }}" + client_id: realm-management + target_username: service-account-ansible + roles: + - name: view-users + +- name: Setup LDAP + ansible.builtin.import_tasks: setup-ldap.yml vars: target_realm: "{{ keycloak_realm.name }}" diff --git a/infra/ansible/roles/lakekeeper/defaults/main.yml b/infra/ansible/roles/lakekeeper/defaults/main.yml index 20a186c1..9615b7c0 100644 --- a/infra/ansible/roles/lakekeeper/defaults/main.yml +++ b/infra/ansible/roles/lakekeeper/defaults/main.yml @@ -1,6 +1,7 @@ --- -lakekeeper_bootstrap_log_level: INFO lakekeeper_container_name: lakekeeper -lakekeeper_image: quay.io/lakekeeper/catalog:v0.10.0 +lakekeeper_bootstrap_log_level: INFO +lakekeeper_log_level: ERROR +lakekeeper_image: quay.io/lakekeeper/catalog:v0.11.2 lakekeeper_http_port: 8181 lakekeeper_working_dir: /var/lakekeeper diff --git a/infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py b/infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py index caefe0cf..e1251d1f 100644 --- a/infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py +++ b/infra/ansible/roles/lakekeeper/files/bootstrap-warehouse.py @@ -1,9 +1,11 @@ # /// script # requires-python = "==3.13.*" # dependencies = [ -# "boto3~=1.42.24", -# "click~=8.3.0", -# "requests~=2.32.5", +# "authlib>=1.6,<=2", +# "boto3>=1.42,<=2", +# "click>=8.3,<9", +# "python-keycloak>=7,<8", +# "requests>=2.32,<3", # ] # /// """Combined script to bootstrap a Lakeeper instance and create a named warehouse. @@ -12,15 +14,16 @@ """ from collections import namedtuple -import dataclasses import json from pathlib import Path import logging -from typing import Any, Dict, Callable, Iterable, Sequence +from typing import Any, Dict, Iterable, Sequence +from authlib.integrations.requests_client import OAuth2Session import boto3 import botocore.exceptions import click +from keycloak import KeycloakAdmin import requests # Prefix for environment variables mapped directly to class variables @@ -28,102 +31,21 @@ LOGGER_FILENAME = f"{Path(__file__).name}.log" LOGGER_FORMAT = "%(asctime)s|%(message)s" -KEYCLOAK_MACHINE_USER_PREFIX = "service-account-" OIDC_PREFIX = "oidc~" -REQUESTS_TIMEOUT_DEFAULT = 60.0 -REQUESTS_DEFAULT_KWARGS: Dict[str, Any] = { - "timeout": REQUESTS_TIMEOUT_DEFAULT, -} -@dataclasses.dataclass -class Keycloak: - """Encapsulate Keycloak as an OpenID provider - - Keycloak should be accessible at "{url}" and the admin api at "{url}/admin" - """ - - url: str - realm: str - - @property - def realm_url(self) -> str: - return self.url + f"/realms/{self.realm}" - - @property - def realm_admin_url(self) -> str: - return self.url + f"/admin/realms/{self.realm}" - - @property - def openid_config(self) -> Dict[str, Any]: - response = requests.get( - self.realm_url + "/.well-known/openid-configuration", - **REQUESTS_DEFAULT_KWARGS, - ) - response.raise_for_status() - return response.json() - - @property - def token_endpoint(self) -> str: - return self.openid_config["token_endpoint"] - - def oidc_ids(self, users: Iterable[str], access_token: str | None) -> Iterable[str]: - oidc_users = [] - for user in users: - response = _request_with_auth( - requests.get, - self.realm_admin_url + "/users", - access_token, - params={"username": user, "exact": True}, - ) - response.raise_for_status() - users_json = response.json() - if len(users_json) == 1: - oidc_users.append(OIDC_PREFIX + users_json[0]["id"]) - elif len(users_json) == 0: - raise RuntimeError(f"No user found for username={user}.") - else: - raise RuntimeError( - f"Multiple users ({len(users_json)}) found for username={user}." - ) - - return oidc_users - - def request_access_token( - self, client_id: str, client_secret: str, scope: str | None - ) -> str: - """Request and return an access token from the given ID provider""" - LOGGER.debug(f"Requesting access token from '{self.token_endpoint}'") - payload = { - "grant_type": "client_credentials", - "client_id": client_id, - "client_secret": client_secret, - } - if scope is not None: - payload["scope"] = scope - - response = requests.post( - self.token_endpoint, - headers={"Content-type": "application/x-www-form-urlencoded"}, - data=payload, - **REQUESTS_DEFAULT_KWARGS, - ) - response.raise_for_status() - return response.json()["access_token"] - - -@dataclasses.dataclass class LakekeeperRestV1: - url: str - access_token: str | None + def __init__(self, url: str, auth_session: OAuth2Session): + self._auth_session = auth_session + self._url = url @property def catalog_url(self) -> str: - return self.url + "/catalog/v1" + return self._url + "/catalog/v1" @property def management_url(self) -> str: - return self.url + "/management/v1" + return self._url + "/management/v1" def assign_permissions(self, oidc_ids: Iterable[str], entities: Dict[str, Any]): """Assign a list of grants to a user""" @@ -133,22 +55,20 @@ def assign_permissions(self, oidc_ids: Iterable[str], entities: Dict[str, Any]): self.assign_grants(oidc_id, entity, grants) def assign_grants(self, oidc_id: str, entity: str, grants: Iterable[str]): - response = _request_with_auth( - requests.get, + response = self._auth_session.get( self.management_url + f"/permissions/{entity}/assignments", - self.access_token, ) + response.raise_for_status() if any(map(lambda x: x["user"] == oidc_id, response.json()["assignments"])): LOGGER.debug( f"Grants already assigned for entity '{entity}'. Skipping assignment." ) else: - _request_with_auth( - requests.post, + response = self._auth_session.post( url=self.management_url + f"/permissions/{entity}/assignments", - access_token=self.access_token, json={"writes": [{"type": grant, "user": oidc_id} for grant in grants]}, ) + response.raise_for_status() def bootstrap(self): """Bootstrap the lakekeeper instance using the given access_token. @@ -159,88 +79,94 @@ def bootstrap(self): return LOGGER.info("Bootstrapping server.") - _request_with_auth( - requests.post, - url=self.management_url + "/bootstrap", - access_token=self.access_token, + self._auth_session.request( + "POST", + self.management_url + "/bootstrap", json={ "accept-terms-of-use": True, "is-operator": True, }, ) + LOGGER.info("Server bootstrapped successfully.") def is_bootstrapped(self) -> bool: - response = _request_with_auth( - requests.get, self.management_url + "/info", self.access_token - ) + response = self._auth_session.get(self.management_url + "/info") + response.raise_for_status() return response.json()["bootstrapped"] def rename_default_project(self, project_name: str): - response = _request_with_auth( - requests.post, - url=self.management_url + "/project/rename", - access_token=self.access_token, - json={"new-name": project_name}, + response = self._auth_session.get( + self.management_url + "/project", ) response.raise_for_status() + LOGGER.debug(f"Current project info: {response.json()}") + + if response.json()["project-name"] != project_name: + LOGGER.info(f"Renaming project to '{project_name}'") + response = self._auth_session.post( + self.management_url + "/project/rename", + json={"new-name": project_name}, + ) + response.raise_for_status() - def provision_user(self, access_token: str): + def provision_user(self): """Provision a user through the /catalog/v1/config endpoint User details are retrieved from the access token """ try: - _request_with_auth(requests.get, self.catalog_url + "/config", access_token) + response = self._auth_session.get(self.catalog_url + "/config") + response.raise_for_status() except requests.exceptions.HTTPError: # Do not check the response status. If the user does not exist then a 400 is returned # but the user is provisioned internally. pass - def warehouse_exists(self, warehouse_name: str) -> bool: - response = _request_with_auth( - requests.get, + def get_warehouse_id(self, warehouse_name: str) -> str | None: + response = self._auth_session.get( self.management_url + "/warehouse", - self.access_token, ) for warehouse in response.json()["warehouses"]: if warehouse["name"] == warehouse_name: - return True + return warehouse["id"] - return False + return None - def create_warehouse(self, warehouse_config: Dict[str, Any]): - """Create a warehouse in the server with the given profile. + def create_warehouse(self, warehouse_config: Dict[str, Any]) -> str: + """Create a warehouse in the server with the config. If the bucket does not exist it is created. """ - warehouse_name = warehouse_config["warehouse-name"] - if self.warehouse_exists(warehouse_name): + name = warehouse_config["warehouse-name"] + id = self.get_warehouse_id(name) + if id is not None: LOGGER.info( - f"Warehouse '{warehouse_name}' already exists. Skipping warehouse creation." + f"Warehouse '{name}' already exists. Skipping warehouse creation." ) - return + return id - LOGGER.info(f"Creating warehouse '{warehouse_name}'") + LOGGER.info(f"Creating warehouse '{name}'") _ensure_s3_bucket_exists( warehouse_config["storage-credential"], warehouse_config["storage-profile"] ) - _request_with_auth( - requests.post, + response = self._auth_session.request( + "POST", self.management_url + "/warehouse", - self.access_token, json=warehouse_config, ) - LOGGER.info(f"Warehouse '{warehouse_name}' created successfully.") + response.raise_for_status() + LOGGER.info(f"Warehouse '{name}' created successfully.") + return response.json()["id"] -Credential = namedtuple("Credential", ["client_id", "client_secret"]) +TwoTuple = namedtuple("TwoTuple", ["left", "right"]) -class CredentialsParamType(click.ParamType): - """Accepts a string in the form 'username:password' and splits it a 2-namedtuple (username, password)""" +class TwoTupleParamType(click.ParamType): + """Accepts a string in the form 'abcd:efgh' and splits it a 2-namedtuple (left, right)""" - name = "credential" + name = "two_tuple" def convert(self, value, param, ctx): if isinstance(value, tuple): @@ -248,30 +174,18 @@ def convert(self, value, param, ctx): try: left, right = value.split(":") - return Credential(left, right) + return TwoTuple(left, right) except ValueError: self.fail( - f"{value!r} is not a valid credential string in the form 'id:secret'", + f"{value!r} is not a valid credential string in the form 'abcd:efgh'", param, ctx, ) -def _request_with_auth( - method: Callable, url: str, access_token: str | None, **kwargs -) -> requests.Response: - """Make an authenticated request to the given url. - - A non-success response raises a requests.RequestException""" - headers = kwargs.pop("headers", {}) - headers.update({"Authorization": f"Bearer {access_token}"} if access_token else {}) - response = method(url, headers=headers, **REQUESTS_DEFAULT_KWARGS, **kwargs) - try: - response.raise_for_status() - except requests.exceptions.HTTPError: - LOGGER.debug(f"request failed: {response.content.decode('utf-8')}") - raise - return response +def oidc_user_id(kcadm: KeycloakAdmin, username: str) -> str: + """Return the ID of the user prefixed with the string Lakekeeper expects""" + return f"{OIDC_PREFIX}{kcadm.get_user_id(username)}" def _ensure_s3_bucket_exists( @@ -291,118 +205,134 @@ def _ensure_s3_bucket_exists( raise +def configure_logging(log_level: str): + this_file_dir = Path(__file__).parent + logging.basicConfig( + filename=this_file_dir / LOGGER_FILENAME, + format=LOGGER_FORMAT, + level=getattr(logging, log_level.upper()), + filemode="a", + encoding="utf-8", + force=True, + ) + stream_handler = logging.StreamHandler() + logging.getLogger().addHandler(stream_handler) + stream_handler.setFormatter(logging.Formatter(LOGGER_FORMAT)) + + +def create_oauth2_session( + keycloak_url: str, + keycloak_user_realm: str, + client_id: str, + client_secret: str, + scope: str, +) -> OAuth2Session: + client = OAuth2Session( + client_id=client_id, + client_secret=client_secret, + scope=f"openid {scope}", + token_endpoint=f"{keycloak_url}/realms/{keycloak_user_realm}/protocol/openid-connect/token", + ) + # Cache an access token + client.fetch_access_token() + return client + + @click.command() @click.argument("lakekeeper-url") +@click.option("--lakekeeper-project-name", required=True, help="Project name") +@click.option("--keycloak-url", required=True, help="Base endpoint of Keycloak") @click.option( - "--project-name", - help="Project name other than the default", + "--keycloak-user-realm", required=True, help="User realm for Lakekeeper users." +) +@click.option( + "--keycloak-admin-credentials", + type=TwoTupleParamType(), + required=True, + help="Credentials for a user who has admin access to the KC master realm", ) -@click.option("--keycloak-url", help="Base endpoint of Keycloak") -@click.option("--keycloak-realm", help="Realm name to retrieve access token") @click.option( "--bootstrap-credentials", - type=CredentialsParamType(), - help="IDP client id & client secret in the format client_id:client_secret", + type=TwoTupleParamType(), + required=True, + help="Credentials for user who will bootstrap lakekeeper", +) +@click.option( + "--token-scope", required=True, help="Additional scopes for required for token" ) -@click.option("--token-scope", help="Additional scopes for token") @click.option( "--server-admin", + required=True, multiple=True, help="Human users assigned as server/project admin for UI access. Value should be username. Options can be provided multiple times.", ) @click.option( - "--additional-user", - type=CredentialsParamType(), - multiple=True, - help="Credentials for an additional user. Registers them but does not set permissions.", + "--warehouse-json-file", required=True, help="JSON file for creating a warehouse" ) -@click.option("--warehouse-json-file", help="JSON file for creating a warehouse") @click.option("-l", "--log-level", default="INFO", show_default=True) def main( lakekeeper_url: str, - project_name: str, - keycloak_url: str | None, - keycloak_realm: str | None, - bootstrap_credentials: Credential | None, - token_scope: str | None, - server_admin: Sequence[str] | None, - additional_user: Sequence[Credential] | None, - warehouse_json_file: str | None, + lakekeeper_project_name: str, + keycloak_url: str, + keycloak_user_realm: str, + keycloak_admin_credentials: TwoTuple, + bootstrap_credentials: TwoTuple, + token_scope: str, + warehouse_json_file: str, + server_admin: Sequence[str], log_level: str, ): - """Bootstrap Lakekeeper and create a list of warehouses + """Bootstrap Lakekeeper and create a list of warehouses. lakekeeper_url: Lakekeeper api base endpoint """ - - this_file_dir = Path(__file__).parent - logging.basicConfig( - filename=this_file_dir / LOGGER_FILENAME, - format=LOGGER_FORMAT, - level=getattr(logging, log_level.upper()), - filemode="a", - encoding="utf-8", - force=True, - ) - stream_handler = logging.StreamHandler() - logging.getLogger().addHandler(stream_handler) - stream_handler.setFormatter(logging.Formatter(LOGGER_FORMAT)) - - identity_provider, access_token = None, None - idp_required_args = ( - "keycloak_url", - "keycloak_realm", - "bootstrap_credentials", - "token_scope", + configure_logging(log_level) + auth_client = create_oauth2_session( + keycloak_url, + keycloak_user_realm, + client_id=bootstrap_credentials.left, + client_secret=bootstrap_credentials.right, + scope=token_scope, ) - main_args = locals() - if all(map(lambda x: main_args[x] is not None, idp_required_args)): - LOGGER.debug("Creating identity provider.") - identity_provider = Keycloak(keycloak_url, keycloak_realm) # type: ignore - access_token = identity_provider.request_access_token( - bootstrap_credentials.client_id, # type: ignore - bootstrap_credentials.client_secret, # type: ignore - token_scope, # type: ignore - ) - else: - LOGGER.debug( - f"Skipping identity provider creation. Missing one of {idp_required_args}" - ) - - server = LakekeeperRestV1(lakekeeper_url, access_token) + server = LakekeeperRestV1(lakekeeper_url, auth_client) server.bootstrap() + server.rename_default_project(lakekeeper_project_name) - if project_name is not None: - server.rename_default_project(project_name) - - if server_admin is not None and identity_provider is not None: - # Server admins are human users and will be provisioned the first time they log in - server.assign_permissions( - identity_provider.oidc_ids(server_admin, server.access_token), - entities={"server": ["admin"]}, - ) - server.assign_permissions( - identity_provider.oidc_ids(server_admin, server.access_token), - entities={ - "server": ["admin"], - "project": ["project_admin"], - }, - ) + # Server/project permissions + kcadm = KeycloakAdmin( + server_url=keycloak_url.rstrip("/") + "/", + username=keycloak_admin_credentials.left, + password=keycloak_admin_credentials.right, + ) + kcadm.connection.refresh_token() # force token request to master realm before switching target + kcadm.change_current_realm(keycloak_user_realm) + # Server admins + server_admin_ids = [oidc_user_id(kcadm, username) for username in server_admin] + server.assign_permissions( + server_admin_ids, + entities={ + "server": ["admin"], + "project": ["project_admin"], + }, + ) - if additional_user is not None and identity_provider is not None: - for user in additional_user: - server.provision_user( - identity_provider.request_access_token( - user.client_id, user.client_secret, token_scope - ) + LOGGER.debug(f"Creating warehouse using file: {warehouse_json_file}") + with open(warehouse_json_file) as fp: + warehouse_json = json.load(fp) + # Permissions are not part of the spec to create the warehouse. + # Remove and deal with them separately + permissions = warehouse_json.pop("permissions", None) + warehouse_id = server.create_warehouse(warehouse_json) + + # Warehouse access + if permissions is not None: + for username, user_permissions in permissions.items(): + server.assign_grants( + oidc_user_id(kcadm, username), + f"warehouse/{warehouse_id}", + user_permissions, ) - if warehouse_json_file is not None: - LOGGER.debug(f"Creating warehouse using file: {warehouse_json_file}") - with open(warehouse_json_file) as fp: - warehouse_json = json.load(fp) - server.create_warehouse(warehouse_json) - if __name__ == "__main__": main() diff --git a/infra/ansible/roles/lakekeeper/tasks/bootstrap.yml b/infra/ansible/roles/lakekeeper/tasks/bootstrap.yml new file mode 100644 index 00000000..9178fb05 --- /dev/null +++ b/infra/ansible/roles/lakekeeper/tasks/bootstrap.yml @@ -0,0 +1,62 @@ +--- +- name: Ensure Lakekeeper working directory exists + become: true + ansible.builtin.file: + path: "{{ lakekeeper_working_dir }}" + state: directory + mode: "0755" + +- name: Copy bootstrap script + become: true + ansible.builtin.copy: + src: bootstrap-warehouse.py + dest: "{{ lakekeeper_working_dir }}/bootstrap-warehouse.py" + mode: "0755" + +- name: Generate warehouse templates + become: true + vars: + lakekeeper_bootstrap: + warehouse_permissions: + "service-account-trino": ["select"] + ansible.builtin.template: + src: bootstrap-warehouse.json.j2 + dest: "{{ lakekeeper_working_dir }}/bootstrap-warehouse-{{ warehouse.name }}.json" + mode: "0644" + loop: "{{ lakekeeper_catalog.warehouses | dict2items(key_name='name') }}" + loop_control: + loop_var: warehouse + register: warehouse_json_files + +- name: Bootstrap Lakekeeper + community.docker.docker_container: + name: "{{ lakekeeper_container_name }}_bootstrap" + image: ghcr.io/astral-sh/uv:python3.13-bookworm-slim + command: >- + uv run + /opt/work/bootstrap-warehouse.py + --lakekeeper-project-name "{{ lakekeeper_project_name }}" + --keycloak-url {{ keycloak_url }} + --keycloak-admin-credentials "{{ vault_keycloak_bootstrap_admin_user }}:{{ vault_keycloak_bootstrap_admin_passwd }}" + --keycloak-user-realm {{ keycloak_realm.name }} + --bootstrap-credentials {{ lakekeeper_bootstrap_credentials }} + --token-scope lakekeeper + --server-admin {{ lakekeeper_admin_user }} + --log-level {{ lakekeeper_bootstrap_log_level }} + --warehouse-json-file {{ warehouse_json_file | replace(lakekeeper_working_dir, '/opt/work') }} + http://localhost:{{ lakekeeper_http_port }} + state: started + cleanup: true + detach: false + restart_policy: "no" + recreate: true + env: + # + UV_LINK_MODE: copy + UV_PROJECT_ENVIRONMENT: /opt/uv-venv + network_mode: host + volumes: + - "{{ lakekeeper_working_dir }}:/opt/work" + loop: "{{ warehouse_json_files.results | map(attribute='dest') | list }}" + loop_control: + loop_var: warehouse_json_file diff --git a/infra/ansible/roles/lakekeeper/tasks/main.yml b/infra/ansible/roles/lakekeeper/tasks/main.yml index 57934ae9..076e7308 100644 --- a/infra/ansible/roles/lakekeeper/tasks/main.yml +++ b/infra/ansible/roles/lakekeeper/tasks/main.yml @@ -1,107 +1,18 @@ --- -- name: Stop Lakekeeper instance - community.docker.docker_container: - name: "{{ lakekeeper_container_name }}" - state: absent +- name: Set Lakekeeper shared facts + ansible.builtin.set_fact: + lakekeeper_shared_env: + LAKEKEEPER__PG_ENCRYPTION_KEY: "{{ lakekeeper_metadb_encryption_key }}" + LAKEKEEPER__PG_DATABASE_URL_READ: "postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" + LAKEKEEPER__PG_DATABASE_URL_WRITE: "postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" + LAKEKEEPER__OPENID_PROVIDER_URI: "{{ keycloak_realm_url }}" + LAKEKEEPER__OPENID_AUDIENCE: lakekeeper + LAKEKEEPER__AUTHZ_BACKEND: openfga + LAKEKEEPER__OPENFGA__ENDPOINT: http://localhost:8081 + RUST_LOG: "{{ lakekeeper_log_level }}" -- name: Run Lakekeeper migrations - community.docker.docker_container: - name: "{{ lakekeeper_container_name }}_migrate" - image: "{{ lakekeeper_image }}" - command: migrate - state: started - cleanup: true - detach: false - restart_policy: no - recreate: true - env: - # - LAKEKEEPER__PG_ENCRYPTION_KEY="{{ lakekeeper_metadb_encryption_key }}" - LAKEKEEPER__PG_DATABASE_URL_READ="postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" - LAKEKEEPER__PG_DATABASE_URL_WRITE="postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" - RUST_LOG="error" - network_mode: host - comparisons: - env: strict +- ansible.builtin.import_tasks: migrate.yml -- name: Start Lakekeeper container - community.docker.docker_container: - name: "{{ lakekeeper_container_name }}" - image: "{{ lakekeeper_image }}" - command: serve - state: started - cleanup: true - detach: true - restart_policy: unless-stopped - recreate: true - env: - # - LAKEKEEPER__PG_ENCRYPTION_KEY="{{ lakekeeper_metadb_encryption_key }}" - LAKEKEEPER__PG_DATABASE_URL_READ="postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" - LAKEKEEPER__PG_DATABASE_URL_WRITE="postgresql://{{ lakekeeper_metadb_user }}:{{ lakekeeper_metadb_passwd }}@{{ lakekeeper_metadb_host }}:5432/{{ lakekeeper_metadb_name }}" - LAKEKEEPER__OPENID_PROVIDER_URI="{{ keycloak_realm_url }}" - LAKEKEEPER__OPENID_AUDIENCE=lakekeeper - LAKEKEEPER__UI__OPENID_PROVIDER_URI="{{ keycloak_realm_url }}" - LAKEKEEPER__UI__OPENID_CLIENT_ID=lakekeeper-ui - LAKEKEEPER__UI__OPENID_SCOPE="lakekeeper openid profile email" - RUST_LOG="error" - network_mode: host - comparisons: - networks: strict - env: strict +- ansible.builtin.import_tasks: start.yml -- name: Ensure Lakekeeper working directory exists - become: true - ansible.builtin.file: - path: "{{ lakekeeper_working_dir }}" - state: directory - mode: "0755" - -- name: Copy bootstrap script - become: true - ansible.builtin.copy: - src: bootstrap-warehouse.py - dest: "{{ lakekeeper_working_dir }}/bootstrap-warehouse.py" - mode: "0755" - -- name: Generate warehouse templates - become: true - ansible.builtin.template: - src: bootstrap-warehouse.json.j2 - dest: "{{ lakekeeper_working_dir }}/bootstrap-warehouse-{{ warehouse.name }}.json" - mode: "0644" - loop: "{{ lakekeeper_catalog.warehouses | dict2items(key_name='name') }}" - loop_control: - loop_var: warehouse - register: warehouse_json_files - -- name: Bootstrap Lakekeeper - community.docker.docker_container: - name: "{{ lakekeeper_container_name }}_bootstrap" - image: ghcr.io/astral-sh/uv:python3.13-bookworm-slim - command: >- - uv run - /opt/work/bootstrap-warehouse.py - --project-name "{{ lakekeeper_project_name }}" - --keycloak-url {{ keycloak_url }} - --keycloak-realm {{ keycloak_realm.name }} - --bootstrap-credentials {{ lakekeeper_bootstrap_credentials }} - --token-scope lakekeeper - --log-level {{ lakekeeper_bootstrap_log_level }} - --warehouse-json-file {{ warehouse_json_file | replace(lakekeeper_working_dir, '/opt/work') }} - http://localhost:{{ lakekeeper_http_port }} - state: started - cleanup: true - detach: false - restart_policy: no - recreate: true - env: - # - UV_LINK_MODE=copy - UV_PROJECT_ENVIRONMENT=/opt/uv-venv - network_mode: host - volumes: - - "{{ lakekeeper_working_dir }}:/opt/work" - loop: "{{ warehouse_json_files.results | map(attribute='dest') | list }}" - loop_control: - loop_var: warehouse_json_file +- ansible.builtin.import_tasks: bootstrap.yml diff --git a/infra/ansible/roles/lakekeeper/tasks/migrate.yml b/infra/ansible/roles/lakekeeper/tasks/migrate.yml new file mode 100644 index 00000000..5cd2655e --- /dev/null +++ b/infra/ansible/roles/lakekeeper/tasks/migrate.yml @@ -0,0 +1,20 @@ +--- +- name: Stop Lakekeeper instance + community.docker.docker_container: + name: "{{ lakekeeper_container_name }}" + state: absent + +- name: Run Lakekeeper migrations + community.docker.docker_container: + name: "{{ lakekeeper_container_name }}_migrate" + image: "{{ lakekeeper_image }}" + command: migrate + state: started + cleanup: true + detach: false + restart_policy: no + recreate: true + env: "{{ lakekeeper_shared_env }}" + network_mode: host + comparisons: + env: strict diff --git a/infra/ansible/roles/lakekeeper/tasks/start.yml b/infra/ansible/roles/lakekeeper/tasks/start.yml new file mode 100644 index 00000000..f15ff28c --- /dev/null +++ b/infra/ansible/roles/lakekeeper/tasks/start.yml @@ -0,0 +1,25 @@ +--- +- name: Start Lakekeeper + community.docker.docker_container: + name: "{{ lakekeeper_container_name }}" + image: "{{ lakekeeper_image }}" + command: serve + state: healthy + cleanup: true + detach: true + restart_policy: unless-stopped + recreate: true + env: "{{ lakekeeper_shared_env | combine(_extra_env, recursive=true) }}" + network_mode: host + comparisons: + env: strict + healthcheck: + test: ["CMD", "/home/nonroot/lakekeeper", "healthcheck"] + interval: 10s + timeout: 30s + retries: 3 + vars: + _extra_env: + LAKEKEEPER__UI__OPENID_PROVIDER_URI: "{{ keycloak_realm_url }}" + LAKEKEEPER__UI__OPENID_CLIENT_ID: lakekeeper-api + LAKEKEEPER__UI__OPENID_SCOPE: "lakekeeper openid profile email" diff --git a/infra/ansible/roles/lakekeeper/templates/bootstrap-warehouse.json.j2 b/infra/ansible/roles/lakekeeper/templates/bootstrap-warehouse.json.j2 index 81ea74cc..4d316e91 100644 --- a/infra/ansible/roles/lakekeeper/templates/bootstrap-warehouse.json.j2 +++ b/infra/ansible/roles/lakekeeper/templates/bootstrap-warehouse.json.j2 @@ -18,5 +18,6 @@ }, "delete-profile": { "type": "hard" - } + }, + "permissions": {{ lakekeeper_bootstrap.warehouse_permissions | to_json }} } diff --git a/infra/ansible/roles/openfga/defaults/main.yml b/infra/ansible/roles/openfga/defaults/main.yml new file mode 100644 index 00000000..ff47d2be --- /dev/null +++ b/infra/ansible/roles/openfga/defaults/main.yml @@ -0,0 +1,16 @@ +--- +openfga_image: openfga/openfga:v1.11.2 +openfga_grpc_port: 8081 + +# database +openfga_database_engine: postgres +openfga_database_port: 5432 +openfga_database_max_open_conns: 50 +openfga_database_max_idle_conns: 25 +openfga_database_options: "" + +# general configuration +openfga_cache_controller_enabled: true +openfga_check_query_cache_enabled: true +openfga_check_iterator_cache_enabled: true +openfga_playground_enabled: false diff --git a/infra/ansible/roles/openfga/tasks/main.yml b/infra/ansible/roles/openfga/tasks/main.yml new file mode 100644 index 00000000..904e2328 --- /dev/null +++ b/infra/ansible/roles/openfga/tasks/main.yml @@ -0,0 +1,49 @@ +--- +- name: Stop OpenFGA instance + community.docker.docker_container: + name: openfga + state: absent + +- name: Run OpenFGA migrations + community.docker.docker_container: + name: openfga_migrate + image: "{{ openfga_image }}" + command: migrate + state: started + cleanup: true + detach: false + restart_policy: no + recreate: true + env: &openfga_env # + OPENFGA_DATASTORE_ENGINE: "{{ openfga_database_engine }}" + OPENFGA_DATASTORE_URI: "{{ openfga_database_engine }}://{{ openfga_database_user }}:{{ openfga_database_passwd }}@{{ openfga_database_host }}:{{ openfga_database_port }}/{{ openfga_database_name }}{{ openfga_database_options | default('?sslmode=disable') }}" + comparisons: + env: strict + +- name: Start OpenFGA container + community.docker.docker_container: + name: openfga + image: "{{ openfga_image }}" + command: run + state: started + cleanup: true + detach: true + restart_policy: unless-stopped + recreate: true + env: + <<: *openfga_env + OPENFGA_GRPC_ADDR: "0.0.0.0:{{ openfga_grpc_port }}" + OPENFGA_DATASTORE_MAX_OPEN_CONNS: "{{ openfga_database_max_open_conns |string }}" + OPENFGA_DATASTORE_MAX_IDLE_CONNS: "{{ openfga_database_max_idle_conns | string }}" + OPENFGA_CACHE_CONTROLLER_ENABLED: "{{ openfga_cache_controller_enabled | string }}" + OPENFGA_CHECK_QUERY_CACHE_ENABLED: "{{ openfga_check_query_cache_enabled | string }}" + OPENFGA_CHECK_ITERATOR_CACHE_ENABLED: "{{ openfga_check_iterator_cache_enabled |string }}" + OPENFGA_PLAYGROUND_ENABLED: "{{ openfga_playground_enabled |string }}" + network_mode: host + comparisons: + env: strict + healthcheck: + test: ["CMD", "/usr/local/bin/grpc_health_probe", "-addr=localhost:8081"] + interval: 10s + timeout: 30s + retries: 3 diff --git a/infra/ansible/roles/postgres/tasks/main.yml b/infra/ansible/roles/postgres/tasks/main.yml index b0a8685a..cc0adb25 100644 --- a/infra/ansible/roles/postgres/tasks/main.yml +++ b/infra/ansible/roles/postgres/tasks/main.yml @@ -17,13 +17,15 @@ state: directory owner: 26 group: 999 - mode: "u=rwx,g=rx,o=rx" + mode: "u=rwx,g=,o=" - name: Create secrets directory exists become: true ansible.builtin.file: path: /secrets state: directory + owner: 26 + group: 999 mode: "u=rwx,g=,o=" - name: Create postgres secrets diff --git a/infra/ansible/roles/trino/tasks/main.yml b/infra/ansible/roles/trino/tasks/main.yml index 0a1745a7..23a45c76 100644 --- a/infra/ansible/roles/trino/tasks/main.yml +++ b/infra/ansible/roles/trino/tasks/main.yml @@ -40,7 +40,7 @@ content: | connector.name=iceberg iceberg.catalog.type=rest - iceberg.rest-catalog.uri={{ lakekeeper_catalog.uri }} + iceberg.rest-catalog.uri={{ lakekeeper_catalog.catalog_uri }} iceberg.rest-catalog.warehouse={{ warehouse_name }} iceberg.rest-catalog.vended-credentials-enabled=false iceberg.rest-catalog.security=OAUTH2 diff --git a/infra/ansible/site.yml b/infra/ansible/site.yml index 2e1a92b8..120796ce 100644 --- a/infra/ansible/site.yml +++ b/infra/ansible/site.yml @@ -2,42 +2,50 @@ - name: Deploy traefik hosts: traefik roles: - - role: base + - base - role: docs become: true - - role: traefik + - traefik - name: Deploy data services (if required) hosts: datastore roles: - - role: base + - base - role: postgres + tags: [postgres] - role: minio + tags: [minio] - name: Deploy Keycloak hosts: keycloak roles: - - role: base + - base - role: keycloak + tags: [keycloak] - name: Deploy Lakekeeper hosts: lakekeeper roles: - - role: base + - base + - role: openfga + tags: [openfga] - role: lakekeeper + tags: [lakekeeper] - name: Deploy Trino hosts: trino roles: - base - - trino + - role: trino + tags: [trino] - name: Deploy Superset instances hosts: superset_farm roles: - base - redis - - superset_farm + - role: superset_farm + tags: [superset_farm] - name: Deploy ELT node hosts: elt @@ -46,3 +54,4 @@ - role: robertdebock.logrotate become: true - role: elt + tags: [elt] diff --git a/infra/local/docker-compose.yml b/infra/local/docker-compose.yml index e44c653a..b5c1699a 100644 --- a/infra/local/docker-compose.yml +++ b/infra/local/docker-compose.yml @@ -3,7 +3,7 @@ # image versions x-keycloak-image: &keycloak-image quay.io/keycloak/keycloak:26.3 -x-openfga-image: &openfga-image openfga/openfga:v1.10 +x-openfga-image: &openfga-image openfga/openfga:v1.11.2 x-lakekeeper-image: &lakekeeper-image quay.io/lakekeeper/catalog:v0.11.1 x-minio-image: &minio-image quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z x-postgres-image: &postgres-image postgres:16.9-bookworm @@ -297,13 +297,13 @@ services: - | for name in dev_isis_raw dev_isis_cleaned; do uv run /opt/work/bootstrap-warehouse.py \ - --project-name "$$KC_REALM_NAME" \ + --lakekeeper-project-name "$$KC_REALM_NAME" \ --keycloak-url "$$KEYCLOAK_URL_INTERNAL" \ - --keycloak-realm "$$KC_REALM_NAME" \ + --keycloak-admin-credentials "$$KC_BOOTSTRAP_ADMIN_USERNAME:$$KC_BOOTSTRAP_ADMIN_PASSWORD" \ + --keycloak-user-realm "$$KC_REALM_NAME" \ --bootstrap-credentials "machine-infra:s3cr3t" \ --token-scope lakekeeper \ --server-admin "$$ADP_SUPERUSER" \ - --additional-user trino:s3cr3t \ --log-level=DEBUG \ --warehouse-json-file "/opt/work/warehouses/bootstrap-$$name.json" \ "http://lakekeeper:8181" diff --git a/infra/local/keycloak/bootstrap.sh b/infra/local/keycloak/bootstrap.sh index c0693cdf..080f3c8f 100755 --- a/infra/local/keycloak/bootstrap.sh +++ b/infra/local/keycloak/bootstrap.sh @@ -144,37 +144,14 @@ $KC_ADM create clients \ #################### # Users #################### -uid_adp_superuser=$($KC_ADM create users \ +$KC_ADM create users \ --target-realm "$target_realm" \ --set username="$ADP_SUPERUSER" \ --set firstName=Super \ --set lastName=User \ --set email=adpsuperuser@dev.com \ - --set enabled=true \ - --id) + --set enabled=true $KC_ADM set-password \ --target-realm "$target_realm" \ --username "$ADP_SUPERUSER" \ --new-password "$ADP_SUPERUSER_PASS" - -#################### -# Roles/Groups -#################### -admin_role=adp_platform_admins -gid_admins=$(\ - $KC_ADM create groups \ - --target-realm "$target_realm" \ - --set name="$admin_role" \ - --id) -$KC_ADM update \ - --target-realm "$target_realm" \ - users/"$uid_adp_superuser"/groups/"$gid_admins" - -$KC_ADM create roles \ - --target-realm "$target_realm" \ - --set name="$admin_role" \ - --id -$KC_ADM add-roles \ - --target-realm "$target_realm" \ - --gname "$admin_role" \ - --rolename "$admin_role" diff --git a/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_cleaned.json b/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_cleaned.json index be11cfbb..604ce566 100644 --- a/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_cleaned.json +++ b/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_cleaned.json @@ -18,5 +18,10 @@ }, "delete-profile": { "type": "hard" + }, + "permissions": { + "service-account-trino": [ + "select" + ] } } diff --git a/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_raw.json b/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_raw.json index 7055b8c4..749934ea 100644 --- a/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_raw.json +++ b/infra/local/warehouses/lakekeeper/bootstrap-dev_isis_raw.json @@ -18,5 +18,10 @@ }, "delete-profile": { "type": "hard" + }, + "permissions": { + "service-account-trino": [ + "select" + ] } }