ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ# Copyright (C) 2018-2020 Canonical, Ltd. # Author: Mathieu Trudel-Lapierre # Author: Łukasz 'sil2100' Zemczak # Author: Lukas 'slyon' Märdian # Author: Simon Chopin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import tempfile import logging import ctypes import ctypes.util from ctypes import c_char_p, c_void_p, c_int, c_uint, c_size_t, c_ssize_t from typing import List, Union, IO class LibNetplanException(Exception): pass class _GError(ctypes.Structure): _fields_ = [("domain", ctypes.c_uint32), ("code", c_int), ("message", c_char_p)] class _netplan_state(ctypes.Structure): pass class _netplan_parser(ctypes.Structure): pass class _netplan_net_definition(ctypes.Structure): pass lib = ctypes.CDLL(ctypes.util.find_library('netplan')) _GErrorPP = ctypes.POINTER(ctypes.POINTER(_GError)) _NetplanParserP = ctypes.POINTER(_netplan_parser) _NetplanStateP = ctypes.POINTER(_netplan_state) _NetplanNetDefinitionP = ctypes.POINTER(_netplan_net_definition) lib.netplan_get_id_from_nm_filename.restype = ctypes.c_char_p def _string_realloc_call_no_error(function): size = 16 while size < 1073741824: # 1MB buffer = ctypes.create_string_buffer(size) code = function(buffer) if code == -2: size = size * 2 continue if code < 0: # pragma: nocover raise LibNetplanException("Unknown error: %d" % code) elif code == 0: return None # pragma: nocover as it's hard to trigger for now else: return buffer.value.decode('utf-8') raise LibNetplanException('Halting due to string buffer size > 1M') # pragma: nocover def _checked_lib_call(fn, *args): err = ctypes.POINTER(_GError)() ret = bool(fn(*args, ctypes.byref(err))) if not ret: raise LibNetplanException(err.contents.message.decode('utf-8')) class Parser: _abi_loaded = False @classmethod def _load_abi(cls): if cls._abi_loaded: return lib.netplan_parser_new.restype = _NetplanParserP lib.netplan_parser_clear.argtypes = [ctypes.POINTER(_NetplanParserP)] lib.netplan_parser_load_yaml.argtypes = [_NetplanParserP, c_char_p, _GErrorPP] lib.netplan_parser_load_yaml.restype = c_int lib.netplan_parser_load_yaml_from_fd.argtypes = [_NetplanParserP, c_int, _GErrorPP] lib.netplan_parser_load_yaml_from_fd.restype = c_int lib.netplan_parser_load_nullable_fields.argtypes = [_NetplanParserP, c_int, _GErrorPP] lib.netplan_parser_load_nullable_fields.restype = c_int lib.netplan_parser_load_nullable_overrides.argtypes =\ [_NetplanParserP, c_int, c_char_p, _GErrorPP] lib.netplan_parser_load_nullable_overrides.restype = c_int cls._abi_loaded = True def __init__(self): self._load_abi() self._ptr = lib.netplan_parser_new() def __del__(self): lib.netplan_parser_clear(ctypes.byref(self._ptr)) def load_yaml(self, input_file: Union[str, IO]): if isinstance(input_file, str): _checked_lib_call(lib.netplan_parser_load_yaml, self._ptr, input_file.encode('utf-8')) else: _checked_lib_call(lib.netplan_parser_load_yaml_from_fd, self._ptr, input_file.fileno()) def load_yaml_hierarchy(self, rootdir): _checked_lib_call(lib.netplan_parser_load_yaml_hierarchy, self._ptr, rootdir.encode('utf-8')) def load_nullable_fields(self, input_file: IO): _checked_lib_call(lib.netplan_parser_load_nullable_fields, self._ptr, input_file.fileno()) def load_nullable_overrides(self, input_file: IO, constraint: str): _checked_lib_call(lib.netplan_parser_load_nullable_overrides, self._ptr, input_file.fileno(), constraint.encode('utf-8')) class State: _abi_loaded = False @classmethod def _load_abi(cls): if cls._abi_loaded: return lib.netplan_state_new.restype = _NetplanStateP lib.netplan_state_clear.argtypes = [ctypes.POINTER(_NetplanStateP)] lib.netplan_state_import_parser_results.argtypes = [_NetplanStateP, _NetplanParserP, _GErrorPP] lib.netplan_state_import_parser_results.restype = c_int lib.netplan_state_get_netdefs_size.argtypes = [_NetplanStateP] lib.netplan_state_get_netdefs_size.restype = c_int lib.netplan_state_get_netdef.argtypes = [_NetplanStateP, c_char_p] lib.netplan_state_get_netdef.restype = _NetplanNetDefinitionP lib.netplan_state_write_yaml_file.argtypes = [_NetplanStateP, c_char_p, c_char_p, _GErrorPP] lib.netplan_state_write_yaml_file.restype = c_int lib.netplan_state_update_yaml_hierarchy.argtypes = [_NetplanStateP, c_char_p, c_char_p, _GErrorPP] lib.netplan_state_update_yaml_hierarchy.restype = c_int lib.netplan_state_dump_yaml.argtypes = [_NetplanStateP, c_int, _GErrorPP] lib.netplan_state_dump_yaml.restype = c_int lib.netplan_netdef_get_embedded_switch_mode.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_embedded_switch_mode.restype = c_char_p lib.netplan_netdef_get_delay_virtual_functions_rebind.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_delay_virtual_functions_rebind.restype = c_int lib.netplan_state_get_backend.argtypes = [_NetplanStateP] lib.netplan_state_get_backend.restype = c_int cls._abi_loaded = True def __init__(self): self._load_abi() self._ptr = lib.netplan_state_new() def __del__(self): lib.netplan_state_clear(ctypes.byref(self._ptr)) def import_parser_results(self, parser): _checked_lib_call(lib.netplan_state_import_parser_results, self._ptr, parser._ptr) def write_yaml_file(self, filename, rootdir): name = filename.encode('utf-8') if filename else None root = rootdir.encode('utf-8') if rootdir else None _checked_lib_call(lib.netplan_state_write_yaml_file, self._ptr, name, root) def update_yaml_hierarchy(self, default_filename, rootdir): name = default_filename.encode('utf-8') root = rootdir.encode('utf-8') if rootdir else None _checked_lib_call(lib.netplan_state_update_yaml_hierarchy, self._ptr, name, root) def dump_yaml(self, output_file): fd = output_file.fileno() _checked_lib_call(lib.netplan_state_dump_yaml, self._ptr, fd) def __len__(self): return lib.netplan_state_get_netdefs_size(self._ptr) def __getitem__(self, def_id): ptr = lib.netplan_state_get_netdef(self._ptr, def_id.encode('utf-8')) if not ptr: raise IndexError() return NetDefinition(self, ptr) @property def all_defs(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, None)) @property def ethernets(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "ethernets")) @property def modems(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "modems")) @property def wifis(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "wifis")) @property def vlans(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "vlans")) @property def bridges(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "bridges")) @property def bonds(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "bonds")) @property def tunnels(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "tunnels")) @property def vrfs(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "vrfs")) @property def ovs_ports(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "_ovs-ports")) @property def nm_devices(self): return dict((nd.id, nd) for nd in _NetdefIterator(self, "nm-devices")) @property def backend(self): return lib.netplan_backend_name(lib.netplan_state_get_backend(self._ptr)).decode('utf-8') def dump_to_logs(self): # Convoluted way to dump the parsed config to the logs... with tempfile.TemporaryFile() as tmp: self.dump_yaml(output_file=tmp) logging.debug("Merged config:\n{}".format(tmp.read())) class NetDefinition: _abi_loaded = False @classmethod def _load_abi(cls): if cls._abi_loaded: return lib.netplan_netdef_has_match.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_has_match.restype = c_int lib.netplan_netdef_get_id.argtypes = [_NetplanNetDefinitionP, c_char_p, c_size_t] lib.netplan_netdef_get_id.restype = c_ssize_t lib.netplan_netdef_get_filepath.argtypes = [_NetplanNetDefinitionP, c_char_p, c_size_t] lib.netplan_netdef_get_filepath.restype = c_ssize_t lib.netplan_netdef_get_backend.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_backend.restype = c_int lib.netplan_netdef_get_type.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_type.restype = c_int lib.netplan_netdef_get_set_name.argtypes = [_NetplanNetDefinitionP, c_char_p, c_size_t] lib.netplan_netdef_get_set_name.restype = c_ssize_t lib._netplan_netdef_get_critical.argtypes = [_NetplanNetDefinitionP] lib._netplan_netdef_get_critical.restype = c_int lib.netplan_netdef_get_sriov_link.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_sriov_link.restype = _NetplanNetDefinitionP lib.netplan_netdef_get_vlan_link.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_vlan_link.restype = _NetplanNetDefinitionP lib.netplan_netdef_get_bridge_link.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_bridge_link.restype = _NetplanNetDefinitionP lib.netplan_netdef_get_bond_link.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_bond_link.restype = _NetplanNetDefinitionP lib.netplan_netdef_get_peer_link.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_get_peer_link.restype = _NetplanNetDefinitionP lib._netplan_netdef_get_vlan_id.argtypes = [_NetplanNetDefinitionP] lib._netplan_netdef_get_vlan_id.restype = c_uint lib._netplan_netdef_get_sriov_vlan_filter.argtypes = [_NetplanNetDefinitionP] lib._netplan_netdef_get_sriov_vlan_filter.restype = c_int lib.netplan_netdef_match_interface.argtypes = [_NetplanNetDefinitionP] lib.netplan_netdef_match_interface.restype = c_int lib.netplan_backend_name.argtypes = [c_int] lib.netplan_backend_name.restype = c_char_p lib.netplan_def_type_name.argtypes = [c_int] lib.netplan_def_type_name.restype = c_char_p lib._netplan_state_get_vf_count_for_def.argtypes = [_NetplanStateP, _NetplanNetDefinitionP, _GErrorPP] lib._netplan_state_get_vf_count_for_def.restype = c_int lib._netplan_netdef_is_trivial_compound_itf.argtypes = [_NetplanNetDefinitionP] lib._netplan_netdef_is_trivial_compound_itf.restype = c_int cls._abi_loaded = True def __eq__(self, other): if not hasattr(other, '_ptr'): return False return ctypes.addressof(self._ptr.contents) == ctypes.addressof(other._ptr.contents) def __init__(self, np_state, ptr): self._load_abi() self._ptr = ptr # We hold on to this to avoid the underlying pointer being invalidated by # the GC invoking netplan_state_free self._parent = np_state @property def has_match(self): return bool(lib.netplan_netdef_has_match(self._ptr)) @property def set_name(self): return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_set_name(self._ptr, b, len(b))) @property def critical(self): return bool(lib._netplan_netdef_get_critical(self._ptr)) @property def sriov_link(self): link_ptr = lib.netplan_netdef_get_sriov_link(self._ptr) if link_ptr: return NetDefinition(self._parent, link_ptr) return None @property def vlan_link(self): link_ptr = lib.netplan_netdef_get_vlan_link(self._ptr) if link_ptr: return NetDefinition(self._parent, link_ptr) return None @property def bridge_link(self): link_ptr = lib.netplan_netdef_get_bridge_link(self._ptr) if link_ptr: return NetDefinition(self._parent, link_ptr) return None @property def bond_link(self): link_ptr = lib.netplan_netdef_get_bond_link(self._ptr) if link_ptr: return NetDefinition(self._parent, link_ptr) return None @property def peer_link(self): link_ptr = lib.netplan_netdef_get_peer_link(self._ptr) if link_ptr: return NetDefinition(self._parent, link_ptr) return None # pragma: nocover (ovs ports are always defined in pairs) @property def vlan_id(self): vlan_id = lib._netplan_netdef_get_vlan_id(self._ptr) # No easy way to get UINT_MAX besides this... if vlan_id == c_uint(-1).value: return None return vlan_id @property def has_sriov_vlan_filter(self): return bool(lib._netplan_netdef_get_sriov_vlan_filter(self._ptr)) @property def backend(self): return lib.netplan_backend_name(lib.netplan_netdef_get_backend(self._ptr)).decode('utf-8') @property def type(self): return lib.netplan_def_type_name(lib.netplan_netdef_get_type(self._ptr)).decode('utf-8') @property def id(self): return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_id(self._ptr, b, len(b))) @property def filepath(self): return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_filepath(self._ptr, b, len(b))) @property def embedded_switch_mode(self): mode = lib.netplan_netdef_get_embedded_switch_mode(self._ptr) return mode and mode.decode('utf-8') @property def delay_virtual_functions_rebind(self): return bool(lib.netplan_netdef_get_delay_virtual_functions_rebind(self._ptr)) def match_interface(self, itf_name=None, itf_driver=None, itf_mac=None): return bool(lib.netplan_netdef_match_interface( self._ptr, itf_name and itf_name.encode('utf-8'), itf_mac and itf_mac.encode('utf-8'), itf_driver and itf_driver.encode('utf-8'))) @property def vf_count(self): err = ctypes.POINTER(_GError)() count = lib._netplan_state_get_vf_count_for_def(self._parent._ptr, self._ptr, ctypes.byref(err)) if count < 0: raise LibNetplanException(err.contents.message.decode('utf-8')) return count @property def is_trivial_compound_itf(self): ''' Returns True if the interface is a compound interface (bond or bridge), and its configuration is trivial, without any variation from the defaults. ''' return bool(lib._netplan_netdef_is_trivial_compound_itf(self._ptr)) class _NetdefIterator: _abi_loaded = False @classmethod def _load_abi(cls): if cls._abi_loaded: return if not hasattr(lib, '_netplan_iter_defs_per_devtype_init'): # pragma: nocover (hard to unit-test against the WRONG lib) raise LibNetplanException(''' The current version of libnetplan does not allow iterating by devtype. Please ensure that both the netplan CLI package and its library are up to date. ''') lib._netplan_state_new_netdef_pertype_iter.argtypes = [_NetplanStateP, c_char_p] lib._netplan_state_new_netdef_pertype_iter.restype = c_void_p lib._netplan_iter_defs_per_devtype_next.argtypes = [c_void_p] lib._netplan_iter_defs_per_devtype_next.restype = _NetplanNetDefinitionP lib._netplan_iter_defs_per_devtype_free.argtypes = [c_void_p] lib._netplan_iter_defs_per_devtype_free.restype = None lib._netplan_netdef_id.argtypes = [c_void_p] lib._netplan_netdef_id.restype = c_char_p cls._abi_loaded = True def __init__(self, np_state, devtype): self._load_abi() # To keep things valid, keep a reference to the parent state self.np_state = np_state self.iterator = lib._netplan_state_new_netdef_pertype_iter(np_state._ptr, devtype and devtype.encode('utf-8')) def __del__(self): lib._netplan_iter_defs_per_devtype_free(self.iterator) def __iter__(self): return self def __next__(self): next_value = lib._netplan_iter_defs_per_devtype_next(self.iterator) if not next_value: raise StopIteration return NetDefinition(self.np_state, next_value) lib.netplan_util_create_yaml_patch.argtypes = [c_char_p, c_char_p, c_int, _GErrorPP] lib.netplan_util_create_yaml_patch.restype = c_int lib.netplan_util_dump_yaml_subtree.argtypes = [c_char_p, c_int, c_int, _GErrorPP] lib.netplan_util_dump_yaml_subtree.restype = c_int def create_yaml_patch(patch_object_path: List[str], patch_payload: str, patch_output): _checked_lib_call(lib.netplan_util_create_yaml_patch, '\t'.join(patch_object_path).encode('utf-8'), patch_payload.encode('utf-8'), patch_output.fileno()) def dump_yaml_subtree(prefix, input_file, output_file): _checked_lib_call(lib.netplan_util_dump_yaml_subtree, prefix.encode('utf-8'), input_file.fileno(), output_file.fileno())