From 69689462bc67f81efa34693b1eff2cdd05d89fd0 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Sat, 16 Mar 2024 20:54:33 -0300 Subject: [PATCH 01/16] Add model pkg group event module --- .../sagemaker-model-package-event/README.md | 55 ++++++ .../sagemaker-model-package-event/app.py | 26 +++ .../deployspec.yaml | 26 +++ .../docs/_static/.$architecture.drawio.bkp | 1 + .../docs/_static/architecture.drawio | 1 + .../docs/_static/architecture.drawio.png | Bin 0 -> 43830 bytes .../pyproject.toml | 41 ++++ .../requirements.txt | 6 + .../sagemaker_model_package_event/__init__.py | 0 .../sagemaker_model_package_event/settings.py | 73 ++++++++ .../sagemaker_model_package_event/stack.py | 176 ++++++++++++++++++ .../tests/__init__.py | 0 .../tests/test_app.py | 26 +++ .../tests/test_settings.py | 48 +++++ .../tests/test_stack.py | 28 +++ 15 files changed, 507 insertions(+) create mode 100644 modules/sagemaker/sagemaker-model-package-event/README.md create mode 100644 modules/sagemaker/sagemaker-model-package-event/app.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/deployspec.yaml create mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp create mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio create mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png create mode 100644 modules/sagemaker/sagemaker-model-package-event/pyproject.toml create mode 100644 modules/sagemaker/sagemaker-model-package-event/requirements.txt create mode 100644 modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/stack.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_app.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py diff --git a/modules/sagemaker/sagemaker-model-package-event/README.md b/modules/sagemaker/sagemaker-model-package-event/README.md new file mode 100644 index 00000000..363da67c --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/README.md @@ -0,0 +1,55 @@ +# SageMaker Model Package Event + +## Description + +This module creates a SageMaker Model Package Event Rule to send SageMaker Model Package Group state changes events to another EventBus and creates an IAM Role that can be assumed to get model package group metadata. + +It is usefull when you want to setup cross account integration with SageMaker model package groups. + +### Architecture + +![SageMaker Model Package Event Architecture](docs/_static/architecture.drawio.png "SageMaker Model Package Event Architecture") + +## Inputs/Outputs + +### Input Paramenters + +#### Required + +- `target_event_bus_name`: The event bus name in the target account to send events to (e.g. default). +- `target_account_id`: The target account id which shall receive events and must have access to model package group metadata (e.g. 11112222333344). +- `model_package_group_name`: SageMaker Package Group Name to setup event rules. + +#### Optional + +- `sagemaker_project_id`: SageMaker project ID. +- `sagemaker_project_name`: SageMaker project name. + +### Sample manifest declaration + +```yaml +name: sagemaker-model-package-event +path: modules/sagemaker/sagemaker-model-package-event +targetAccount: primary +parameters: + - name: model_package_group_name + value: mlops-model-xgboost + - name: target_account_id + value: 111222333444 + - name: target_event_bus_name + value: default +``` + +### Module Metadata Outputs + +- `RoleArn`: the IAM Role ARN to get model package group metadata. +- `RuleArn`: the EventBridge rule ARN. + +#### Output Example + +```json +{ + "RoleArn": "arn:aws:iam::111222333444:role/service-role/xxxxxxxxxx", + "RuleArn": "arn:aws:events:xxxxxxxx:111222333444:rule/xxxxxxxxxxx", +} +``` diff --git a/modules/sagemaker/sagemaker-model-package-event/app.py b/modules/sagemaker/sagemaker-model-package-event/app.py new file mode 100644 index 00000000..0473c5d7 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/app.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Create a Sagemaker Model Stack.""" + +import aws_cdk as cdk + +from sagemaker_model_package_event.settings import ApplicationSettings +from sagemaker_model_package_event.stack import SagemakerModelPackageEventStack + +# Load application settings from env vars. +app_settings = ApplicationSettings() + +env = cdk.Environment( + account=app_settings.default.account, + region=app_settings.default.region, +) + +app = cdk.App() + +stack = SagemakerModelPackageEventStack( + scope=app, + construct_id=app_settings.settings.app_prefix, + env=env, + **app_settings.parameters.model_dump(), +) + +app.synth() diff --git a/modules/sagemaker/sagemaker-model-package-event/deployspec.yaml b/modules/sagemaker/sagemaker-model-package-event/deployspec.yaml new file mode 100644 index 00000000..c0f572cc --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/deployspec.yaml @@ -0,0 +1,26 @@ +publishGenericEnvVariables: true +deploy: + phases: + install: + commands: + - env + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk deploy --require-approval never --progress events --app "python app.py" --outputs-file ./cdk-exports.json + # Export metadata + - seedfarmer metadata convert -f cdk-exports.json || true +destroy: + phases: + install: + commands: + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk destroy --force --app "python app.py" \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp b/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp new file mode 100644 index 00000000..cc2eb4b2 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio b/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio new file mode 100644 index 00000000..523275e3 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png b/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..0adf5e3d7e87db6cc20b2ca377fccc62a19667b7 GIT binary patch literal 43830 zcmdqIWmuG7*Eej7f&r2uB_Sa&%+QiEFwD^1H83<2#4s>`D8kT4iG(1HproXfgoKEM zv275FcqxCGp}SK4?29L{>^szA>af4;MH=FC-`r?Rmp#@pV>)$R;81oqDvHyGrI z^YG+`C~_-ib?=K5Rec= z6lD5Odz6D6_Fsg=gg^iRUJaD36V?;w;m8ev0@p~ar=2Ts1a1SrT6(~bG4L+}vJw%q zlHdbQ74djiJ0m+AH78GQFkB2GAp{WzZu6=k^flDE!7$+5)yc&UIH=g!y5LT`pxseK zAtxNr91Icy3qgQeFh_SB251Bq0fB`;LSi70n2?CL1n}g)CkPjj0v?Ho05|ne_9%C! z|M9-l$=i9N9R8U&(hlq-?rme|uC0qVa>KcZI=PAdGhr_~cMm6kTYq`?@3)_J_VmHq z{c{&>=jCJz3!vW-Ni}x{txJlV-NvJ@*6;VET9Yd53$^(pWmvq&Tlq6xXa19ty1nUGQsEA2v zcxl3PRegvEM;y*jMcvj8s_!AG4=_hu--TeR3qtA<;mRhSN+L!Mt^}kP@IV!WL%J&I zY3qoo8meGb)s(#@wB01t6!A_js&>joDk>;VZ4o0AeT=Iv7Hg-4R6(jqD8lS8QV0)Q zNjFz46KBtjV?LW06@7)Pv;v#Egtz$d7hjcL_lCge4Ioj^rf_bju>YhFMGI>mxnXdndkvL(A2W^ zGFAiz3|2H!))R99&g`Thk{C%H5ZqhCKnY-?l&LrdtsyFAVo2~ZL5RW)HMDhXJ@hpV zG)+;?o_6|%ie6}O91NvuoOsFshbnkmdqR0EIoF>-=x7`uW<`WP`4 zaVPXY6XH?&_wfjz=H~Vi>5KvxlOtqbneXP!~;QXH!)d zEKwWj<>e{?GJzSZ8F`xkvvX0A#HtZdh6W};CwH)&x}K&Q#MxBR$I~0+jqw2bssq9b zm-G;GS2uF@Fm}~acEllYV%|i8p(0jO8G&;I+JMCLl)P^D*+>U+B@0hf(NIFuU6ZKn1S6P0-9$`XZP03N;t&^{in|8MSW?g3NJkusfg6g6 zYH1qbP4UjI#x^8dLwBeS)@cRdjU?o%Os7KsG*-M0;Bo0?N>oq@{J*68N(-uqCGRH_S`N(Ov?f=%eQBqXTyo!y&;&+Q4%MQzu6f7!T-KxS^A?yS;{tPc z(8EW(G`NP>@- z5=a?O1bW$-xS&N~5M>kuttsjOvDeWw(h)e}E92(IG#B!arL64njur6Q(huPm+zl0?C5^z1-R4%+Jaa8nne zsf#C`2vYVW*rLE9Ha;+tl!S)1sgk!O5uzlC2k6_1Xye>ni3F0ECrk_kc0@}M@t#O6 zF)#@0CgDlcxApQ-l5oJ{o$dAXog@f=0fU6fiZYl^Y36+?((#j!XjM8(ZfMOoK~00n_e?eHFssvf2)rX)uxcL@h50f85_6_NB7 zclLJl5EBLLEL2KV95{ivDdD}H#S}fe#QcxQL41{R6L>jQJs_OSIrW3>so#uBc;xZ&z<&Z0;Odv6I_kPQi@CvJn0v3u55p`FCOB$ z(ex12#cF9wK)gl8+?)`mA`(t`ccL3XS;9+7kqD9^X=_7Cj&`CtN(h`Tp!lJV+9YRn z2uaaF!xQhW;-d*Csd%AueSjwtqF7)h!|ioF)E%%=>KdL{RaY;2K)b@-MZn^Kar2Zk z)I~U=@glaGNH1L*A3PeP=jGucrKWajYp{4turWzQRnHJux=Dp*H^OuSgN+}T-Ysr#z;?dVe=8& zPz8>Rn-uL7LE31>NC&HH7Qm%81@DCmXO+pS)U@aRwqYOT%;*G-zJ7G>wDsv@S|)y$ zzsGY|A6q|tbhp*IBUIm?X7??co1 z!J^^Cg(bi|-iOBapMQQACd*qO4W|a3c8kh-y)1Bn78vxi>yj6NLz+#B{yn;LpCeA` zI3#?83_gh~w52`ENh*|qWiJ-)pK@s9pVnvLn-$$I4lKRb{cIv_#NDelVHI=lIla?3ie(nU!t zAc^WrJ~Fudj|;`LQ?AByIr{RiH7)1k^L_8SW$n%g=4qv>*1y+z;+u)?4;K-cWpW!1W4{UK=Ay0^FleT>&77rIm1f4$U z=>q!VVFsphJlI%V*Yi6*M7~QYK@+Ifrp_y0U5M=!$x?wOfsSDpDr}`FjoO;iUQc65V!XcJ^Z>Gq1simB;&^nlVtk ziq?y`&~J?x0sQJ5llqH=v%OBXXsMOOF392o^!<;|g~-2HOh?nQHW`GTpgX}X8=>s8 zo{P=*6}n$?Z8f4C_gZuBNX`R%>Cy~1Nk*IPp%vh>-SQL0&prZuTYBJmE> zzm6_<9;kUyt$t%#pZ!36j&wZ3JznX^J0fUabqP1w2-}2L>bzBC9wfXDl-hibYHve% zpB$gxzrp-;lR-G!vAKS6pqMF+8yOVxG~K#$u?t+a<)oYC@ME|$MyS$)d%W(1g0#){ z82dBTc4AN}YcUfVT+UmXx638OFa9je8y-btQm;ZY_Fc|m-l_CqQQ=}=VQenT@ca|y z?fXnkE@pZ(pA}Pu{L3&semSbK=F4t}=gC^{*#cOh3opNf5lz_Fgs(UKIRELpPJHR+ z)1!foVU3kc!^*4P2is|%AZ2$K{Py2WWkD9yS-7=gw#a(K8vw>Lp?w0eWRaT)`!(8DKW3(+fnb;EJAnL4DA=y z5DtWX$$Rte>}1GUi1>eozL~1O#TTGT{>1bJ$s4y5&3Vx58z##jexfoL)N>O$ly2{LetIiv$*BUGkaNoV{A|s$c1`?H66o zwFgwyYZI-yPxwCFgg@%bI6c^>c!=D#|O%Zz&7E zHUVcHW$|C@6yI5ufKntM*`efL8{U8N0#It2U#{DH{UK!jc!y0cL*okx|JqP-^(ijt zA|q4Wr(LBM{JrkTFiUcD+8e_%D(NbxKr!Yjn~g{xo4G5-vF?>Ox!t)qG)PbUq{r$A z!7&_X9oECB ziv_8Rw)c8l;vITLY!tMdLgEjX|1fV6*Fr0#!Z~zvLLDbdtIk+8+TWrTNHFRoZ6D^^ z^z9`WH#~IVculXdJku9@Yi8c*g8gfxh3^f;7m{Utc_#io%eJF|M?n>e1B;J0zPGLK{2Df771DRZ zUO0@WI^WtTdAIc`n|zkSQp$#c>NhqCpG28e5zhyh&(O%1(kj0+hN(wCBds=344E<4Dr zx+8N4Kjb>oAe7i|wIO|(un5^{cVwWq8)$K)m?r$^_KJ2HjLT;{uEI(8Zo(4+Y}}00 zS&_^EB`ZW+Z@@3Vr&o%my}}@5pJxic1Kdr#{iH2J6bvaI;;kk%aa=mNt6 zs^~i>lrG|~DqS`g6%zCBdT2zt1phd0KuftKXo(#b;d-z0>f}69A5UP~EG4xXs!jQu zLD0OmKl3mfbIJ_q!7DZmbydx~QwuY{%i^v5Ue0d}G4onfJ`SRw7qYaE$$Xv-V{3RAO!j6-cSZ9LRP()oQuV z9I>v2f=Ily`PDjdyn1K(J zT~Brsk~iJ950yE%;yBjIV0FWxmpemgx-y;4msFByC|J%s^g_!s&TsIkt3qWD)|5>I z=H5M6bL+Xj&N@|rkuVfYB5yS%yeHdv;CCj^N4@~Dd>s)|s0y49PFj*G$2&QJkQv%!_j*8SZHw_0HIp3&pz^Pu19+9HdsW zjN8LY{qDkg#LJpYHlBajuB4X{l5ZprU`xxPH)67i#3?2ARf9fhe(TW<5NVBAW}Z$y~tpSE~zc)eSJ!fOW2 z=e>5XC62KcGc)tAt9zdCRL-KO=y{WArsgegQjG4PnjSw(bepLQr!eB%ixKWt9&`**0v@uFlhg%xkemE&(3*Ns-s4Y>`~xRP{t~&Ow@DQ+Rnta;%=(Nm7h$+E^WmB_y0eJZ{KZj! z%S!FFCmedk_coRueYj_r8zJNM(G@qQS5Icr@3}o3ck`lbe}V1gh&KVkma#EE#>5QnlJ zWpHULl5J(FJE6T1c4KJiOKy9)0drg3fDFt9jD|aJk}I`)i^4;hMcrxA>ea;TWe+6 zFIK1mGWcXgVl5^}#;g~)*@Eyg{e1R$rX=gQg7meM7j^7D=kGflO9^vnq`Iv6%6S4G zQH;4#xm9|I3)~TEw(P3hrr6i1Z5EAspcqHx@#7PJm+&xB1dg+^kBFtH#lEtO+fyQs zr?9u)`!m5G;eP1zJ1K~zmZ#%S{kxtAUGbE&)1LFf=8gP0immbv&F=SKNLkXr8K391 zjK^`jtaqm`2ue>g%{I*)U=;Z*a6WFueCr`XG-}!Kc@YcMV^<4Vt>GILEv3%X%ryO- z66#5P;I5Q&o4zag>84R%)k3GM-C$f?t)xA*A%)`!DNqg0C~WA~6M7ckZ0F#C#7c#a z9JO@(D?DXhJQI#~lQRZMAWU(P%~#K#5ypT^J@V^kneC3;`l}tR$*G~ zKIyHI2@K}SLF;8JtlAgW-+L@w2Ye~SF$Wec zaa2e!i=~u7NGWM>IE_F8-El+W2pW5vHy_eBR_<=6} z7&~Ki75lp3;4!n;!181qWM0t`-x)gEnivG!UHMutR}Kr3`s{ zMTqj3snwqBOHnDgsm>UQ`?!fqm9PC7EUyGsSrWP!E<`di$y$$NizuiLvi^J-hoC)Q z8OC2PUwuE2VQ7F%pKbWPwd7&`j>Ixc{u4WuBqmE(@VjqPYxxUR%(lT6_VM09rYwco zRMWU&d4cVv%ugd?@>Pltj7rrtY77L0_ZZV73?FsN4YMXJ=z#e`YOmZkH(cIdx!EO~ z;Xmi!b9g@bNf>@OI817_8pD`??u{JJWsUFm{qCsNczH)&lSvlAO0(+UdlVM!x_J!i zXmhd83{i7mXmn9Y&!jIZ4@s7lg=FQ+tCLMz)x%NSyzw@#-rs*gwGzjR@qG z?3uui$;tVLpVmzLIAOz?aTE0fBc245>R0HzT^=ulzOwJ!N}q2Qv@PnEnr6v*LFqNJ z&}@73e9`S{y1qJ8s91(()3n~LvYFfWUbY;%J72K-7O;!3`8CWaxu3pLA%Xn{e=%Ct zs5aUkJz*R8XmVruk4wsXq}f5M*_=L2aC~=a)Y8xWj(~uIMwkj-D`=10C+OnR5Zw=~$ zw^nY_GVRkT5*ncF);B>m=&dcp4W{MTSv{7MD&a`EpJM^WtouvzZZCyL{^(zkxe&Qn zaJTh+>b;?I=zAyX(+*Svbe-D0k_Ksy{(;!2Mr(D7_%4{AJIe`&_dcRyT2@33iG)9* zmNh^-(pnf;ox+YWJ4`9p-Ufsqdw!L~{U8FU;yw$ETM>Bl`|N4I8!`U_ZgcL03|{va zALnTJlN|OjQp5lN6dz|q{e|VY67p9gSabAQI4KzW^=dPd zNWYz5a?%@e^9Kh1>@I9%x{(c6W z@jXQXpWdZ_qdyE+8H6I~1Pm|WSI&NUaSE`i*mr;Vl2AFrevgE2gZ=daWN)54BuMaq3O?yCWPZ4v`&QiMKxioN5%44BzxX4Vzbrdww z;LGsyYqiSCa8Vg?&C+$Uo=GoHJvN>AbDWM_Em0{6gaY|)yZY^IC@+`(>V7U<=Dlu% z?oPa>_FPCgl2HUd*GcWP^6{AfuTk+G9`&S0i~ZTD;en^4ME&QtY&oN3J@`sT(rqTl zJBUv&Pibwkp}*e8RO{4pZ zWx1-)uFiKqXUGCrNS`k2OFlZ-nFZ$Anf9RCI{r-Mu@)3jcxd5f}FDxqbP{<>OtOoDwMN^WvBiBkdxj=p}qhH8%5YPw{dI`)}7NMRR6x_;GnqG`&In`_5#@UC%AYjpvio zufGIzTMhdX7G9PRvN_yB5)l)P!38;^g$4o~_c29JqhdQ3i((n~%z#hswl*otg@{p* zktx2&D*w=YcwgFUm3Pvn%;rO1o(A&^86O(0bQ#5PT0XR__6;U?!8laEZAUbRMv5p+ z^!)9+B@+LGhfhdp8Lws)Ss-7JnNHWqXYsn``X;U_7g)#4@SsVc(pKPJOg-*~n&jX; z??tIevl{i{qqRLsj+RJ)g{61H5~EY~K2H#_cc#6F%Ts|J$TOnX#Bz0am)C4SLc0d zsYKc4%5{nQy!>?>&ENHFqSm9ln2`f|LCu%=Yl3=4HC?85{sAfO2CK|X;Wd?y9Z%V~ zQxm35;tIw-oMO#YV;fr)OtdSKb{V$)8;d0lbW}I7Hag1JT!(hetlIM z9vPY5_|I?)G_{A;GlKe`Zhbf%@*#E5kl0!B8RV*8w`St@%t%SF=i7TH+6R97a#LBH zte6R&pyz~++msvw5i$nv3jMU8TSe;WGA3~bN%!A+{Lq{#teaRnrQe+TmmRr(=?6Zw z+DDzP`2CAcgrX)+yrhL@_pxHr%}jy{GIWPt@FeLQOSfvO zW0EtPi%zvismQ7Jc%qt?kvFS&kpc1#xs=B>anr&S#_}Q%DV`s(-|{N6so8dJgXccB zsLS2vDVAT53Yai6+q!W125Tgcb?AcoB=Q>+TK^oc%=^=oeBsncwcDKgFxxz8y8=HQR$4|l>gi)1)-&m&n}=$SjkxU(8uUBoah;yU5g z+(M zQo3?MjOB)_%we`aCn>ij=UC*t(0xcoXUtdjid#0}^$oZY4>aRYb!#m~c&<_D7q?#( zuIbKij77~*;IuIfSJ;4Mos1XeaCaRVK4ID%smtn5KM{UPpZVYP4FLM)pIoQ@exh4@ z*6acR`FpK+N~PVLu-G3{x4dFy)9aF4qe5SNSoxzK`xaf{`Ya=)gLAB6L~6Z=etf~# z1vP%jkF1V-G-0Y3nA?FI$T4y*iJ{#a=VEQ?Azr&wZ3CEu=Jr#QK(#8ZXwh+=nGG^7 zk!2K4)uSN8*ac*p(e9L8VVON|9(?K^O&FP0Pv%bDt?jam_s31UOSPE^_)%U8sQ$rn zDSOFFQvcKiwLEof?5#(1q<@`2tp6^A6TcdLN+8GK9=bg^1~oox`nllkYYhNeMpFIf zD}?IPSviqI`<>!H3nmQH6O{{@_?5}qLnDCx@;IM#y0jh}s4RKE=fIs(yEIW>qoj7M zOs3LskJ8@!yqH|(SYjGJm63R-ibbx#Xm@k6g-&;5yI%Jt`ui8jf7b#iERZE0vxLhysg1fMnLqotjvUmTESOFL@YhNhygH6L&)cfA2dew_;Xvc>2B$pt+TpDCV zF!B-=DvtIio&BS@IRtt-p1bl5n=_%oa`|A#Xl9F4TLLF>DStiRSSev?zC5W_te2j- zLDf@(dKR7fILnO*&RR~(bh|^s*lL;$-FXzWHF$^R30zltDl7g2WzI#w2q> zpfjr>X=MEEm*bOcE=Nl7u;-IJBMeJYstZmPj`St{0=L9cKFoTKcWrb;GXtvN9qO+t z_=jK>KuhsKQspum8bN{6NaHqRbG7=^4SF@gyt0X091Ok3_$iM^A;d-8Cs|Nfp}B7? zX}~Q_A=$VG5YFB{rSDIk@Sfal{r)ITAgUi=DFpi;exsfWfLrQIrWqyyLhcarAKq_X zw03M}0baTYbQ=G#_W*`K!=U32P-FlIqlFclI!oqVnBKIfDe6??D*n~zh0!aVe8o56a{oh_9h!7M`r4jtaDfm0+_FlPhm7IHiL{`_ekqz39B=rr@`?144IlyhoV3zlo-fPfHipjK9%6maUJ9r<5q z3Vb?c0OJ3`fJGy4z5kBf)#rrPf8if$Q}D=_PbDvaxypYE#^F(SsfB9CJY&dUq<| zghORIJkSNmg-=iB{vo0z?LM&sS^KsP|BKamk^$4;OWVI9_bUrYskdmxKg%Dtt@3fs zA85UlLjg!WqZCRy(MMDHvupC5&(jrBlx|BKVv%h9!p6OCaZdmN_KW{8_4k{e6lQx) zG{uMsjr`SX(YVsa7Wq#6+A-(iSipsMmAGg(y&vDde;%Ts<4^dm-U}jpoI~6c7D9yH zAHhw+*TV_1LK>=r#g_d-Ouxng7`uOHRDK!)>Na9UhTg#q|e~$vh=27ygwmCNF zUu)V^S2>(_dhkoY%hsdIPqoWj-9pl*C#K*LRAiG8 z%p^x!mBS{uE$SBY2mWEj@j*Ty$#@Jp>-@WoD-+_3R*Q&9;z(4f?QK(SAAketXW-0FA_cW|6ikSf@ zeR4CN680BMrLwB`3tqk0Z8m&0wQ?C@8*FuL5;o9^uE>+g-xrmf|Cm~ zA0lD}&BSAGal)y*NhF)O&RAwj$|Tq^{KK*pO;YEIIM3(&d0%vQy9tXvt| z0tli)Pl_0ebMhG4IN#|qmsaA1XCD(|#FJMCYBY%IFF~~25%d)?sK8w}onp8xP#Hyg z^1zq%El<>$E1z_d2EBT;eWe{J#64eWJ5H7{Xdc%xWkSicZ_4R;F9Q`T_}x(}D0xdA zGA}6jbFSwnU3r7=+T%BNJZf>BJ9T{@Tu$BP4A7~r2Q=KNnCuAW_&@);`EDy-!Ys@n(!IYzAwwJwrO@skZ3B|Od9Wb9) zmJQN|Z;T%UOv(b>N5`@9+!+0++hTnMdiQ7rO=;y%4&Qnw-e$UfM^d}^U@8A)sn<`_ zxlhTrrdxu~cZnGlo4+zDN%wxkrD&;TlWR_@a*epusGbbw)qfRJA>y-vUJ^dgDj(dJ zCM@gUk@GXAI0!g7U;~`>ARv5uVD2FrPt{7k!f82~N5>jRmHliu?SLIfb;N-MqJhIalrK zIF=)JyGpTTcb%(Z|NAs2$I`YSGqOgkhT+s>GyJ#W;DP1N9tW4p)&8|WK5SzA8YN*Y2OAZ%|WCkR>6Za=( zz5a!igx&}APPuqjA9q)z9U#aEz?JrXIE)$q1U{UI4CL{N4Xq=r@T`OZSWjHjmW{`c z&j5aLBO#|ldo52R_4luD5B9!EtABi`$J3w|9RRS{-2#y6pU$aPU45h*!=L51ZHL|o zj@CYmt*#ozjB~7bOqeA#>0i4NANQW8_jlVI(l0HKx8xAE? zN!a(MvosxUM9l95UZey-np*&{l+zB%fs?r^fh)7l-(znbB)xxUa4kCqXX=o=ZUZUb zrJ}ePQsX^<;Zqh0Eq z>VUXRH^O$40HZW_KR5El8S0jh>aRCjt46RB@SITUoAQ=-PF5o0k>aPGPS0QO|2iSC zE}HoiVG$=@=pv&lvEQUQ+93{~GXNk@(q|(lW*XOY#Dmfy-)7RB8)xZ?R^VA#%({ePDidme>=}X>+48c}PX)Z>%;8wmWD z$2Y!szBLK}OP_0V-$El~0G^*68UFlIUKl-Cg)!rxr_ulMIn9pw671#HO+ZcZqjTtJ zB11w#g7|*=khgAr`}VCrSB;^|#?F^2GwuwN9Jds1qDBW1wc@jNSKe=zeI?kO`EaGA z34gfjHdt(7w%?6T|5itgaOezT8|L71FN*7uri#r|PaYT;87$CKE5F&G9>-s8q5sCX zAGPqtsx_xL;Nap@>f3;Vy7@Pr|8KNB;|7($1{*8{i2e_Yhk@Cne}-Pjp(FE~BrR@m z1}k-F3&z`5Ky_z#%v;f@cHWU&W8y3D2zrhE4rwlDvTjj#gYS;QIa}S{m64Kp>FwTk zC0iHvq`X!~zmxrGz$Ilyv`ojHpWaQ1oieWfFF5gEjQjtFtNj0Ntg!18RE_->_r=Bg zkwmZUP-l$F@KA}Bm1f0sm{j2f;lF9A&i_;10JZ_W{9u=!y3Q7eaVa-m%r>uePn@0i zcD>VUXu$3LH+`h|HvkmjAw4hm45|7apce%eD$%gz# zOoa*By1sXA7RW^)HX`|(8UE&=RR2e8_gaCob(op{LZ^CA$ByvAwHUGiCs2bhG~O+r0>i4Qtena-TbUhEh}D_|2a^ML2Gv7t%{uG6gx~v^iayERPT(K zb?_s>(<*n(3n~|01X59wB9%rF)IUFK&4H%-#LYN@D9*apk**JYLDPZJVQhT-hE|fn za3+u0!6P%{-@Vi!9uvQROz3LS1uNiH6k<{Mkw@R99_F*R9(usp)!Tkg^=Qwk`@{-b z+Hf?OuKX~4C>%fIuy;}=aAMr}I#%exRUXYSzI)r>2fl<%b$?8{90hrOOE>TbCIgO% zdUO|-d!w29><{+Vu~uq+s3guMcx>jL;ux*G?QSL}$-JCi`j3 zm=T%M8lm4D`s6Eqed#=IBL)3Ag2!azq6&+)ts#Xs4A^&|8~Q!;kzNdH(ga4$?`n*5 zc{nzE@8XsI*}=2we0rBzssxog%s+_iEv|LmmM?h2lwMDS9p{BJG5Av^w0^jEL#aJc zEns%Afq8OHtS5!7r+lIM_4(fPCIujqa#OJI#J=Lv<%@F#qCkP})$6b!vG$RYsqg*q z`G^;whcV5{v6;mMde6+M&H_x5fsonJqoB!*-tEUCr>RIyX5k`9Esz5B{s>zSHl<#>UVJ zP}7%LQRX+Pk2`W4sJeVC<4v1t=*j zbX(UM1g#>H)79`cQ#@a;4d)7sXy=)7I-R_a9tvfUs5(4Zdi!HH7s0*PghNZ(52zpR zEZrgzF)rbzLS_xr{vnadacx)DJMwbrlk0X^l)9rE<~MWoe6LmJhEV1An1rf5FF_rK zKAc2vPUq;nE|#ot5d{3B$JMy0>FP1cQr{_#I}^40Au_K&5==AnR(RM8F6p0cLHIc0 z8dy=Fm(k>_(iTp4i$T~zIknh7R@%<}Q;O)_jCVHGEXw4^SDTz=Vs7HEfr?=*McSnC(G;=4k$^z&nzz3$4um>opK< zLuUf3WeeId`7LPM{J?0gw4&1l(fH-#pVVrddK-)TDYE@Dgg(xcr7}i>vp#e}vaHOd z_u(gt(mTM$0j0C~8}g!0F`rAuDiW6za`IV;R?$-x)N&*y{$_&VabIQbq&McHx04 zrvV98ovdS))=;a_Mh#~Ru0CYy{G5c0`F0Fm^2p^5vQO7bEP+Vhn)rLkeMKNAG=E5;0x+Dj(LRU29|9^sX3=Tw z(6-51w?UVw#^5zO27BWFaw~RLz@q-9=F!@Ho~Nvi$--tYFs&^DBbeqs5k0(h2R~3L z+!eS3lqr5#zV`LPWt-39ze+VkEv4)vSd?+(2Vx5Zp#0#uKk zgPVHy`6H$(_j5s}^Zd?xY3&tC^ZVW#TTIUSLJ{?~<+IOMP42T!196^ChNp(7;}8W< z+5Ntz>urqmyw>8EY*}DmNvKfM_2$mUzpQsX0IW)%-|XBhc}Mxrei5~pD!Rf^mmwtj z#e1r!A0+pF*vokDB4l7B0#{uwt6n|g9ZZ;GmU=cnOi_JLPdDVJGursI=|%mbk*k8Y zhv)~s0$U?e0OaQQS6U2M8C1K3K(}-BwGn@W(9e`i$6#qF z4-kG*mZeMnk>_XyHZ5^zt@Aq?=imOA$Qx*~nK(1FG6Kdyc!gkUwkDjyhQx{!h;`4pcJ(fmdj>?B$u^pLL7hideQ;B?EiD5^YDw zAFPuS(x*F4j$w7!BT&GC_grt<`6ntGE%HBvvQ+X7Yy5u*z5Bz&GwA?SK(y&Ow6Vjt z7TFj45H>ZV)SugW-u<5bzMXck@w*vnad5czHdl4AFVpR?)Po2_*cX zXn~O)D}UZ+(?<-#R>;oeoTIrhk4VNoJ#?v7iDgqW5I#~I4^D@19;h8*o34KVDni#i z_C>>~&JS%Bwm-RjPE(OAmA`e_icYOO)vTx>NaE>KW4Lnu^(#v~1;eyGBi@~BFjf6c zFhu~iIbvr`TgqRYRwI`6h*%UmbxS!adh^DbKha{vT)9u0D1zJE1a=O+`BtHk{%PrP zsc69(dD7x*C@Pbv()*~L%-TD)$zaz|a#FeuJ)^-~V?q(d)!J*YT-BB?*v*Za0T94W zz4}7NB7MHNu(GdZSPoSKCw8)gc@4f(=T#u%Pnu`TSC|HF;Rl7HzJCxH!S4tB&~Eh9 z;gC;EOe1j`eqyAzMfRc-f6%y^L99CQgWoNomQ}%M1g^xto6IQd#^Y6N#dRs*duLdA z)rT1Fu9zTQkd+XJJUz|gT=HhyYx}0BKgS+1_Ge#_d*&>`0G9Dv6<#kqnW)mOPY@Wv zcGfVR14`fDK1J!SJaoTSsrJ>L*OIGA#=8$T(|Sd=|N6Is2;yjkqQ>8X_xX6gcCEB0 zWI`dO_|~V>eOpuQ*JAh3Ql48}G(4dp7uILOpSM30ej#@D=}Q?p|0G$voWWT}NyE5I z&KF^+1gEc+CEh{VA>P3$KqT)m@)6uykj2c^Iwc{b2o#=yfI{Rua$YpA>D8Y*KQEb7 z0II`FabK_Aw`}~Z0!DLX+s#V5vk>C@DSIL+9AXk27zaO9ENdySpPFcYUHQYEdC2d1 zN*U)Wh5Ty~=roYU>V-vIZ#3qpn$IWN~{J1Um=N2KKI0X5&Aiz46ad{`#(1>EO5@1l1{js-Ky&|qZ%4k*R% znTpJ)8*?z_A<_p?bzHDwr*ix?bu(XnlUliA%ci9w3(^w+NTLBxGWaGFI+&TgCp?9>EHZSg_e)goDg}#yetuu8Ihx@k zKPw0WmaFx2*!e|+igOQ}Jn1{I1E~j9;sfGVZgZ+L&!TF7w>=c?jAe^@-kaFV)l#*m zYMhsne(q)a(^bnKn0?BG;>Y&aT&}FgRyalu>R)jn)@#zOZ~e+gI)AqpJ-Ggrn`yu{ zBc%PpwKxuB8N5}zGmY~#TB96*$FJUClZEeB6g0+7L$5TA(??M;8D@*vcSg@Od2cJN zM~Z>pjMT@+5RXg^{vYn%GAye0?H9fcqy`X>7?AFUp<5Wb8ze=B?rtz(q#Hz#ZYe>! zrID_oK|s2@VXyK2KYQ9PMa1Bd%}5@r8I*_gX|_JL z+Ji;e&g(LjQ`7D_4ub0G=d2BM@ z{6fWnmkAPmIZ3PtLIuYeHoYbLmB2_XrIRPgBo+2tqEh%v(+gn_r7cTfQFOH4UK8Ec zl1&Ypn;z6rW8_OKB5WQpC4m55`TnL&9SDz{hLWjD`K_uuX?@($R|9EW;WD=T){dV+ zSHIo&mlTtQJihl{U#t*(S^XjFX*%YH!x<)llaU8(Ev?*Hn~n|Hr)0WZX&jSIJ^upB zD|MdzVGnd2r&o%z1f|-TdO3H3OpfkX10f+O?)qli@6!Z)Rv}|u$|0xD_;2@?dVs|T zg%_%K+<4NFRErd?W@Nr}3Uz42>O)xS9kQ=F^0;#q8gJ=F#9BZ9d^3utpF99e^H&m| zh~tpQSM&Rz@hGt%9J<#N$>XeNtBh2G(DC)#^_o$m@XVw?b}ViGuMzRrj@y%YSR|9B z>;`;w^mXoMBIpD*u*>s31EAetbp4ZcNWl5+>)sgS&p95Y_*R=_b_-XZyv(`R*}Id- z0^jUUj7qlqKZ};WJY4QfJvj#XGeCVPXo-B}LH3Y{!dR-Q$6H*=Bx`5FtsL4-&$k=3 zC5EUIN1CCS6NMVsnwCRE!-=rV%Og#1PCX${2mtW%Y33wJo*J#0A92*j?Is29gg9qE zB_1h`_wsd5^~6(+4x*z(nd(v zulIl7npK{oOXxf6{Debc))s<(_?7ROJRaQ(i1}NLz8J{f^1zwUPP@_?^_mTh8Jwpw zUtTpHojpF^Hw@Krn0unTLvY{=TYL$=ah;zwxtH9oDC&5mtzA(YzB zWinTGx-2`ZJG(_!$nOg=3D=cr7ut){Zl+OOkPnfCmC){Yssl zGwA(lys&7C%KnmZG&DO?#6DxW=!pSLv zphmvUs&ueIv|&Z=OeXw!jz^5yTwb*LOtCH=*6XeSH?haWH%lsC4B-v`5_6=FSlHa- zl9JO{*ZC*zA84=gtVeB0ahNB+d2WRN&oSP(i1v2TgbfbkZkOSrN|QKwexT*m>sX>V zmdUU?CkB;;)jf=GRqgCci*T7q$;LkRrhTD!e0O;NgrsKQ0^RzAsz52U-FPO~KBZR- zN`LYE0)@M0bzo_O7V_#q#5XCTGVxSb=$F#r5i#H?_^s!%+jny%XOEb3Q6ypETp{CB@VY@Vb# zUX9(IM?U+hTs(7dFbM20g=k_)V2Y}M2BO5fy(Ex^|BS_$NKX3z;*Q=6u02>%)OaMy zc)zDr$4bAqz??z)pn+B&Um!D#72d^?`?>c3$D}a?bcEl(an*DFy6iyhJ5T;?{X%L} zRPe8N4|bRtD**plp7r|phn-F1z}c$Dp#X9L+aguQ3RvIlnFTNkJMv=*Em!^wKZXAr z+e*Fo{`y`c-mmLeP5YPCichu*4@uxm^+}yh1*%17;w}bGqA@cmFU8|kO4eM<|MY(@ zkEM0~a4@^p@f7NJ+6%O*ZfKN!8{xi}mzl^pe(Uh#9O-c8IhML-ehON68C%b_cbjAn+ zKH_Wh)|KwiHDzq*`4eCFBsLPeXyZahpa$cJR7A<0#qsr=q#n4bwg@a7zD z`Tq&p!pqL7ZF8^D#?0@#PGlYent`oySF>@E@^8P0dBy_!Li@xofFJ|(WQPsFp`gy& zuE#?L(O}uLwqkzA(q7C>!>X4e*n8dKp0x7ic)WgIS6>;tZLa(-XHovhr$6%!P{4|Y z2LA49;sLOFGGsgShQ2`DCu`=u!E@Yht5&6w~~?cl2NOHOE6uPsTG3J(x6q4u;`tZ z`-{hA?~w0J?V>YSl4yeV$;X&)IXX^NyzOYaPLtBB-Mu(S!V>8mu$@73L?Hp^lOhN> zjGIntx-IXrC4D~NFQlw=$A6L|;l>s!KYSiU-@yAbk|8>l$AZ<(csVpxmBRn*1#ju~ zd97`z!lv=q&MP}$H{mB3Ku~$lf1_B_(iK;F(A@lKa;|Yb2!$+#E3mc9;dNZr6uGr7 z3m^v8pFFyMSaSGHexT(tTV9XvS=^=z1*7t391fm~*{WY?B#!0)qiWON7@vON21>S1 z9M6dyslX8Nc3(pt=@qr!_`ZV~1OQQU8A#!@wq9_sc&%A$_;iA$pqFz*q=V<9a9?r6 z?MZB*>=yIp>)D;B1pga(;ry*XB1O9e$BgiTVbDdT@h87xpu|0d z1a@hfF{#?(O|lCxqk_hb6_$-Ii!Fo8=BucoY$JK@lYfSp;)1#h*?}M5e<4B-Of;$W zlbR?$+#BS3|0&^$O&A57;_sS*lM`3?+==YzK+6(x6pHb0h5wPZY_HR}t#y>*N80oO z>-Kj4+wp&f>B%N}pA8vazU$JtE7GNkqlU6Kpy==w&G1{kVydx`rTQI#mh9j9MHv|C z!&GpsvRY+1%pg3nQDdr%F&%bRZK8fN`!<%2`rQ7kcXvtFGn@%lXVt-CoqW8b8^QWu zSn=;}L*XMEC5%U;z`Dw_bOqMg!?AEstBc`B1@#y~d%e?_H`Bu4Sqn9*Kbwp{R!{>d z7H9~EZf_o8@;6n*{D?8lV|wpC#sCfX|5s z?M)vUbe~4-NyB%(^c`r+1?|SO080DJ^6a$PpchZPqFi^YbfcJ(p(xX{$!ABf@4NqA z{^HSg>4xn`dM!1W)#DIxM#(0& zUtok6G1vn?DNtwhCW&Cv9A{_RZ}fPyP+R&m(x9Wnn=4;QSqkSM<;KO!mf3KZl+m&q zC@rof1Y@4B5KsBO z{;hg?R@Cb>A1{9R?hGwF;{bpQ8M4(Z;hj0EC7;uI`?CmooT?KoHC--;QZ=lpB&#^2 zExr6Uu;nWm5uiz)6zqC8pzxBu?>26K`pF-S6Ny*P6-_y|{63Dn2q6*%t#o12xtGmf zzSDJV36b6UJeF?^dMR>6h&dHN({Zhxon<dyu2i6NL@OX0ILn%l4C1<7`RV zjG5b94Ib%mMv@QhM2j@Dfcd7@^4kYO4_-s05Ecd3>lf3A|X`pQ^R`jY*;t9V#*anh=~qj}Z<3k41!6KWZt+l#R)e~0nwQS+zl_dZI=55W!qKQNCU=Gf{Liu2 zjmAaPOM^MAv}g!PCL`=+;w4nz)KF5${7hZHr|D?i3B8_(OxYC|t_+Jusmk`F>={)nM6}T8;MiOBwh8-`X|h&M1wW4n(VNngOcYi7Xq(Egyf%^xQ1u@d*D| z0;b7!eb~wOh=Wbn>AW;q8)Jcq>aqfpYeyh5Wr%2w?J#*xR|CpoQ#}QGyiks1p;Lt_ z94Rp!ZQN%kN#bp{1Y$9!Y(DVVk@^STg*4Wf{vFoQJK0vU6ANE1bD!9(VHeW+SkUUW z!`v5UW=Iv_EEX4SiD;n{DgRV!!jc6{$siNGG3jqVmM>;kKjt)r-{5V%CbwPaIgaOs z%M-9KSs;Xzwr&0j3;urnuGl)8uazLNfpCR^Z@6^4kaH}r8LivCuQv96&E3M{#v1qd zo5kZ#)7LkepU!@mhXr+4uV!m572~B%&i+W8s`GfsQ$)sb_|I`q5&`&H5FY3?OgyqoqOf*lISAS!3$t-(<(dk1L9(!RyR&4Mda-{Je_@zJ2%l!;Ja zc&_Mxz|k=?nu3GX$5SZ+H{Nl1@eU_x2Xj3%Y%vWl$8g~XW3n(a$yC%Q75}o|lc}nU zWy2{aXW}9H@WL%c{G3`Y4I&f#CsA@k3w9}2+To=`9Todzp`voqi zwVuMR`0dLMhJ{lZmyR3yo@P`EE^lDTAc`2H)kFS+mbmn*jO8h(qhkX_G36Si*x)}U zYqP$ooIBru%K%RacxBFbd)zeP_1R+zJ^7T&134DgyI z*Lu@)9gXaf^jqi+5mgtU(LxQ{X|s$rMYKLI>N$4S))uIKelD(~Q8$gsS+|_sI}*(w zN5Ouf=hh<(){iB{w@8^(WiMfDpo?TjBnw-fb6Cc#o$WAT0F5O`)a2 z6qxl#KKDb{tsctkQ@xJ=RGMEYz(wtQNAmsgOvkU$?>O5teXYOKL+7v#^n2rP&~iRS zGcM*NU23>2di95LA;#sQMQOA-X?!P+8>TMpAe;?8NjsVLANN@rQ4m7{_H!ILCvCv} zT=y*Gsjkll*Dlv@4)GsX9uEY8NNFX{2h>MeHoQ4pFdrqbfJs|DZqPfrjk*wN(LuER ziD8lXSa?}79pg!dj60q-X<7k0qMOtz=>beripdEuJ#k#2Q;~zxmh(oLeBfC>2>|sf z?x?jG@r?=L=aSv~D@Nu^kX?6=!9u_2Y_p<&pfL$5_dWn_O?hKAuj(agDc6*rHmM3#yj zm=?fP2}6}ZE92;Xl|jxl(sCnc*l@hwxFerV){yaIVoF4Q&nLhFXwkA)S!|Azi$|V% z2|GDb#=l9P&;A7Z!u88%wm+~`^_wZ0j&Edqj)Ag4AkT_&iuW9|p&xeqtgz59!5NT_ ze(_A?dhJRy|M)?Wd60x&=Db9Gv}|YQ;G0y`OOkTqJD}v>VS1KrS7`jeG2gs#=j3>A zaYKd@tCY&CV5eK=4zo+M^BYw zKTv}!hcSq5%C}sQLBqAjr|YEy*NvsqFCbDn+!dRtGm1}Xn>*eRs>d>;bcJtx9J z@t2IB+kxw(8)ch9+UC<`vLFP0(PzN&w|Iz#4yl_?M}mtb5D&-4H1%|}JVUb}jCRVT>GYQkrGA_C&99q{-Pbue-m9Hf zb5{rD1iMoVxJYvcgf*Pt*)ld6aK`-x@I9KAV0qi25=KJPse1L#-Y!V8`8y=Qu*k_o&A>nVXtpR+$#!oDOexC#4lbylyrl|UKKN@K?Z`Zq8B2I6Ahf!Lv z3oJZ}k8F7QZlR`Sad_rOEGRz9zGs|pc*OXoq(iI@8RKFPb1wFlNe0b-?zMkGTbE;k zvrt3d0fd?~d!1q$x-7Hd9~vDEsB9g?yk8B#%nK)8x90-)LjQBd88AU!`2jUOyaPu~ zQO^k>6LLo^U_Rw2eO~V<&&$^tnA=uEJdY!UD~s(uQ%a0B{t)w= zAO|0JlEg6vuD)OB%jt=um*X4|n8YwY+Tm_aC5mEb5rC2I1&wBm^+l>!nBeA=3k|6ct@-x#p+ESAK{C)j`)ESn>A| zv%(3pqrU#}vJ>jQ3CRy)-e!-AP60Msp%NiO=06MT&84q@l)pKjXNkf=JtN9c3#3*F zw_qBnln!fq(o120U6Y>0n}0rpGI_I(?u*Xjm&Ji;vizsFe;m)Ign#{JRpcSYDmdJK z+7OwZh`Ti=yNs61eCF~Unk=v6i5rtbz26`ll!r4ZgKgFDvwIJBBBmW|FV!Jc{69|_ zFZsfeag)4Fd!K+9-NaKAb9KN~qUa)1tltaX+?GZdP2KJ_^WV&w(|FU0 zQ>dX&U~wvVYa3zyR$qDQ07n)2 zjH}zsiwYDYg!oA$@b@GbI!g~3QtqfR`DLbmWwW|$sI-8Cv^9$|Aojius zmdB(|+xX&s4*)?GPfr~)(@-!llts}Sv*T}gVMevZMFl6_y81?MU!Fw+$#t$`r&ZgB zPW(Cau{;A_Z+zWW$~~K|HdI^u8}MND5p~<8fYl&|;mAfk-{_Wk6m>4mmVbh_TIZ`I~($#f^8G!mn>kw(J zuG26DW~7+Ow(m7L&cGS-Y$HbXu;IljA!ddMbmuOFR;-*ix2Lj4eeR8_B-Srv(&k}^ zG)@v%QCj{d96;`_Po>ztQ)3@ovc*->*Bm%-3#0Omy}aaiy`?=S-Pxprmb=}eEb>i9 zv#sXebhX~+{p0fo2o-Yvd;@qg;i_9GR0aolBFwGuVVrM;n@6ZdN%<4$N~6626P4;5 zQLByVSYq{D??FPJq+={CF>z1nm9{tFHgVnl+TSx3^(kQ*;5cLhB%*?NY3Ck3qJi&wRMV*?n(9J` zx!Q5i?}43LVC?J+I`u956hP}8Au6e3%bsbtN*ZC}mi!_D9acX%CFaY!=#h+$b_env z$Jhb2VnUrsabAhW{PwQ<+q%c>VW7K!1EU5Ptc7b(hxkU*5SfLwR;>^)4HXUyij&$< zD&AM>0T_f^;{uDjV%~5D`r%~DSW+epDJ@1L5L@bmG|*&LD|Ys+(`Jd@PW(k;V^BPw zX-mcXZQDM?IAoMh#8-VePBNjq{uSr*(RIsbwZ^-Z=(C+Ms|DZ*ce> zldHwwJLf|JhJ_`RM>4M|X8HCcg%DGTs66;%E6<+4iud6~cG93&n^Yb0?pzbrsFVByXx>KrBg2Q6$>GmOD)zpxqhc)Bpmk zOxM$`>o5?O5)S|9Nplb=aqPy-YNBR@JK4{xHii~j{4VR1(!v;UB($vYG@XzI(UK6ytw`SsRyF;S>;%M@g^UngKNCZ$B)$ZmS z9AH0-OX-Us1*z@;!LZXAy!6np3a@;rNjmYEZnVXm20bed^dNMHvpz^{xpkYpX3J%9 z%=&e<73GPnk`#c%6?3)WX0_BTu0X&MM8|?qTv%L~&DP8EI}VgR1{#|hSv(}w`(fxb z*Q-Fwc=`ULY@PQF`#FUW9Dwb5GZ159E%B=#$ZGTv-=x~wN+ ziRZF0u+8sucVq+XkfD{~l|FZpL1N9=BS>oZ>0y)Qma>f>jGF_1%((w`Z*-zul+t(W z#X>#>t#6;qeEEPe8!NoU60OGinB}0Qb!uQ{gqSO{pcfeHz_jpY|2cFP9t4tCLgeW! z`)w{}(T(L^ZwUh{S#N|Yf%!K8YCBV}tASXwkjR2VZ(Xg0=I@9I>sd1OU{o4}6o5VG zm^Sr~q4!lsAJ$ru)JM^$= z$4!AhL$L&V2E1{$08UEqr)cti_j3fORMu1x?pwnuyApKN#XX>(`Ep*kwmT*AUotPq zMJm0%k$|H}5ky~(le$afX=3<2J+`_Rd~skR5`s^(0ck!pHUchQ@bASdEVcN5iqqa! zZ0~<(oYo1Q1Nv{L;Kyh9%Z|BMkek`9m#vfJ<^abIn zn1R6}?v0PMP{@3ZD@V=lw@^XxUZh-%*fFmE`i zHG3Y6OK4yfQt=o6*(XDsz z25>nnx@tW@s<0IKINq!d+lmbFaA$vmf%`=Zs7*h3SxuEvV3Ub5wT^~=6>|6aQioWTv9g5x>Th%vO!F z?-i=-yl9Ntx3&-&=AHsurf;+yYzVb3UF`S{o*DK(7;fAi{8*ZP1r1WNq_j?{mi_Z% zbF8I$c6nR5wZIkLujBtAfd;?{Jb%-jtG9dQEiQENGS{P1!~2^BFR%1B-|l0C?T2zI z#=e-LJvF@h!$BJu4!XQlcuSiV(L`aE|46<$K3`b)qCr^=;W$xjtz@lSLYX1H?dL(+ z(8e&I7dbL9yowf+oR8Ia;r&J8s;g%+m=zlvCTe*aDAj}Wq#j$LpBJ124+C3^jNnyy zX&xJ+N$x4f5bYq)Af-G-uiNSGa>dVTe}+Er`?H!fxPe>m0*J3p)|3r}t60Ycf4Xk4 zXUZ*q0D#k>+0fkhd=)_3BUZY?Xm3b+Y^fQjJM2x$yX#LjCb>dbzi^X|XH1}OpWGkR zcA)ho5tyPJyUt@4DyhXUpN8hBm3u^XVpvUA=3HlIp}d);9$*uKUG~YIs8@QqKyo>% zu$CM&8Y>z+LfYPKmKl!Atd*!NvDfi9kfB?sp49x;EP#!gn~e3wzJGG#8|zACRGE(# zSpzC{hRr?%i4=}OpNh34SD#f^xC|C-zpJj!8GcGH38z=9D!>j0X<|R!-6{=i#?Xhb zY9F(^n^3ynYGIQLMd(LTPki7Tw8aPSKb%ooSxyfvi0Aw4;FUc?JIm4crv-92A&yi?!f@ojK!eixTzK`rOjKd;JP9OU&KxoQ>*xzLVqR&*J&P) zs>}|?L`eYceNdEam#)^)D3W}QGJJ0m2u;i1i`#2E(erC^9E<#q+vG*8#Z|FcdKRL(TbjT zQkA(1c`D8W-|sJvQzLE4(mXz>d$$~Lo|)(ioO?|3f2h zzVN-t_NgUhN0{0@&RO6d-bd&)la^*PJ;7;K0c- zmDze!lHtHga#88}X$4*gStB8M7KNyIiP8_@I2{7S1aFUatqV0~28`+?;e4B4x=>na zo=?E^I9XquWjK`YBj%3A$=&4=(4u?4p9U&sx|aW(Zdf*}UYHU3Ds)jDntXUnYQr}bxapmaK7)Zx_^t$XB)Doxyd}fGoMV!s_n>UOPTNlfoC5P?W{-YGDPd7 z>Xt~NCcCZ;EKlW|#b%K?37+0iY*Sm#UP^Od-5!jeZI4vBf6haH69iJuB=#Z$Hua#N zu*o<*_YME_E-&%nUv8d>qWM6o$h)dMR!4g|o9^Sczh=3Y;@N-CGSXX>!G0V&dvqD^ zRikJ!V)Rifz3`k9V%f5=VX7&_k}xX>ZP-ia8yO+Bde0~?eD`WqGKa#)hTeQs^oh-5 zS>ffkgHo(lPKg+$a?6t2n}BibrkB(f3Iat+fYqd-aL~^`eet8^CgK1_6nkciz)vr= zxGa^fky4Nw-zV%=djEkqgzct)%zHo`9VT+StUoEcAAX0S5DgbzVG;@XxZF!hHq4(f z!jh8+P0N=?3ONITT)}M%4`MkKfzP1QyUCfzj0dUG#SZ8H@Tv{>IRxZIFkktEhxseS zJWRj4{_cEkwARrCOojnobTJNnEJ0cL{5izU!<~VQKp()_;ye>dj5XaR4h^Dwu7!pM z7HVvWj{;5ub4&I~!vN<;#mb>QmI!_qljT~RDiiE|vaY@1{KLE-0HRkZdPs|j`KMv( z95`d8$7Y^SFeokcVx^ukh8=iPK4wXPxNCg$sp=LsRit<hIfT+cJ&NPk0(@!AH1>a=NCzBMoA!ODFD^>NZ<$vG z@ynFv`V>9+n&v^EE8c>5U4VK+X^mbrm){gm-Gso{R{xElfB!rfM28v#dXoMGI2-eG z$R9JhxD+%w=)}{3ycOV;sE_~qD`}u&Wza^Rp zh$@MI!L2`u!1?RKaw7opU@+ptmjG$H01iC`OYDTyE0S#bH|zvcTQGs=$%vkcEiJJ1 z$J>(axJP;Vd!|@RwWjtT<}Yd^v~2+72`hO)WveYfE#%w z1AjF92#`bqfE$1$DnHGovU$9&;hd%dfAZei|7EI5Vk}>8xnNpTTr3rZZfrtq)}MC~ z|G&B@Ed4$JE8*?XS3&0cTr94wR#Y|t36j5T>;0E)0B?}I2KZX{Ebk96yn5J<$w3Kh zffIAReVQJ4@7;8Ny+bVMLJI&p29M^*nvG;iNQ5eLqLu=;>;(DumI1*=iUuGCWfCl} z>s`$478(cukdKaP+o{8hhg(#1baVi36Ayrv#H*@%>iQcg_6+|Kdq9AIE5HLJgrq9Y z7EvP6pTy=2;3GPN3$#&KD1c#Osz3=}bO88euVjKH(lD64073Ye^sgY`0ww{F9&|vsv3%rh-57J)GgVQjI{wKA z#E!!6UjcvrPb4-eze5B7McJ>P^~}NH#U!8~N2&kn2Ow$xOQ|R+(QZG!1H|*u;Qz(v zuw3AO(f+$`qdbzJ#6sHyboA`MTabUP0UFw42;|fj+s@{Sx{|Oz+6A8mf6rmA1`9wX zJuRkC;iZRdPNe)OS~8$9`8k^CTo~w9p)C5M_BIkp!_i+F7KL=_Yq_W`lCzXjx;0zr6iUlH|s@NAxqyiV@>l7sf<5PbG@N=(Pa$% zE~D-LOAsIBNQmh%lFx#F`Alh6EehZW*zdPRBo&`abG8%TqBfPd6=5e0Rrft$wS2~G`~!CcV5EXFV4bQy4mh5OYdUz*RMsV# zO_oFy)OfB9pp>vIfZN9fV?fFvr7Cq@*Vd`V+OYRLJC93D}O|U_;#6*R?Vg}KxnBK z5a!GZQoIT^`NW%byD$@^QtWBD2Ap?YR}LfyKjmG?_qnUAA~D)ZMlf=hoGBasBKnrW zGW2X6F#J)C$ZMOGSnvH6nW+q!2@CeI`*>qn(l+$yhlt9@F(r`y0Ai6_@y__yAll}i z#2C{U%NSl11sXAuD{tl_lf5>6_qR;}@AYBw;g^XP=h!Y%A8GuM0vdu8P> zpvZp&gT-DI?P(M>@N}=L%B{CW5M)MAzhM*drh^feygpW)pLk;xdp{7}+CKM!p>KCn zT(gwOM}O|HhiE%Crze(wn5^#ZTQSSnsI zlN?E#1fGSQMijrFanax3^g(W2em^8&sv^8VPqG`cVMo~let(Dq=Icq%Y^DxF1$3h{ z?TH?)G1thyeLSOR4R7}({BzsM0cm~~)%h`zCDXn*KcJ!jpy}mbQ=CSKuzKv>gUg4F-yPK6 zha6udN_@t~Wz?g5PdKCET64pf$lRLhRR7*#X%FlHFn64x7`BMg9u8FO?0ZesH`8tD+m1Kq4`I{jE7*rCUPx=_ywpujN0Bd zZ?>CN<7AC+@1H=oy=mB9s7w$;>+E~frEt^QA8y#9ncKmjCm5~73{Oj})v2M^3T(n$ z8aHZ(4|nIn=^)3CK!Jg>DZrjrs6_8xtlB}2rplABCAuDSspqS%NxT2WeB^B8T=w>p ziyZ9}8NIXC;ls5l=S!z$(&XCBk5Yov&Fl7!RF+Y@A-m7tAFR~;js&)P-8*`=mr7i= z#>}B}KoMfUl*kv_L#_bRuPs z5Od*&9ns>;@zLw67ag)AOg5BooG_U_ye>|wOMxmI1e$20Sb^Miqp|@3uH*delf|1& zvxt{t2EYWv;Bbm}#e7b+r=#LC1e{0c$If0yf=4XeHiU z4A1f6uxL;(WX5IO9a8A&9+#-^QNdn4^J}=bJq-tk8#(VB4t5WQju#0B&W6+D8hsBq z!+XL00uBI}tY+yebJ~?uIFymUNwr{*QF+Uf-} zKc<@kP$1^Ng7$fSP}w=j2}GiO$L(#jpyJ~sp1F^eB!d<>q5jhL`-Eg@S@ zmx+HAM7yAj3CvIIZr6R_c{A6*><~RDk`}gd_Y~h%n**mKE7jftAN1`aV(7lE!oBYqE$L zeNw4@v0SP`fk!D`-*@vL&w!gCOQ(3JA1(f2mx?O6@lcf2L`1U>zap{B5b)Twi_l3c zZfmM$3N}A{RbJ~)2V!?`B+^(F=JXG4FiXI|)%tn(X@W=bSGhvCh_w%QwQYLI7DXDj#(bMK%+ohN zf|%b$ZACTUVU^Kl!ZFx9e6jrqp8>z`uofDaX_13S7Vs(5kEX%P+UiMwU7mR^<@jIR zPhZYJf`VY$KGs+VLmyCLHlpT3J*=*O*uxxm7mn+x|Ew~s9JQ*pN`~j3#qZH8E5Iq( zA1G?}rh|*{Wtjb?8uAnfRcSjy2$!wqe>0hPPHw8)E3cpBX8Xkfj{?4-Zc8*|i6eT>Tq z?SdF#Rvl8c;ffQ#&xiHO@@=f7Hnz?&Y>(1?*ZUfjl39kkJ|KQtt)lPI=T%o1>ppa+ zx)Pag@3?-yOXZ!eaua8`!7Tsi9B$rX$j?z5ZdIubD>tk0$ITfBh57QLv8L6sCXqIw|A?E^6W6G8sNsCJ~{d>8PSNco%3{y5-%tnSLK|obj3rzie!l?(C6627~~@6U!#? zt)oF%<^qASW6NC)Z9$N%&g`{*!)zCv8?e^=wJaXROkEkJr#A_;wc~mF1F^Z8pE6(X zLT!s+%_PfmJAK~^$(!#A4_qEureS=ZazLLZU!0CWvyNA?YH2EosPnrDqqaUybO;hW z+pzxgD^XMVy$|yX(paOtlu4((gI3(YV^yuEf!U*Kz**0kI z9iLV)g*fyHdTn~TUuNVBTaQPjg#-CZXa_Qf3hdM4P|dCS>=t>qq-;yIEjm)Q-=@Mw zzjG*#>#8s2qdD$a+qwgU)>#WS9Ix&-?Y7R>R<^$*w!?qik9KV#GM_w+O;-N!Hz^zX z_kqtxN4YR&tNm9WL<0ZG&%@F%SjDsaj!QLJ{x@?emY(j6a(C_O`q(r1IZGRl=rBC^ z8(F?J(*yY~TN(CaMQ4t`Sr7`6MSJvSMp_c6$Npq>wYN1Et_o7YzJ8WG)_yH91O{P_{LqEm7AlkB%e(u?&6_)4xIwn6H*iL#f)wR4yt3%IOnJ z%7hJ$rw6uyImdTv6Kkt(CQL?o>l4US;4Kd_6)1xs>WxeqMew7!39zIil{H^$=1sI{ zHr5Q5CFf3ZAhUNRmS5OcY1JR&8_Qyn$nxm{xzh)gq)J0n^KKK@k}* zF@>DW{PSF_CY(2_lG;w4UgtTRTNd zB*(btbHFrCg~${!KuR==g@kHsz2gNNo+LUHhi)xF?;`i5^KcjD9F=r7h(tvnNnd;+ z7bY(s;ux!TG(TuIFv5{V{e#-%w2IZUAzTZc!p60l%EJK`c!QIeLIgxtRB+Oe8q)HY z(ggWyJki1FTtl8Bq&=^egYTWXEEY{?Q9gHXM|Z7_Gv1{%zA0*u z`+EABSsg^v-JI=1({%m@LwcH&XRc@r_p82 zTv(gMF=;d`d@Dc}y!{MY2$))t#9G~(Vkq)NDY{Gn-Zx&wC`utcUU=GjxXz*bt$4f% zxMdE7kuxj&x2P<4Zbg^pWa34mCJKiSOf?BUF;`MiSPVHE34_DcxZCcXbWh>T4lSixiMNAQa$DqxMVuqm?8_nq? zO*OZTfYC$$Th|o+rN=+&)$o0eMBAs~;i6lxJ!|_L|BTj&SQhMwanz&WFVgWzlOcwHSzNl{lGH@;oaGy2%KlY&*gUgyv1aqs2D{zCMZ59vfdA0Jm%VFhHJLF})o13s|1 zelF!j2=+0stQx}eX`q;ns<`f8K=R~NHgdZ6nR594OHM`Q$gvN?Mq{=vpBT2H9&8xM zjf4?}#`w=$_-tEz==-Q?9XCLsry;k65hUW;q8(BP)E0wr(s!w~44Ip0T?w!{vnlet z@bsi?PyABu2%brOpph*y*YG8Dl>e(W&pCcL>tCv;Hf zFyja0(Hp&fyhyt%h^w{p0c1jCH6DCXr4BE%jgJ$03=~>uIwp?X5}yLuzD>+xD^^r2}b>B9P`tC1Hl^O@4z}-S!wUL&Je* z#$R`QFliR}5m7w1*gK^E=fWKtDP8Cm72`0)SvZcHqlNzpu}BWZc6b8&sJ9 ztFpQO4^~CU0I9|cl^*7Cpfo9w0+c41Ap5efkp5yJ14oAD1%wBqz$|gg4f2)pZ*epp z)LeZ9qziq9%z-B0)166_Xr0pC@ktM(Ys#j@c&pK>_i2)q!5n`@D4f|f3SSaFR8)L= zss(7vrRl$sV#o;6QoHRv&zjF>Xg5x|rr-RzndWR8OXq4)zCY$jiga*;B(qGk2AA1L zX4DiyHUy|(n_n9gR8@iIQxwHt0hWiOl`7;N_obhO&D}nkb&Kl$Y@4(aUf$C6<;TVd z&GG9sK2d=*ry~yObostRsavLq0Be%X+KYK3yeGb%9nIznGfY~o*MDCD6rDFn@gZ&j zWGNxYHrq6kRX_<+%1TOqL8wOEflQb@TGw)@1DM8G4c89ma^6lA0KVzo9Z6hxukvtn z8~ikaWR@WoWSvswJ?GH-g+`cKSn79U9h;ZgkfhzhVa0Xr8&)J+h$Fx*m|U3P#m8Rk z*F{J}JBJJgh5Md^18S{zl-Zjh0Rkk5(k^N!_4xlPcI12bi)$4ZQ4chzia8WG-K~fJ z4G~NA=S}5$N65~+iq(;-ZSXH=y7jrMWZ(tEU!|3HrCy4XA2`DVJjRNFsNbbB)->wn z%&2QyNNRgn)FuE_jADWutXS-N(S3rH|Hz+ggVy?^!^FM7I%lc;EEi{ozA+9c**fB)xrK6XxJ z`FfD(uX#48uIq@2$r9c^(#b(q#A}c}!K(NO2FQH%jU_N(6FO*8)`^@tczu%e<~~(( zLWbn}f{!hgmZoEtw>V27(h0ObPBtvfqbtiBqBP}q$fuCV`v1<7^=Dw6SLF_S_zTx} zhqx0~hoF2F;`d<$`_TRkpWmAm_-HIK6w>>wA&n0P)UlvaSGZKl-vn{<-vqH*voPRH z8mSt0`n*H@K*Qr!hzIvQaDf$ny}&rE%&761kPvQ)I7cCw4~v`35s-S>knrjby;Q** zFKqp*6EA!&SO75#By**n*n&OI`Y+R!j)62?|6ld-e>WF7xENrP;PHxI$Os|^DF$H? zH@G9nTfO)n)m?WymF@q}s1A9AtfQ=>WX}`X>yUM1Zy_gJQljkay(?Qr_Q;BqT|^Ye zR))h9U-Pd(}=KKA*J|BYy5Fr#X?eoT${-X;s zxvnk0k#L{uCjxh7E61K_Ymx}n^M@w+TtOd41EKUdHW*XB2n{Bw!zm_4FW9_+%FW9Y z22Bi3hrTu1)9RH5(>^vp&MTFUQA20Y8~#6*lbxLix^8|)9Q2br>#@kV+%Qd981l}@ zAGQVm@|5l3SQVq3pES6QIj%QVl!J$dJc$!~)s)e1&FJ43KM5yb(HIPR)N!L~dtB-X zp`a62iT~aANLYa<^WhccpKBix7$t{LXet;NRb*hpV{I6bTw>1y$H^5a%=Bzxnlp&hCBc=>(2*+Mu0UVeqt(+<*B%W!uhZ zBKoC#6?crdT_|j&^}oW0^FumODeRKpMt;iy=cwwoqa* zkP2T+g}}a(NG4|6-V~ijnmAP{H6=yG3|d8bpJDJoM}U69wHR`wWc{+H7lcxF+=1n*ddS?X8RL2>NE~h@EIdbfeN}q?3Z`vzTbbtp+76pE1?z zhb|-uxwH^3ffNBTC!nt#-h4mX09$XpOHdC-gHpqHKYy%vIwJhOfv60Jo2z$p3u9+j z@*J8jSIGg}pzL&{2|yOFU5`yDkjNZEq z?x^MwvF$B-e_lOQZCA#=jSSxk5lb+z_V`7qfxhVs*D27pFbaVG96$wu)1VB`CUXHe~!T4jqGg|1g`=H|^U#e%incc+>?7c#w^W!@~cIn)JWNiOD_j zMNKG&S(WzCr05HqMWdJItpNS!!aua_w?4K=1Zmr{KRG{tP6NRC%IC8H&Wtmk(YO)ahf-of;t|4^ zOpFRdRm19A9#aaDaZy7KF$tW%(Yn2lSaJRKNvvB1FTv!QKEVBPJ#<&~e7rmPIwz~5 zzRaDr0W5?^IDfh7s~lo=KGfDRCApt;AEgf&bhjZ?3X`mhMEx!&*bGL9wDj##Jf1SX z75Ga3+-6o^CS#zb??|^EaGnffnxdB&g^nGzEz`%G3%*-zj5GGQXqOyKe*XJM*H2H` zewH$`fZlC7k>c~WAkV(a6*@V`E|rOKYaDwr0aI953My`!TApFD^V=StZe)#NdF3n@ zU{0rg^0>q#qk)HFf%*7w5#EfwT2ms|oj+cIJ_QVPt8>p!(@vKZvN~T$axt&)nKL`fJ@ZFc zMrr0<(IM(MV~%CIN~c6VB@|8r@&mLEK+-XR4ZXup<0rLz?O}*gsGXm5z?y+KhuMJk z&=}g_`R6b}69azS^T2L`BF40qceU-lFiSV~v=?3(qZ*u2>f^>Cd3`ae%%4gKif}g% zAFMGfTI&BARDbD6btk)$6PeP-=@I9X-vI}pphHN2M-a2h6k$r!U8SzFqj zR-OMX$USA!C`X6YrA*DVh<%V2`z*C-e6akM5{7Sz92&d}j?+Mk^gMNUT!e?G_rwe~ zuWO^y17*2ZEneO==uELh#Kv4L?Uv^O8~a%P_6O%jgb}(LcH|o0DU8mcq?rpJj2u5J zke?Cyr7l+WkD);mH`|53=D5Vs2q8?z67XxEE6VeyE%ZvCZ}nBlo7} zc{LsHJ(bifl@v9s-gL8Y$X2Q8VQNKpKRguv!7o6cs=2;ks9&NVaX(h>s( zvN)|f7Xz-(-kp%6=362yjI*wX9esCiF`;RVm9<0c7+dAD7)>D1!LXtvO9c*R&W$hc z5l4=6fOb0Cprur$W70z+n%o~gzL_NX)6|ZC{4BT6eyT=#F5T9iEO>-mhHl|RULEKF z9BToQKXM}I-w`v9NFHpGf49)TQXb3`e1msxe4!rNx@*wVV!J`~i=noj?M-!iC9@vD z(pI5Y7;ChKCaJmBs@Xq&A;J!h${q^2X!0n0-DXjH?z z3b&r|7&Z>xmGbYzZ;$$#;04fcx3GGJA1HS+DM`|~jLusgz*gyYfJV*7!Vjs00a5(Y z4~e3lh~29!WqW%r;Fsp>hXx1dt4Aj9{wS=}*1Myhc)`P!a9neD=0f0#3q#4;3NA=& zBpFGc@Toc!RO8+|2f1EL+BQVrZ#|D_W|1K%s8^Web*^5yrZlL%xb^LdR}p>Ye0ZzT zl(XX6Iaai7Tdk~3tT3Gm==*$HE2DL|)aaOh!YK`U{c=+}0FtklgglQdP)wTKPM$DP zbnSVv6RVP1i#%O-M%}h3mIlNl`qwwROq>|zyDL4og3vn`We7NI)eW!AKwNnZ7Ss_+ zF_s^9e0kJ|7^zg9qRa2K9W39)LL&5WIHI!*xM97*1-YOFE~xxF?h>UA*nT<^<1)Z*9jgj++2U8#?ktwm`{mW)_55nH(o!y zqMUCrKF7C_oue+ZsQ#jcU%DU6N)v>v2>O%~M@whS@t^_?ewq`26KGm%pX1UlIq_lX zP}&f!sZ$IZPZ3JUEq<2+unBA$QyS&26t-<~QSJ!x8UP!uIM=4$e=^449g-k!SGnk9 zfjYL84P#HN>^%p?qAU=V&OyBZ1QKKQuB__GT(@cy*)}iwChPtvqZU8Yw`?Lfa3g(Q zt>)t4pRj|>Y}_{g?yBRc{LN}L%c}b}S=KUxCow53b@XT9+&pYEvE3L9<{s!>q*#l955yPzGIEz;VbQF_7YBFZzi$5_j_4PIW?a`)#Zc=n8MWv)gU6{xuAj@QqH zf`cUtGk;rfLrdq6$gbN9%;ZgE*k$-^W;T4#L9Z6ntISUi>*Y-A*Vep_f#Xy{3Xwy! z8uJr>AyekO&vge}U3Loc7y*r}%A>8`xuq z`@K-*yk0zHhvSMZTrXe7Xd;3PA-NBLl>B!39Rqd&Y zFX^iqkg#A;D+erUA1Bj~6}P2&MP#|-xpWKUnd1+Hnu9R=EGPU}P4t<%9s;RN`MVd+ z*srEgxTGXikLYMV)pRhsMlIB463=Z^>ehcXE*dO%!~|?oU-YZ4JdcbqJ#J>7mYk)Q z6o>bBGKA&x4GP~To<__+Ao^xOV|6tol?bVwZ-F(;Q}@~wTjv#HZl}5J2XnR?Y#}qh zKb@%DAQ|m(o8V(DjtyC6{#GHWo^bW}_n0p4uoAP4c{1cenmB(4YLN@JyH>E>RSvzw zN6EjzM|i`He@O4t<=H4fCI1Z0D78jbr#m=_rxn^|l+A0qI?HK`QWTsYf9=)GF?DxQ{+}|GG&yWmCm?Un<_)x2`;x}-h=WAau;zf1-TPA^7%l`O9Ah#si=5H~f}O{Vh%FUfMe~T2b78`Nu2&H%urg0VuPD>);6t3}CZ@Q?1%?a8=t|?`HPd zd9;~j*zfT&3CQi+4Skq+C4Nt{IFZXVUt6RJu;>(;pWN&(HS^B8O;ovcjbj`uZe zR94XD$Cg5ypLS=H^YW3bg$afQIlpzzVbB;%NLJTrteV3MBOB#a=Y&9d6>CD1Kod}y zY&I%V_|wnAN|WCv;hmtI&&{Myd0kp>H=8?z*gb?m%BbWzMNl3l!x;Jap#`wwghUVur-_DODyH1v=G_NX_7g=F zTQV`|DU(%MkL91py6hVi9AT>}-{YwUg*FYc;aIoM@o5U%TS{00p6)F|)$`9YZXHR* zlzrI@2CNON*VAPR!z;`RA2!_hBAD#mKuP~HEwbfjW3ZMEiLj40TKx`Js6t0qjzNve z-1wSAts`p)zuaQ$hbmz>b*q(;JW#6l@#wDc7Zxvw_320*7muYnqwc@i|BP zsD|xA#x~QZp_16KlxF)kk~B~$-1MRI+&8zTKw7v>$$%#pBqTMn&ys_v5_Dpy zu0#KkWVr?k9O82%vQI`nnpu7R;CoEdoGNr1G zPc4+O9UqK*Aqk!vEuppv43L7^$<(m@GwmEjbB{~jm=kh56WKbN{UlCpa7qzWED5nS z@;ZuNj@0TT+|uT83azvCN;_QIEQ{SOWFvT>>$&tzpJi>LaZC$26M zwn9*Y`5a120QukxtTlB5sst^c^pYEwr{~YvpOd{7s4vVFKf-3 zZ~o}HXIUxGJ%WgGM-waRRRNsMU1D*~cuVq055C$n>h- z6HFTFHMymCoIU>}VC`h?5wLO>o9@#^DAyv+XN-JEWFOCU(BY|N+5A~O3`*Abt|d?*Lm6&|zuK>Q|l)jeacJ_3vq5(A>9xRfWQ+T5t7T1`8<+ z*I@^lpaKKK${k{<=@FF1Z0`l;!YQ#5GY#x(RWcUnI8*ZcI?fQiNW8eY)Yy7BcDCgQ zyGl%UB%qrXIcW0;wn&=Q)s2f@P*hbleEL*N@eW_=m!6Gxvfc6FF$J1ta?Dx1EH3v< zn!PenucMKoxhwGlH*&W+995I!PG1TS>dV2Q$I0L~V2>NTJ7HS{8_a z*mSB(vyNUvTN~2e%hFz}-b7O_6D8)kCaK6I8y&8)fb$|5XkX6BlqUHE6Z|wCCKRH2 zvU)D*Y&~Dj80AEvEid@&5Z1w63@uyhKVD2liQ2u)an-k5^G2zg4(6}D$dITm&8!4O z;4^@>s#KT8(_Z%D!}RhYAw(Vo&wc*go))Xvmm^I% zl(r_KBQnGDx~64fDdryt`L=N~Bk`l$*qVI4+Hlr^T(=V0@PHavz7wGA;(nW4tTN}& z_Zcwc*sDKUEk>G!Ik@&JZ+FWLGZEju++?H8hPD(D;nRN*U>XH+ChN!o$O$P5dgzKO-}=Y1wtsf5}K)nNhHhp!)q`#+*#_5Uhi+h-QI>8ho3++l9&b**z^ zG=S;6H8jBg61vZCxOVjZbcD>mOQJzXf&y^bM00T-{3_55bmRnN2^#~HJa^RKtC z$_6t1k<~Ut#-Ux+?rXnkezbol(wzZi@x^`JU*lwA&h?k^s-j-~MHufLZ`FD6& zH8?Q94R#*fphLSK^hVK=Msq;kSu~{S_n(CSkDo-3Y-E)L>p(@?fY|=pao2o6f2L?K z5rxP str: + """Application prefix.""" + prefix = "-".join([self.project_name, self.deployment_name, self.module_name]) + return prefix + + +class CdkDefaultSettings(CdkBaseSettings): + """CDK Default Settings. + + These parameters comes from AWS CDK by default. + """ + + model_config = SettingsConfigDict(env_prefix="CDK_DEFAULT_") + + account: str + region: str + + +class ApplicationSettings(CdkBaseSettings): + """Application settings.""" + + settings: SeedFarmerSettings = Field(default_factory=SeedFarmerSettings) + parameters: SeedFarmerParameters = Field(default_factory=SeedFarmerParameters) + default: CdkDefaultSettings = Field(default_factory=CdkDefaultSettings) diff --git a/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/stack.py b/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/stack.py new file mode 100644 index 00000000..47cedaf5 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/stack.py @@ -0,0 +1,176 @@ +"""Seedfarmer module to deploy a SageMaker Model Package.""" + +import logging +import pathlib +from typing import Any, Optional + +import aws_cdk.aws_events as events +import aws_cdk.aws_events_targets as events_targets +from aws_cdk import CfnOutput, Stack, Tags +from aws_cdk import aws_iam as iam +from constructs import Construct + +logging.basicConfig() +logger = logging.getLogger(pathlib.Path(__file__).name) + + +class SagemakerModelPackageEventStack(Stack): + """Create a Sagemaker Model Package Event Rule.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + target_event_bus_name: str, + target_account_id: str, + model_package_group_name: str, + sagemaker_project_id: Optional[str] = None, + sagemaker_project_name: Optional[str] = None, + **kwargs: Any, + ) -> None: + """Deploy a Sagemaker Model Package Event Rule. + + Parameters + ---------- + scope + Parent of this stack, usually an ``App`` or a ``Stage``, but could be any construct + construct_id + The construct ID of this stack + target_event_bus_name + The event bus name in the target account to send events to. + target_account_id + The target account id which shall receive events and must have access to model package group metadata. + model_package_group_name + SageMaker Package Group Name to setup event rules. + sagemaker_project_id + SageMaker project id, defaults None + sagemaker_project_name + SageMaker project name, defaults None + """ + super().__init__(scope, construct_id, **kwargs) + self.target_event_bus_arn = f"arn:aws:events:{self.region}:{target_account_id}:event-bus/{target_event_bus_name}" + self.target_account_id = target_account_id + self.model_package_group_name = model_package_group_name + + self.sagemaker_project_name = sagemaker_project_name + self.sagemaker_project_id = sagemaker_project_id + + self.setup_resources() + + self.setup_outputs() + + self.setup_tags() + + def setup_resources(self) -> None: + """Deploy resources.""" + + self.role = self.setup_role() + + self.rule = self.setup_events() + + def setup_events(self) -> events.Rule: + """Setup an event rule + + The event rule will send SageMaker Model Package State Change events to another EventBus. + + Returns + ------- + An event rule + """ + rule = events.Rule( + self, + "SageMakerModelPackageStateChangeRule", + event_pattern=events.EventPattern( + source=["aws.sagemaker"], + detail_type=["SageMaker Model Package State Change"], + detail={ + "ModelPackageGroupName": [self.model_package_group_name], + "ModelApprovalStatus": ["Approved", "Rejected"], + }, + ), + description=f"Rule to send events when `{self.model_package_group_name}` SageMaker Model Package state changes", + ) + + target_role = iam.Role( + self, + "SageMakerModelPackageStateChangeRuleTargetRole", + assumed_by=iam.ServicePrincipal("events.amazonaws.com"), + path="/service-role/", + ) + + target_role.add_to_policy( + iam.PolicyStatement( + actions=[ + "events:PutEvents", + ], + effect=iam.Effect.ALLOW, + resources=[self.target_event_bus_arn], + ) + ) + + target = events_targets.EventBus( + event_bus=events.EventBus.from_event_bus_arn( + scope=self, id="TargetEventBus", event_bus_arn=self.target_event_bus_arn + ), + role=target_role, + ) + + rule.add_target(target) + + return rule + + def setup_role(self) -> iam.Role: + """Setup an IAM Role to get model package group metadata. + + This IAM role allows a target account to get model package group metadata. + + Returns + ------- + An IAM role + """ + role = iam.Role( + self, + "Role", + assumed_by=iam.AccountPrincipal(self.target_account_id), + path="/service-role/", + ) + + role.add_to_policy( + iam.PolicyStatement( + actions=[ + "sagemaker:DescribeModelPackageGroup", + "sagemaker:DescribeModelPackage", + "sagemaker:ListModelPackages", + ], + effect=iam.Effect.ALLOW, + resources=[ + f"arn:aws:sagemaker:{self.region}:{self.account}:model-package-group/{self.model_package_group_name}", + f"arn:aws:sagemaker:{self.region}:{self.account}:model-package/*", + ], + ) + ) + + return role + + def setup_tags(self) -> None: + """Add tags to all resources.""" + Tags.of(self).add("sagemaker:deployment-stage", Stack.of(self).stack_name) + + if self.sagemaker_project_id: + Tags.of(self).add("sagemaker:project-id", self.sagemaker_project_id) + + if self.sagemaker_project_name: + Tags.of(self).add("sagemaker:project-name", self.sagemaker_project_name) + + def setup_outputs(self) -> None: + """Setups outputs and metadata.""" + CfnOutput(scope=self, id="RoleArn", value=self.role.role_arn) + CfnOutput(scope=self, id="RuleArn", value=self.rule.rule_arn) + + CfnOutput( + scope=self, + id="metadata", + value=self.to_json_string( + {"RoleArn": self.role.role_arn, "RuleArn": self.rule.rule_arn} + ), + ) diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/__init__.py b/modules/sagemaker/sagemaker-model-package-event/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py new file mode 100644 index 00000000..7bfcb432 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py @@ -0,0 +1,26 @@ +import os +import sys + +import pytest + + +@pytest.fixture(scope="function") +def stack_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" + os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_target_account_id"] = "dummy321" + + # Unload the app import so that subsequent tests don't reuse + if "app" in sys.modules: + del sys.modules["app"] + + +def test_app(stack_defaults): + import app # noqa: F401 diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py new file mode 100644 index 00000000..96767057 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py @@ -0,0 +1,48 @@ +import os + +import pytest + +from sagemaker_model_package_event.settings import ApplicationSettings + + +@pytest.fixture(scope="function") +def env_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" + os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_target_account_id"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_name"] = "dummy321" + + +def test_settings_inputs(env_defaults) -> None: + settings = ApplicationSettings() + + assert settings.parameters.target_account_id == "dummy321" + + project_name = os.environ["SEEDFARMER_PROJECT_NAME"] + deployment_name = os.environ["SEEDFARMER_DEPLOYMENT_NAME"] + module_name = os.environ["SEEDFARMER_MODULE_NAME"] + prefix = f"{project_name}-{deployment_name}-{module_name}" + + assert settings.settings.app_prefix == prefix + + account = os.environ["CDK_DEFAULT_ACCOUNT"] + assert settings.default.account == account + + +def test_settings_required_parameters(env_defaults) -> None: + del os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] + del os.environ["SEEDFARMER_PARAMETER_target_account_id"] + del os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] + + with pytest.raises(ValueError) as excinfo: + ApplicationSettings() + + assert "3 validation errors for SeedFarmerParameters" in str(excinfo.value) diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py new file mode 100644 index 00000000..df7e9e93 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py @@ -0,0 +1,28 @@ +import aws_cdk as cdk +from aws_cdk.assertions import Template + + +def test_synthesize_stack() -> None: + from sagemaker_model_package_event import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelPackageEventStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + target_event_bus_name="dummy123", + target_account_id="dummy123", + model_package_group_name="dummy745", + sagemaker_project_id="id", + sagemaker_project_name="project-name", + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::Events::Rule", 1) + template.resource_count_is("AWS::IAM::Role", 2) From 74ed5d5b5cdc5abbe81641262c0da5455dce1b6c Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Sat, 23 Mar 2024 00:02:57 -0300 Subject: [PATCH 02/16] Refactoring --- .../sagemaker-model-package-event/README.md | 55 ----- .../docs/_static/.$architecture.drawio.bkp | 1 - .../docs/_static/architecture.drawio | 1 - .../docs/_static/architecture.drawio.png | Bin 43830 -> 0 bytes .../sagemaker_model_package_event/stack.py | 176 -------------- .../tests/test_app.py | 26 --- .../tests/test_settings.py | 48 ---- .../tests/test_stack.py | 28 --- .../sagemaker-model-package-group/README.md | 68 ++++++ .../app.py | 6 +- .../deployspec.yaml | 0 .../docs/_static/.$architecture.drawio.bkp | 1 + .../docs/_static/architecture.drawio | 1 + .../docs/_static/architecture.drawio.png | Bin 0 -> 41135 bytes .../pyproject.toml | 0 .../requirements.txt | 0 .../__init__.py | 0 .../settings.py | 9 +- .../sagemaker_model_package_group/stack.py | 220 ++++++++++++++++++ .../tests/__init__.py | 0 .../tests/conftest.py | 23 ++ .../tests/test_app.py | 14 ++ .../tests/test_settings.py | 27 +++ .../tests/test_stack.py | 57 +++++ 24 files changed, 420 insertions(+), 341 deletions(-) delete mode 100644 modules/sagemaker/sagemaker-model-package-event/README.md delete mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp delete mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio delete mode 100644 modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png delete mode 100644 modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/stack.py delete mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_app.py delete mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py delete mode 100644 modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py create mode 100644 modules/sagemaker/sagemaker-model-package-group/README.md rename modules/sagemaker/{sagemaker-model-package-event => sagemaker-model-package-group}/app.py (70%) rename modules/sagemaker/{sagemaker-model-package-event => sagemaker-model-package-group}/deployspec.yaml (100%) create mode 100644 modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp create mode 100644 modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio create mode 100644 modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio.png rename modules/sagemaker/{sagemaker-model-package-event => sagemaker-model-package-group}/pyproject.toml (100%) rename modules/sagemaker/{sagemaker-model-package-event => sagemaker-model-package-group}/requirements.txt (100%) rename modules/sagemaker/{sagemaker-model-package-event/sagemaker_model_package_event => sagemaker-model-package-group/sagemaker_model_package_group}/__init__.py (100%) rename modules/sagemaker/{sagemaker-model-package-event/sagemaker_model_package_event => sagemaker-model-package-group/sagemaker_model_package_group}/settings.py (87%) create mode 100644 modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py rename modules/sagemaker/{sagemaker-model-package-event => sagemaker-model-package-group}/tests/__init__.py (100%) create mode 100644 modules/sagemaker/sagemaker-model-package-group/tests/conftest.py create mode 100644 modules/sagemaker/sagemaker-model-package-group/tests/test_app.py create mode 100644 modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-group/tests/test_stack.py diff --git a/modules/sagemaker/sagemaker-model-package-event/README.md b/modules/sagemaker/sagemaker-model-package-event/README.md deleted file mode 100644 index 363da67c..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# SageMaker Model Package Event - -## Description - -This module creates a SageMaker Model Package Event Rule to send SageMaker Model Package Group state changes events to another EventBus and creates an IAM Role that can be assumed to get model package group metadata. - -It is usefull when you want to setup cross account integration with SageMaker model package groups. - -### Architecture - -![SageMaker Model Package Event Architecture](docs/_static/architecture.drawio.png "SageMaker Model Package Event Architecture") - -## Inputs/Outputs - -### Input Paramenters - -#### Required - -- `target_event_bus_name`: The event bus name in the target account to send events to (e.g. default). -- `target_account_id`: The target account id which shall receive events and must have access to model package group metadata (e.g. 11112222333344). -- `model_package_group_name`: SageMaker Package Group Name to setup event rules. - -#### Optional - -- `sagemaker_project_id`: SageMaker project ID. -- `sagemaker_project_name`: SageMaker project name. - -### Sample manifest declaration - -```yaml -name: sagemaker-model-package-event -path: modules/sagemaker/sagemaker-model-package-event -targetAccount: primary -parameters: - - name: model_package_group_name - value: mlops-model-xgboost - - name: target_account_id - value: 111222333444 - - name: target_event_bus_name - value: default -``` - -### Module Metadata Outputs - -- `RoleArn`: the IAM Role ARN to get model package group metadata. -- `RuleArn`: the EventBridge rule ARN. - -#### Output Example - -```json -{ - "RoleArn": "arn:aws:iam::111222333444:role/service-role/xxxxxxxxxx", - "RuleArn": "arn:aws:events:xxxxxxxx:111222333444:rule/xxxxxxxxxxx", -} -``` diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp b/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp deleted file mode 100644 index cc2eb4b2..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/docs/_static/.$architecture.drawio.bkp +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio b/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio deleted file mode 100644 index 523275e3..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png b/modules/sagemaker/sagemaker-model-package-event/docs/_static/architecture.drawio.png deleted file mode 100644 index 0adf5e3d7e87db6cc20b2ca377fccc62a19667b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43830 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 None: - """Deploy a Sagemaker Model Package Event Rule. - - Parameters - ---------- - scope - Parent of this stack, usually an ``App`` or a ``Stage``, but could be any construct - construct_id - The construct ID of this stack - target_event_bus_name - The event bus name in the target account to send events to. - target_account_id - The target account id which shall receive events and must have access to model package group metadata. - model_package_group_name - SageMaker Package Group Name to setup event rules. - sagemaker_project_id - SageMaker project id, defaults None - sagemaker_project_name - SageMaker project name, defaults None - """ - super().__init__(scope, construct_id, **kwargs) - self.target_event_bus_arn = f"arn:aws:events:{self.region}:{target_account_id}:event-bus/{target_event_bus_name}" - self.target_account_id = target_account_id - self.model_package_group_name = model_package_group_name - - self.sagemaker_project_name = sagemaker_project_name - self.sagemaker_project_id = sagemaker_project_id - - self.setup_resources() - - self.setup_outputs() - - self.setup_tags() - - def setup_resources(self) -> None: - """Deploy resources.""" - - self.role = self.setup_role() - - self.rule = self.setup_events() - - def setup_events(self) -> events.Rule: - """Setup an event rule - - The event rule will send SageMaker Model Package State Change events to another EventBus. - - Returns - ------- - An event rule - """ - rule = events.Rule( - self, - "SageMakerModelPackageStateChangeRule", - event_pattern=events.EventPattern( - source=["aws.sagemaker"], - detail_type=["SageMaker Model Package State Change"], - detail={ - "ModelPackageGroupName": [self.model_package_group_name], - "ModelApprovalStatus": ["Approved", "Rejected"], - }, - ), - description=f"Rule to send events when `{self.model_package_group_name}` SageMaker Model Package state changes", - ) - - target_role = iam.Role( - self, - "SageMakerModelPackageStateChangeRuleTargetRole", - assumed_by=iam.ServicePrincipal("events.amazonaws.com"), - path="/service-role/", - ) - - target_role.add_to_policy( - iam.PolicyStatement( - actions=[ - "events:PutEvents", - ], - effect=iam.Effect.ALLOW, - resources=[self.target_event_bus_arn], - ) - ) - - target = events_targets.EventBus( - event_bus=events.EventBus.from_event_bus_arn( - scope=self, id="TargetEventBus", event_bus_arn=self.target_event_bus_arn - ), - role=target_role, - ) - - rule.add_target(target) - - return rule - - def setup_role(self) -> iam.Role: - """Setup an IAM Role to get model package group metadata. - - This IAM role allows a target account to get model package group metadata. - - Returns - ------- - An IAM role - """ - role = iam.Role( - self, - "Role", - assumed_by=iam.AccountPrincipal(self.target_account_id), - path="/service-role/", - ) - - role.add_to_policy( - iam.PolicyStatement( - actions=[ - "sagemaker:DescribeModelPackageGroup", - "sagemaker:DescribeModelPackage", - "sagemaker:ListModelPackages", - ], - effect=iam.Effect.ALLOW, - resources=[ - f"arn:aws:sagemaker:{self.region}:{self.account}:model-package-group/{self.model_package_group_name}", - f"arn:aws:sagemaker:{self.region}:{self.account}:model-package/*", - ], - ) - ) - - return role - - def setup_tags(self) -> None: - """Add tags to all resources.""" - Tags.of(self).add("sagemaker:deployment-stage", Stack.of(self).stack_name) - - if self.sagemaker_project_id: - Tags.of(self).add("sagemaker:project-id", self.sagemaker_project_id) - - if self.sagemaker_project_name: - Tags.of(self).add("sagemaker:project-name", self.sagemaker_project_name) - - def setup_outputs(self) -> None: - """Setups outputs and metadata.""" - CfnOutput(scope=self, id="RoleArn", value=self.role.role_arn) - CfnOutput(scope=self, id="RuleArn", value=self.rule.rule_arn) - - CfnOutput( - scope=self, - id="metadata", - value=self.to_json_string( - {"RoleArn": self.role.role_arn, "RuleArn": self.rule.rule_arn} - ), - ) diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py deleted file mode 100644 index 7bfcb432..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/tests/test_app.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -import sys - -import pytest - - -@pytest.fixture(scope="function") -def stack_defaults(): - os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" - os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" - os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" - - os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" - os.environ["CDK_DEFAULT_REGION"] = "us-east-1" - - os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" - os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] = "dummy321" - os.environ["SEEDFARMER_PARAMETER_target_account_id"] = "dummy321" - - # Unload the app import so that subsequent tests don't reuse - if "app" in sys.modules: - del sys.modules["app"] - - -def test_app(stack_defaults): - import app # noqa: F401 diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py deleted file mode 100644 index 96767057..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/tests/test_settings.py +++ /dev/null @@ -1,48 +0,0 @@ -import os - -import pytest - -from sagemaker_model_package_event.settings import ApplicationSettings - - -@pytest.fixture(scope="function") -def env_defaults(): - os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" - os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" - os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" - - os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" - os.environ["CDK_DEFAULT_REGION"] = "us-east-1" - - os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" - os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] = "dummy321" - os.environ["SEEDFARMER_PARAMETER_target_account_id"] = "dummy321" - os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" - os.environ["SEEDFARMER_PARAMETER_sagemaker_project_name"] = "dummy321" - - -def test_settings_inputs(env_defaults) -> None: - settings = ApplicationSettings() - - assert settings.parameters.target_account_id == "dummy321" - - project_name = os.environ["SEEDFARMER_PROJECT_NAME"] - deployment_name = os.environ["SEEDFARMER_DEPLOYMENT_NAME"] - module_name = os.environ["SEEDFARMER_MODULE_NAME"] - prefix = f"{project_name}-{deployment_name}-{module_name}" - - assert settings.settings.app_prefix == prefix - - account = os.environ["CDK_DEFAULT_ACCOUNT"] - assert settings.default.account == account - - -def test_settings_required_parameters(env_defaults) -> None: - del os.environ["SEEDFARMER_PARAMETER_target_event_bus_name"] - del os.environ["SEEDFARMER_PARAMETER_target_account_id"] - del os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] - - with pytest.raises(ValueError) as excinfo: - ApplicationSettings() - - assert "3 validation errors for SeedFarmerParameters" in str(excinfo.value) diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py b/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py deleted file mode 100644 index df7e9e93..00000000 --- a/modules/sagemaker/sagemaker-model-package-event/tests/test_stack.py +++ /dev/null @@ -1,28 +0,0 @@ -import aws_cdk as cdk -from aws_cdk.assertions import Template - - -def test_synthesize_stack() -> None: - from sagemaker_model_package_event import stack - - app = cdk.App() - - project_name = "test-project" - dep_name = "test-deployment" - mod_name = "test-module" - app_prefix = f"{project_name}-{dep_name}-{mod_name}" - - stack = stack.SagemakerModelPackageEventStack( - scope=app, - construct_id=app_prefix, - env=cdk.Environment(account="111111111111", region="us-east-1"), - target_event_bus_name="dummy123", - target_account_id="dummy123", - model_package_group_name="dummy745", - sagemaker_project_id="id", - sagemaker_project_name="project-name", - ) - - template = Template.from_stack(stack) - template.resource_count_is("AWS::Events::Rule", 1) - template.resource_count_is("AWS::IAM::Role", 2) diff --git a/modules/sagemaker/sagemaker-model-package-group/README.md b/modules/sagemaker/sagemaker-model-package-group/README.md new file mode 100644 index 00000000..922b2940 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/README.md @@ -0,0 +1,68 @@ +# SageMaker Model Package Group + +## Description + +This module creates a SageMaker Model Package Group to register and version SageMaker Machine Learning (ML) models and setups an Amazon EventBridge Rule to send model package group state change events to an Amazon EventBridge Bus. + +### Architecture + +![SageMaker Model Package Group Architecture](docs/_static/architecture.drawio.png "SageMaker Model Package Group Architecture") + +## Inputs/Outputs + +### Input Paramenters + +#### Required + +- `model_package_group_name`: SageMaker Package Group Name to setup event rules. + +#### Optional + +- `retain_on_delete`: Wether or not to retain resources on delete. Defaults True. +- `target_event_bus_arn`: The event bus arn in to send events model package group state change events to. It can be a bus located in another account. Defaults None. +- `model_package_group_description`: The model package group description. Defaults None. +- `target_account_ids`: A list of account ids which shall have read-only access to the model package group. Defaults None. +- `sagemaker_project_id`: SageMaker project ID. +- `sagemaker_project_name`: SageMaker project name. + +### Sample manifest declaration + +```yaml +name: sagemaker-model-package-group +path: modules/sagemaker/sagemaker-model-package-group +targetAccount: primary +parameters: + - name: model_package_group_name + value: mlops-model-xgboost + - name: retain_on_delete + value: True + - name: target_event_bus_arn + value: arn:aws:events:xx-xxxxx-xx:xxxxxxxxxxxx:event-bus/default + - name: model_package_group_description + value: Test model package group module + - name: target_account_ids + value: '["111222333444", "555666777888"]' + - name: sagemaker_project_id + value: xxxxxxxx + - name: sagemaker_project_name + value: test +``` + +### Module Metadata Outputs + +- `SagemakerModelPackageGroupArn`: the SageMaker Model Package Group ARN. +- `SagemakerModelPackageGroupName`: the SageMaker Model Package Group name. + +#### Optional Outputs + +- `SagemakerModelPackageGroupEventRuleArn`: the Amazon EventBridge rule ARN. + +#### Output Example + +```json +{ + "SagemakerModelPackageGroupArn": "arn:aws:sagemaker:xx-xxxx-xx:xxxxxxxxxxxxxx:model-package-group/my-package-group", + "SagemakerModelPackageGroupName": "my-package-group", + "SagemakerModelPackageGroupEventRuleArn": "arn:aws:events:xxxxxxxx:111222333444:rule/xxxxxxxxxxx", +} +``` diff --git a/modules/sagemaker/sagemaker-model-package-event/app.py b/modules/sagemaker/sagemaker-model-package-group/app.py similarity index 70% rename from modules/sagemaker/sagemaker-model-package-event/app.py rename to modules/sagemaker/sagemaker-model-package-group/app.py index 0473c5d7..9327d85d 100644 --- a/modules/sagemaker/sagemaker-model-package-event/app.py +++ b/modules/sagemaker/sagemaker-model-package-group/app.py @@ -3,8 +3,8 @@ import aws_cdk as cdk -from sagemaker_model_package_event.settings import ApplicationSettings -from sagemaker_model_package_event.stack import SagemakerModelPackageEventStack +from sagemaker_model_package_group.settings import ApplicationSettings +from sagemaker_model_package_group.stack import SagemakerModelPackageGroupStack # Load application settings from env vars. app_settings = ApplicationSettings() @@ -16,7 +16,7 @@ app = cdk.App() -stack = SagemakerModelPackageEventStack( +stack = SagemakerModelPackageGroupStack( scope=app, construct_id=app_settings.settings.app_prefix, env=env, diff --git a/modules/sagemaker/sagemaker-model-package-event/deployspec.yaml b/modules/sagemaker/sagemaker-model-package-group/deployspec.yaml similarity index 100% rename from modules/sagemaker/sagemaker-model-package-event/deployspec.yaml rename to modules/sagemaker/sagemaker-model-package-group/deployspec.yaml diff --git a/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp b/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp new file mode 100644 index 00000000..70d33110 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp @@ -0,0 +1 @@ +7Vpbc6M2FP41nmkf6kFgwH70Jcm2TdrsptNt+uJRQMZsBPIIEdv763sEAhskEje7dpI2TiZBn66c850buOdMk80Fx6vlFQsJ7dlWuOk5s55to4HtwT+JbEtk6NolEPE4VIN2wE38lSjQUmgehyRrDBSMURGvmmDA0pQEooFhztm6OWzBaHPXFY6IBtwEmOro5zgUy+ourB3+gcTRstoZWaonwdVgBWRLHLL1HuSc9ZwpZ0yUV8lmSqgUXiWXct55R299ME5ScciELx+j4Hr8sBg7k+GlF4pNbt3+pFZ5wDRXN/wH5hERgI0/38i/QcBy2KC8BbGt5LJicSoK2boT+IWtp1bPhZ6pbPVttwW0234TQHpLrtEE2m2/CaD28qi1P2ofcA/QWo3lrdb+1t4B4deZsFzQOCXTmoUWgBHHYQzamTLKOGApS0F6k6VIKLQQXK6XsSA3KxxIqa7BggBbsFQoO0B21VaCl6sC0wWGvbhao9AE4WcPpFRIOYZSvMriu3oWJ0HOs/iBfCJZubhEgZMreZ1sImm+fbzOBv2Is3xVHP9n2MvYO4fLeUBZHs4xFXIhwdk9qW60Zzvwcy55OFnElLYE8EC4iMHExjSO5PqCye2walGyKFYEqcRpdFm0Zo6lJGHaIsTZkoTqlnSzqDgOu5LNHqTM5IKwhAi+hSGqd1A5H+WzKmNf7xwAqrDlnvE7AwVi5XSieumdXcKFMk2zmQa/4l+S5f0kvL9dX3ljMl/QrwYzLdQN0F2eabaZ3RMRLJU8jMzskmQHY/d1CMMnljUczDStq8ENbVSqvsR3hF6zLBZxQak7JgRLnuRCQCS1mzbzlH3gbFXe6CLeyHNoBoK6iE/kiDsIQxGZh2SBcyrmBTYHKc85yVjOpaVOOgjVpF23v+0knus2eGf7Xh+4WH+QRsOBgYW230fDb+fhxppdJ6H/cfo3/jClv9/+Fk3/NPDwppTJe7h4Dxf/23DxZLRwjhYtzFaKNPsj4NMqVjAulixiKaZnO3QCekrDWiy7MZdMirugzhcixFZxDeeCNclJNrH4S04H4pet272e2UatXDS2ndJ/zO1U/rdznKMKhDKFfWQ9JXkplUc1zAnFAqygWVYY9KWmXkvj2jHDtux+06WjUUvp5T2peS291wd5PhUczWFr1DBYnGaejjey/bNOW2z7nJbluWNvOvT+tboPt0C7aYGOboH2yGCByDmWBbp6nIRC8Qrfgxu2rapKvsbBvSw/beui8KGP5nHGCGqMoqZIaoymekRtDCtinGGHNmjCfB1E+rAqLOqgCTPlAO3ZyDAbtWZ3R+BD02LoG4wn9mi81zeLIWaq7DaV7lPPmxHyZqA8g30tis+hyXMShyE1Rca6oxUcvyV7Nsb8KhcuIz5kC5kp9mdA7qTgvDOhrXvgpRm24rb9sk7CNWTT7pFchKe5iMot6NH72AWd5fnn3vC/V9DVBJwnhWxruj0r/TicanVgUVwb6VwzVW5HSwj9Tq7p6cA7194Y10avi2vDTq4571x741yra9tXwjWku68TFLrfuWititGnq1b3NFUr8vxW1eo4qG+3nimWxz1a4VpJZb+EEljIYilY4jQCBdpe8SzpDkoqL5JXPxSW8+PJ3cx3eB5+eEr/Ao/ED07fXUtzRSvCY+AC4Tc17BwzGg5aj9Ctl3ZR+qOATzko5g1y9Iih8GA+cpDd/KQE8k9HIOOrP1vnT/UiyrauGY2D7enJdG4NkfdayfR8bwfOKomzDE6Y9Q58y9fNuk6Kec0kytMJBuHWH53QSdVfUXnhPOrglMnVU6bH3psfO2NyfLcPtYbvDgfW0HId1Mye2np7duoEzd33hcrhu29dOWf/AA== \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio b/modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio new file mode 100644 index 00000000..1228668f --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio.png b/modules/sagemaker/sagemaker-model-package-group/docs/_static/architecture.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..398149a5a8ee501c7839bfb134f0849d57521119 GIT binary patch literal 41135 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

2hf5*wH#(I>i+S)FI{Wkw+#cP0 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 MqN)5|(d5~`0hS&ep#T5? literal 0 HcmV?d00001 diff --git a/modules/sagemaker/sagemaker-model-package-event/pyproject.toml b/modules/sagemaker/sagemaker-model-package-group/pyproject.toml similarity index 100% rename from modules/sagemaker/sagemaker-model-package-event/pyproject.toml rename to modules/sagemaker/sagemaker-model-package-group/pyproject.toml diff --git a/modules/sagemaker/sagemaker-model-package-event/requirements.txt b/modules/sagemaker/sagemaker-model-package-group/requirements.txt similarity index 100% rename from modules/sagemaker/sagemaker-model-package-event/requirements.txt rename to modules/sagemaker/sagemaker-model-package-group/requirements.txt diff --git a/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/__init__.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/__init__.py similarity index 100% rename from modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/__init__.py rename to modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/__init__.py diff --git a/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/settings.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py similarity index 87% rename from modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/settings.py rename to modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py index 3775ab75..e9be331b 100644 --- a/modules/sagemaker/sagemaker-model-package-event/sagemaker_model_package_event/settings.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py @@ -1,6 +1,6 @@ """Defines the stack settings.""" from abc import ABC -from typing import Optional +from typing import List, Optional from pydantic import Field, computed_field from pydantic_settings import BaseSettings, SettingsConfigDict @@ -26,9 +26,12 @@ class SeedFarmerParameters(CdkBaseSettings): model_config = SettingsConfigDict(env_prefix="SEEDFARMER_PARAMETER_") - target_event_bus_name: str - target_account_id: str model_package_group_name: str + + retain_on_delete: bool = Field(default=True) + target_event_bus_arn: Optional[str] = Field(default=None) + model_package_group_description: Optional[str] = Field(default=None) + target_account_ids: Optional[List[str]] = Field(default=None) sagemaker_project_id: Optional[str] = Field(default=None) sagemaker_project_name: Optional[str] = Field(default=None) diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py new file mode 100644 index 00000000..bfdecf75 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -0,0 +1,220 @@ +"""Seedfarmer module to deploy a SageMaker Model Package Group.""" +from typing import Any, List, Optional + +import aws_cdk.aws_events as events +import aws_cdk.aws_events_targets as events_targets +import aws_cdk.aws_sagemaker as sagemaker +from aws_cdk import CfnOutput, RemovalPolicy, Stack, Tags +from aws_cdk import aws_iam as iam +from constructs import Construct + + +class SagemakerModelPackageGroupStack(Stack): + """Create a Sagemaker Model Package Group.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + model_package_group_name: str, + retain_on_delete: bool = True, + target_event_bus_arn: Optional[str] = None, + model_package_group_description: Optional[str] = None, + target_account_ids: Optional[List[str]] = None, + sagemaker_project_id: Optional[str] = None, + sagemaker_project_name: Optional[str] = None, + **kwargs: Any, + ) -> None: + """Deploy a Sagemaker Model Package. + + Parameters + ---------- + scope + Parent of this stack, usually an ``App`` or a ``Stage``, but could be any construct + construct_id + The construct ID of this stack + model_package_group_name + The Model Package Group name. + retain_on_delete + Wether or not to retain resources on delete. Defaults True. + target_event_bus_arn + The event bus arn in to send events model package group state change events to. + It can be a bus located in another account. Defaults None. + model_package_group_description + The model package group description. Defaults None. + target_account_ids + The target account ids which shall have read-only access to the model package group. Defaults None. + sagemaker_project_id + SageMaker project id, defaults None + sagemaker_project_name + SageMaker project name, defaults None + """ + super().__init__(scope, construct_id, **kwargs) + self.target_event_bus_arn = target_event_bus_arn + self.target_account_ids = target_account_ids + self.model_package_group_name = model_package_group_name + + self.removal_policy = ( + RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY + ) + + self.model_package_group_description = model_package_group_description + self.sagemaker_project_name = sagemaker_project_name + self.sagemaker_project_id = sagemaker_project_id + + self.setup_resources() + + self.setup_outputs() + + self.setup_tags() + + def setup_resources(self) -> None: + """Deploy resources.""" + + self.model_package_group = self.create_model_package_group() + + self.event_rule = self.setup_events() + + def create_model_package_group(self) -> sagemaker.CfnModelPackageGroup: + """Create a Model Package Group.""" + model_package_group_policy = self.get_model_package_group_resource_policy() + + model_package_group = sagemaker.CfnModelPackageGroup( + self, + "ModelPackageGroup", + model_package_group_name=self.model_package_group_name, + model_package_group_description=self.model_package_group_description, + model_package_group_policy=model_package_group_policy, + ) + + model_package_group.apply_removal_policy(self.removal_policy) + + return model_package_group + + def get_model_package_group_resource_policy(self) -> Optional[dict]: + """Get a resource policy to enable cross account access into Sagemaker Model Package Group. + + Returns + ------- + A resource policy with cross-account read-only permissions. + """ + if not self.target_account_ids: + return None + + sagemaker_arn = f"arn:aws:sagemaker:{self.region}:{self.account}" + target_accounts = [f"arn:aws:iam::{a}:root" for a in self.target_account_ids] + + model_package_group_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ModelPackageGroupPermissionSet", + "Effect": "Allow", + "Principal": {"AWS": target_accounts}, + "Action": ["sagemaker:DescribeModelPackageGroup"], + "Resource": f"{sagemaker_arn}:model-package-group/{self.model_package_group_name}", + }, + { + "Sid": "ModelPackagePermissionSet", + "Effect": "Allow", + "Principal": {"AWS": target_accounts}, + "Action": [ + "sagemaker:DescribeModelPackage", + "sagemaker:ListModelPackages", + "sagemaker:CreateModel", + ], + "Resource": f"{sagemaker_arn}:model-package/{self.model_package_group_name}/*", + }, + ], + } + + return model_package_group_policy + + def setup_events(self) -> Optional[events.Rule]: + """Setup an event rule + + The event rule will send SageMaker Model Package State Change events to another EventBus. + + Returns + ------- + An event rule + """ + if not self.target_event_bus_arn: + return None + + rule = events.Rule( + self, + "SageMakerModelPackageStateChangeRule", + event_pattern=events.EventPattern( + source=["aws.sagemaker"], + detail_type=["SageMaker Model Package State Change"], + detail={ + "ModelPackageGroupName": [self.model_package_group_name], + "ModelApprovalStatus": ["Approved", "Rejected"], + }, + ), + description=f"Rule to send events when `{self.model_package_group_name}` state changes", + ) + + target_role = iam.Role( + self, + "SageMakerModelPackageStateChangeRuleTargetRole", + assumed_by=iam.ServicePrincipal("events.amazonaws.com"), + path="/service-role/", + ) + + target_role.add_to_policy( + iam.PolicyStatement( + actions=[ + "events:PutEvents", + ], + effect=iam.Effect.ALLOW, + resources=[self.target_event_bus_arn], + ) + ) + + target = events_targets.EventBus( + event_bus=events.EventBus.from_event_bus_arn( + scope=self, id="TargetEventBus", event_bus_arn=self.target_event_bus_arn + ), + role=target_role, + ) + + rule.add_target(target) + + return rule + + def setup_tags(self) -> None: + """Add tags to all resources.""" + Tags.of(self).add("sagemaker:deployment-stage", Stack.of(self).stack_name) + + if self.sagemaker_project_id: + Tags.of(self).add("sagemaker:project-id", self.sagemaker_project_id) + + if self.sagemaker_project_name: + Tags.of(self).add("sagemaker:project-name", self.sagemaker_project_name) + + def setup_outputs(self) -> None: + """Setups outputs and metadata.""" + metadata = { + "SagemakerModelPackageGroupArn": self.model_package_group.attr_model_package_group_arn, + "SagemakerModelPackageGroupName": self.model_package_group_name, + } + + if self.event_rule: + metadata[ + "SagemakerModelPackageGroupEventRuleArn" + ] = self.event_rule.rule_arn + + for key, value in metadata.items(): + CfnOutput( + scope=self, + id=key, + value=value, + ) + + CfnOutput( + scope=self, + id="metadata", + value=self.to_json_string(metadata), + ) diff --git a/modules/sagemaker/sagemaker-model-package-event/tests/__init__.py b/modules/sagemaker/sagemaker-model-package-group/tests/__init__.py similarity index 100% rename from modules/sagemaker/sagemaker-model-package-event/tests/__init__.py rename to modules/sagemaker/sagemaker-model-package-group/tests/__init__.py diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py b/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py new file mode 100644 index 00000000..8f2dd16b --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py @@ -0,0 +1,23 @@ +import os + +import pytest + + +@pytest.fixture(scope="function") +def env_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" + os.environ["SEEDFARMER_PARAMETER_retain_on_delete"] = "False" + os.environ[ + "SEEDFARMER_PARAMETER_target_event_bus_arn" + ] = "arn:aws:events:xx-xxxxx-xx:xxxxxxxxxxxx:event-bus/default" + os.environ["SEEDFARMER_PARAMETER_model_package_group_description"] = "dummy123" + os.environ["SEEDFARMER_PARAMETER_target_account_ids"] = '["dummy123"]' + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_name"] = "dummy321" diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/test_app.py b/modules/sagemaker/sagemaker-model-package-group/tests/test_app.py new file mode 100644 index 00000000..bce0cd9d --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/tests/test_app.py @@ -0,0 +1,14 @@ +import sys + +import pytest + + +@pytest.fixture(scope="function") +def app_defaults(env_defaults): + # Unload the app import so that subsequent tests don't reuse + if "app" in sys.modules: + del sys.modules["app"] + + +def test_app(app_defaults): + import app # noqa: F401 diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py new file mode 100644 index 00000000..05f558ed --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py @@ -0,0 +1,27 @@ +import os + +import pytest +from sagemaker_model_package_group.settings import ApplicationSettings + + +def test_settings_inputs(env_defaults) -> None: + settings = ApplicationSettings() + + project_name = os.environ["SEEDFARMER_PROJECT_NAME"] + deployment_name = os.environ["SEEDFARMER_DEPLOYMENT_NAME"] + module_name = os.environ["SEEDFARMER_MODULE_NAME"] + prefix = f"{project_name}-{deployment_name}-{module_name}" + + assert settings.settings.app_prefix == prefix + + account = os.environ["CDK_DEFAULT_ACCOUNT"] + assert settings.default.account == account + + +def test_settings_required_parameters(env_defaults) -> None: + del os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] + + with pytest.raises(ValueError) as excinfo: + ApplicationSettings() + + assert "1 validation error for SeedFarmerParameters" in str(excinfo.value) diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/test_stack.py b/modules/sagemaker/sagemaker-model-package-group/tests/test_stack.py new file mode 100644 index 00000000..71dd3c78 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-group/tests/test_stack.py @@ -0,0 +1,57 @@ +import aws_cdk as cdk +from aws_cdk.assertions import Template + + +def test_synthesize_stack() -> None: + from sagemaker_model_package_group import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelPackageGroupStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + model_package_group_name="dummy123", + retain_on_delete=False, + target_event_bus_arn="arn:aws:events:xx-xxxxx-xx:xxxxxxxxxxxx:event-bus/default", + model_package_group_description="dummy123", + target_account_ids=["dummy123", "321dummy"], + sagemaker_project_id="id", + sagemaker_project_name="project-name", + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::Events::Rule", 1) + template.resource_count_is("AWS::SageMaker::ModelPackageGroup", 1) + + +def test_synthesize_stack_when_event_bus_not_defined() -> None: + from sagemaker_model_package_group import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelPackageGroupStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + model_package_group_name="dummy123", + retain_on_delete=False, + model_package_group_description="dummy123", + target_account_ids=["dummy123", "321dummy"], + sagemaker_project_id="id", + sagemaker_project_name="project-name", + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::Events::Rule", 0) + template.resource_count_is("AWS::SageMaker::ModelPackageGroup", 1) From 3eab709503eefa9037793a07f1ff71fbf70425e6 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Sat, 23 Mar 2024 10:13:07 -0300 Subject: [PATCH 03/16] Add sm model event bus module --- .../sagemaker-model-event-bus/README.md | 53 +++++++++ .../sagemaker-model-event-bus/app.py | 26 +++++ .../sagemaker-model-event-bus/deployspec.yaml | 26 +++++ .../docs/_static/architecture.drawio | 1 + .../docs/_static/architecture.drawio.png | Bin 0 -> 33427 bytes .../sagemaker-model-event-bus/pyproject.toml | 41 +++++++ .../requirements.txt | 6 + .../sagemaker_model_event_bus/__init__.py | 0 .../sagemaker_model_event_bus/settings.py | 72 ++++++++++++ .../sagemaker_model_event_bus/stack.py | 103 ++++++++++++++++++ .../tests/__init__.py | 0 .../tests/conftest.py | 17 +++ .../tests/test_app.py | 14 +++ .../tests/test_settings.py | 27 +++++ .../tests/test_stack.py | 25 +++++ 15 files changed, 411 insertions(+) create mode 100644 modules/sagemaker/sagemaker-model-event-bus/README.md create mode 100644 modules/sagemaker/sagemaker-model-event-bus/app.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml create mode 100644 modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio create mode 100644 modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio.png create mode 100644 modules/sagemaker/sagemaker-model-event-bus/pyproject.toml create mode 100644 modules/sagemaker/sagemaker-model-event-bus/requirements.txt create mode 100644 modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/tests/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py create mode 100644 modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/README.md b/modules/sagemaker/sagemaker-model-event-bus/README.md new file mode 100644 index 00000000..8e480f26 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/README.md @@ -0,0 +1,53 @@ +# SageMaker Model Event Bus + +## Description + +This module creates an Amazon EventBridge Bus for cross-account events. + +### Architecture + +![SageMaker Model Event Bus Architecture](docs/_static/architecture.drawio.png "SageMaker Model Event Bus Architecture") + +## Inputs/Outputs + +### Input Paramenters + +#### Required + +- `event_bus_name`: EventBridge Bus name. + +#### Optional + +- `source_accounts`: A list of account ids which shall have write access to the eventbridge bus. Defaults None. +- `tags`: A dictionary of tags. Defaults None. + +### Sample manifest declaration + +```yaml +name: sagemaker-model-event-bus +path: modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus +targetAccount: primary +parameters: + - name: event_bus_name + value: mlops-bus + - name: source_accounts + value: '["111222333444", "555666777888"]' + - name: tags + value: '{"key": "value"}' +``` + +### Module Metadata Outputs + +- `EventBusArn`: the EventBridge bus ARN. +- `EventBusName`: the EventBridge bus name. + +#### Output Example + +```json +{ + "EventBusArn": "arn:aws:events:xx-xxxx-x:xxxxxxxxxx:event-bus/mlops-bus", + "EventBusName": "mlops-bus", +} +``` + + diff --git a/modules/sagemaker/sagemaker-model-event-bus/app.py b/modules/sagemaker/sagemaker-model-event-bus/app.py new file mode 100644 index 00000000..d79b227d --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/app.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Create a Sagemaker Model Event Bus Stack.""" + +import aws_cdk as cdk + +from sagemaker_model_event_bus.settings import ApplicationSettings +from sagemaker_model_event_bus.stack import SagemakerModelEventBusStack + +# Load application settings from env vars. +app_settings = ApplicationSettings() + +env = cdk.Environment( + account=app_settings.default.account, + region=app_settings.default.region, +) + +app = cdk.App() + +stack = SagemakerModelEventBusStack( + scope=app, + construct_id=app_settings.settings.app_prefix, + env=env, + **app_settings.parameters.model_dump(), +) + +app.synth() diff --git a/modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml b/modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml new file mode 100644 index 00000000..c0f572cc --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml @@ -0,0 +1,26 @@ +publishGenericEnvVariables: true +deploy: + phases: + install: + commands: + - env + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk deploy --require-approval never --progress events --app "python app.py" --outputs-file ./cdk-exports.json + # Export metadata + - seedfarmer metadata convert -f cdk-exports.json || true +destroy: + phases: + install: + commands: + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk destroy --force --app "python app.py" \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio b/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio new file mode 100644 index 00000000..7ab521c0 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio @@ -0,0 +1 @@ +7Vpbc9o4FP41PIbxHfKITUi3Tbtp2dlO9oVRbGHcCIvKMpf++pVsCWNJBHZC07CFZAbr+Ojio+9837Fxx43m61sCFrOPOIGo41jJuuMOO45j216ffXHLprZ4Pbc2pCRLhFNjGGc/oDBawlpmCSxajhRjRLNF2xjjPIcxbdkAIXjVdpti1J51AVKoGcYxQLr1a5bQWW3t+1ZjfwezdCZnti1xZg6kszAUM5Dg1Y7Jvem4EcGY1kfzdQQRD56MS91vtOfsdmEE5vSYDt8+p/H9YDkduGH/LkjourQersQoS4BKccGDr2NmGMQxLtm49crpRoZjgbOcViH1Q/bPZoysjs/ORLzVdXzFoLZ7bYOtt/gYbYPa7rUNtjq8rcxvqwvcMWit1vCWMr+1s0D274a4pCjLYbQFn8WMKQFJxjYlwggTZstxzqIXzugcsZbNDlezjMLxAsQ8qiuWOMw2xTkV8Lcd2RaB56MygFPA5iJijGonILlZwnpDah+EwKLIHre9CIxLUmRL+AUW9eDcyqC44MfzdcqztgtWhddNCS4X1fL/YHMZz07Y4SRGuEwmAFE+ECX4CcoL7Tgu+xtx+IXTDCElAEtIaMYya4CylI9PMZ8OiBaC02pEFpUsT++q1tC1RCRMUySgmMFEXJKeDRLabFa43jGJ7LiFeA4p2TAXcdaVSS2oSjZXTd47MrtnOznvygwHgmvS7dBNOrIDkZF7snOJ4M37+O774Gmw/vihWP91i65cLTu1hDTEWduUaOgEQbB3B1SkKfH2B0HUD7Yh1uLZjvp+ltkbd0cJu6+F3Q4MYXds/yeF3TOEPUACnXkr/sH3kvN3yEJCrwSUB8xDoLlxYEcp/67yNWRSx2THsaoW+w7LQk7BVlzPUvtrG148QRrPxFYZ6dhIySZaNlKzTs8tt4owDTOoRpOtpxtt3U1yrG402UyCova2Db1tpfd+Ot9HPyrNs3OjkTfqhzvnhhkjYJpVdJpjwvHcYkbWJ4xs1w9MaTutPirRyRy+A48Q3eMiE8PPsyRBJprdnlCYdkePDmkPKBZ1PKbZmi/ELCAEFrgkMazlg0lPYRISyCH/WCeAGyLlKkid34oMOL+UfRzfQD4n4J74A3g/nz2FydPD6mMwgJMp+mEoyCRHPHKOeI4MXoReoR0qOC2r7w01cArno3D5iCnF84PyH0NezbwQlmpNZO+D6g4IJwmcghLRSWWbsChPJJD5iJBkbGMhGdeAZAN5xmrj2er66BKk53Wv+y082tdW19r52J6GT595uIayxPtJ8hhoEP0iA+ZY9xhl8eb1gTqy+rbOomcPVIa/eVYUbIVFw4qtkvf0jNj3uvw2toFcG5C2Xha7drd3rSPQO0FdvLaG9/Ok9zn6B7yL0J8Pn9LobwNJjiX+lNtXufjLLezlFvY3uIW1vYO3sHbPcC+1fWp1+lS19hU0l1LmdKWMoZLerqINR0uTkaNLnGep+GiIBn4Loa6OUM8A0FNIibGY6R0vJfrDl4uUXKTkDUvJgdLwv0nJ9hncq2iJMVdt56IlZ6wlh+5U9tLzuWjJ9fFa8umiJRct+W21xOn9ei3Rf1u7aMn/W0uuz0lL5HPeHSTCJIVyb/hvSTjFOUA3jTVkTJInW7g0PneYA6Haq2+Q0o3YcVBS3IZDPSdMtHditGcS8gn5wQSjgKRQ9N3/i4e+LQQiQBm9tmY1hbnqOiAEbHYchJo2I99zQ7PbrttmJFd9eeaAv2SwZovrFTQbvr2UF2DAP3cMOG8aA8pPf/Kds70YUP2D18BAcD4YePYB4BvFgJLXsuA4mgf8F2GANZsXAGv35jVK9+Zf \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio.png b/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..869bd68e2898239fa1b138813435b0a64a3edeaa GIT binary patch literal 33427 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 sll@zm2Llv-R4b|X=RGo=PS9Jl!jSEx({g|>b str: + """Application prefix.""" + prefix = "-".join([self.project_name, self.deployment_name, self.module_name]) + return prefix + + +class CdkDefaultSettings(CdkBaseSettings): + """CDK Default Settings. + + These parameters comes from AWS CDK by default. + """ + + model_config = SettingsConfigDict(env_prefix="CDK_DEFAULT_") + + account: str + region: str + + +class ApplicationSettings(CdkBaseSettings): + """Application settings.""" + + settings: SeedFarmerSettings = Field(default_factory=SeedFarmerSettings) + parameters: SeedFarmerParameters = Field(default_factory=SeedFarmerParameters) + default: CdkDefaultSettings = Field(default_factory=CdkDefaultSettings) diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py new file mode 100644 index 00000000..65f3ed52 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py @@ -0,0 +1,103 @@ +"""Seedfarmer module to deploy an EventBridge Bus for SageMaker Model Package events.""" +from typing import Any, Dict, List, Optional + +import aws_cdk.aws_events as events +from aws_cdk import CfnOutput, Stack, Tags +from aws_cdk import aws_iam as iam +from constructs import Construct + + +class SagemakerModelEventBusStack(Stack): + """Create an EventBridge Bus.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + event_bus_name: str, + source_accounts: Optional[List[str]] = None, + tags: Optional[Dict[str, str]] = None, + **kwargs: Any, + ) -> None: + """Deploy an EventBridge Bus. + + Parameters + ---------- + scope + Parent of this stack, usually an ``App`` or a ``Stage``, but could be any construct + construct_id + The construct ID of this stack + event_bus_name + The EventBridge Bus name. + source_accounts, optional + A list of account ids to grant cross account put events permission. Defaults None. + tags, optional + Extra tags to apply to the EventBridge bus, by default None + """ + super().__init__(scope, construct_id, **kwargs) + self.event_bus_name = event_bus_name + self.source_accounts = source_accounts + self.additional_tags = tags + + self.setup_resources() + + self.setup_outputs() + + self.setup_tags() + + def setup_resources(self) -> None: + """Deploy resources.""" + + self.event_bus = self.create_event_bus() + + def create_event_bus(self) -> events.EventBus: + """Create an Amazon EventBridge bus.""" + event_bus = events.EventBus( + scope=self, + id="EventBus", + event_bus_name=self.event_bus_name, + ) + + if not self.source_accounts: + return event_bus + + principals = [iam.AccountPrincipal(i) for i in self.source_accounts] + + event_bus.add_to_resource_policy( + iam.PolicyStatement( + sid="GrantCrossAccountPermissionsToPutEvents", + actions=["events:PutEvents"], + effect=iam.Effect.ALLOW, + resources=[event_bus.event_bus_arn], + principals=principals, + ) + ) + + return event_bus + + def setup_tags(self) -> None: + """Add tags to all resources.""" + Tags.of(self).add("sagemaker:deployment-stage", Stack.of(self).stack_name) + + for k, v in (self.additional_tags or {}).items(): + Tags.of(self).add(k, v) + + def setup_outputs(self) -> None: + """Setups outputs and metadata.""" + metadata = { + "EventBusArn": self.event_bus.event_bus_arn, + "EventBusName": self.event_bus.event_bus_name, + } + + for key, value in metadata.items(): + CfnOutput( + scope=self, + id=key, + value=value, + ) + + CfnOutput( + scope=self, + id="metadata", + value=self.to_json_string(metadata), + ) diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/__init__.py b/modules/sagemaker/sagemaker-model-event-bus/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py b/modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py new file mode 100644 index 00000000..f7a1e51e --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py @@ -0,0 +1,17 @@ +import os + +import pytest + + +@pytest.fixture(scope="function") +def env_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ["SEEDFARMER_PARAMETER_event_bus_name"] = "dummy123" + os.environ["SEEDFARMER_PARAMETER_source_accounts"] = '["dummy123"]' + os.environ["SEEDFARMER_PARAMETER_tags"] = '{"dummy321": "dummy321"}' diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py b/modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py new file mode 100644 index 00000000..bce0cd9d --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py @@ -0,0 +1,14 @@ +import sys + +import pytest + + +@pytest.fixture(scope="function") +def app_defaults(env_defaults): + # Unload the app import so that subsequent tests don't reuse + if "app" in sys.modules: + del sys.modules["app"] + + +def test_app(app_defaults): + import app # noqa: F401 diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py b/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py new file mode 100644 index 00000000..020cfd91 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py @@ -0,0 +1,27 @@ +import os + +import pytest +from sagemaker_model_event_bus.settings import ApplicationSettings + + +def test_settings_inputs(env_defaults) -> None: + settings = ApplicationSettings() + + project_name = os.environ["SEEDFARMER_PROJECT_NAME"] + deployment_name = os.environ["SEEDFARMER_DEPLOYMENT_NAME"] + module_name = os.environ["SEEDFARMER_MODULE_NAME"] + prefix = f"{project_name}-{deployment_name}-{module_name}" + + assert settings.settings.app_prefix == prefix + + account = os.environ["CDK_DEFAULT_ACCOUNT"] + assert settings.default.account == account + + +def test_settings_required_parameters(env_defaults) -> None: + del os.environ["SEEDFARMER_PARAMETER_event_bus_name"] + + with pytest.raises(ValueError) as excinfo: + ApplicationSettings() + + assert "1 validation error for SeedFarmerParameters" in str(excinfo.value) diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py b/modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py new file mode 100644 index 00000000..b3057cb4 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py @@ -0,0 +1,25 @@ +import aws_cdk as cdk +from aws_cdk.assertions import Template + + +def test_synthesize_stack() -> None: + from sagemaker_model_event_bus import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelEventBusStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + event_bus_name="dummy123", + source_accounts=["dummy123", "321dummy"], + tags={"tag": "1"}, + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::Events::EventBus", 1) From 996192827d252541eaa09bfca861035a2cd4cdfe Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Sat, 23 Mar 2024 10:13:29 -0300 Subject: [PATCH 04/16] Refactoring --- .../docs/_static/.$architecture.drawio.bkp | 1 - .../sagemaker_model_package_group/stack.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp diff --git a/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp b/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp deleted file mode 100644 index 70d33110..00000000 --- a/modules/sagemaker/sagemaker-model-package-group/docs/_static/.$architecture.drawio.bkp +++ /dev/null @@ -1 +0,0 @@ -7Vpbc6M2FP41nmkf6kFgwH70Jcm2TdrsptNt+uJRQMZsBPIIEdv763sEAhskEje7dpI2TiZBn66c850buOdMk80Fx6vlFQsJ7dlWuOk5s55to4HtwT+JbEtk6NolEPE4VIN2wE38lSjQUmgehyRrDBSMURGvmmDA0pQEooFhztm6OWzBaHPXFY6IBtwEmOro5zgUy+ourB3+gcTRstoZWaonwdVgBWRLHLL1HuSc9ZwpZ0yUV8lmSqgUXiWXct55R299ME5ScciELx+j4Hr8sBg7k+GlF4pNbt3+pFZ5wDRXN/wH5hERgI0/38i/QcBy2KC8BbGt5LJicSoK2boT+IWtp1bPhZ6pbPVttwW0234TQHpLrtEE2m2/CaD28qi1P2ofcA/QWo3lrdb+1t4B4deZsFzQOCXTmoUWgBHHYQzamTLKOGApS0F6k6VIKLQQXK6XsSA3KxxIqa7BggBbsFQoO0B21VaCl6sC0wWGvbhao9AE4WcPpFRIOYZSvMriu3oWJ0HOs/iBfCJZubhEgZMreZ1sImm+fbzOBv2Is3xVHP9n2MvYO4fLeUBZHs4xFXIhwdk9qW60Zzvwcy55OFnElLYE8EC4iMHExjSO5PqCye2walGyKFYEqcRpdFm0Zo6lJGHaIsTZkoTqlnSzqDgOu5LNHqTM5IKwhAi+hSGqd1A5H+WzKmNf7xwAqrDlnvE7AwVi5XSieumdXcKFMk2zmQa/4l+S5f0kvL9dX3ljMl/QrwYzLdQN0F2eabaZ3RMRLJU8jMzskmQHY/d1CMMnljUczDStq8ENbVSqvsR3hF6zLBZxQak7JgRLnuRCQCS1mzbzlH3gbFXe6CLeyHNoBoK6iE/kiDsIQxGZh2SBcyrmBTYHKc85yVjOpaVOOgjVpF23v+0knus2eGf7Xh+4WH+QRsOBgYW230fDb+fhxppdJ6H/cfo3/jClv9/+Fk3/NPDwppTJe7h4Dxf/23DxZLRwjhYtzFaKNPsj4NMqVjAulixiKaZnO3QCekrDWiy7MZdMirugzhcixFZxDeeCNclJNrH4S04H4pet272e2UatXDS2ndJ/zO1U/rdznKMKhDKFfWQ9JXkplUc1zAnFAqygWVYY9KWmXkvj2jHDtux+06WjUUvp5T2peS291wd5PhUczWFr1DBYnGaejjey/bNOW2z7nJbluWNvOvT+tboPt0C7aYGOboH2yGCByDmWBbp6nIRC8Qrfgxu2rapKvsbBvSw/beui8KGP5nHGCGqMoqZIaoymekRtDCtinGGHNmjCfB1E+rAqLOqgCTPlAO3ZyDAbtWZ3R+BD02LoG4wn9mi81zeLIWaq7DaV7lPPmxHyZqA8g30tis+hyXMShyE1Rca6oxUcvyV7Nsb8KhcuIz5kC5kp9mdA7qTgvDOhrXvgpRm24rb9sk7CNWTT7pFchKe5iMot6NH72AWd5fnn3vC/V9DVBJwnhWxruj0r/TicanVgUVwb6VwzVW5HSwj9Tq7p6cA7194Y10avi2vDTq4571x741yra9tXwjWku68TFLrfuWititGnq1b3NFUr8vxW1eo4qG+3nimWxz1a4VpJZb+EEljIYilY4jQCBdpe8SzpDkoqL5JXPxSW8+PJ3cx3eB5+eEr/Ao/ED07fXUtzRSvCY+AC4Tc17BwzGg5aj9Ctl3ZR+qOATzko5g1y9Iih8GA+cpDd/KQE8k9HIOOrP1vnT/UiyrauGY2D7enJdG4NkfdayfR8bwfOKomzDE6Y9Q58y9fNuk6Kec0kytMJBuHWH53QSdVfUXnhPOrglMnVU6bH3psfO2NyfLcPtYbvDgfW0HId1Mye2np7duoEzd33hcrhu29dOWf/AA== \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index bfdecf75..98489eb3 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -35,18 +35,18 @@ def __init__( The construct ID of this stack model_package_group_name The Model Package Group name. - retain_on_delete + retain_on_delete, optional Wether or not to retain resources on delete. Defaults True. - target_event_bus_arn + target_event_bus_arn, optional The event bus arn in to send events model package group state change events to. It can be a bus located in another account. Defaults None. - model_package_group_description + model_package_group_description, optional The model package group description. Defaults None. - target_account_ids + target_account_ids, optional The target account ids which shall have read-only access to the model package group. Defaults None. - sagemaker_project_id + sagemaker_project_id, optional SageMaker project id, defaults None - sagemaker_project_name + sagemaker_project_name, optional SageMaker project name, defaults None """ super().__init__(scope, construct_id, **kwargs) From 93a7519ec35e7f4aa953e3aa1228f4c27bebeffd Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Tue, 16 Apr 2024 10:40:43 -0300 Subject: [PATCH 05/16] Add sm model pkg promote module --- examples/manifests/deployment.yaml | 13 + .../manifests/sagemaker-model-event-bus.yaml | 10 + ...sagemaker-model-package-group-modules.yaml | 33 ++ ...odel-package-promote-pipeline-modules.yaml | 24 + .../sagemaker-model-event-bus/README.md | 2 +- .../sagemaker_model_event_bus/stack.py | 16 +- .../sagemaker_model_package_group/stack.py | 16 +- .../README.md | 61 +++ .../app.py | 28 + .../deployspec.yaml | 26 + .../docs/_static/architecture.drawio | 1 + .../docs/_static/architecture.drawio.png | Bin 0 -> 249616 bytes .../images/zip-image/Dockerfile | 3 + .../pyproject.toml | 41 ++ .../requirements.txt | 9 + .../__init__.py | 0 .../seed_code/app.py | 25 + .../seed_code/requirements.txt | 8 + .../seed_code/script/get_model.py | 223 ++++++++ .../seed_code/script/requirements.txt | 2 + .../seed_code/stack/__init__.py | 0 .../seed_code/stack/models.py | 397 ++++++++++++++ .../seed_code/stack/settings.py | 52 ++ .../seed_code/stack/stack.py | 202 +++++++ .../settings.py | 77 +++ .../stack.py | 496 ++++++++++++++++++ .../tests/__init__.py | 0 .../tests/test_app.py | 27 + .../tests/test_settings.py | 58 ++ .../tests/test_stack.py | 54 ++ 30 files changed, 1879 insertions(+), 25 deletions(-) create mode 100644 examples/manifests/sagemaker-model-event-bus.yaml create mode 100644 examples/manifests/sagemaker-model-package-group-modules.yaml create mode 100644 examples/manifests/sagemaker-model-package-promote-pipeline-modules.yaml create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/README.md create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/app.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/deployspec.yaml create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio.png create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/images/zip-image/Dockerfile create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/pyproject.toml create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/requirements.txt create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/app.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/requirements.txt create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/requirements.txt create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/__init__.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py create mode 100644 modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_stack.py diff --git a/examples/manifests/deployment.yaml b/examples/manifests/deployment.yaml index cf35d2e3..808345c2 100644 --- a/examples/manifests/deployment.yaml +++ b/examples/manifests/deployment.yaml @@ -8,6 +8,12 @@ groups: path: examples/manifests/networking-modules.yaml - name: sagemaker-endpoints path: examples/manifests/sagemaker-endpoints-modules.yaml + - name: events + path: manifests/sagemaker-model-event-bus.yaml + - name: registry + path: manifests/sagemaker-model-package-group-modules.yaml + - name: promote-models + path: manifests/sagemaker-model-package-promote-pipeline-modules.yaml targetAccountMappings: - alias: primary accountId: @@ -17,3 +23,10 @@ targetAccountMappings: regionMappings: - region: us-east-1 default: true + - alias: tooling + accountId: + valueFrom: + envVariable: TOOLING_ACCOUNT + regionMappings: + - region: us-east-1 + default: true \ No newline at end of file diff --git a/examples/manifests/sagemaker-model-event-bus.yaml b/examples/manifests/sagemaker-model-event-bus.yaml new file mode 100644 index 00000000..cf017398 --- /dev/null +++ b/examples/manifests/sagemaker-model-event-bus.yaml @@ -0,0 +1,10 @@ +name: event-bus +path: modules/sagemaker/sagemaker-model-event-bus +targetAccount: tooling +parameters: + - name: event_bus_name + value: mlops-bus + - name: source_accounts + value: '["123123123123"]' # Accounts that must have permissions to put events (source accounts) + - name: tags + value: '{"test": "test"}' \ No newline at end of file diff --git a/examples/manifests/sagemaker-model-package-group-modules.yaml b/examples/manifests/sagemaker-model-package-group-modules.yaml new file mode 100644 index 00000000..3c561816 --- /dev/null +++ b/examples/manifests/sagemaker-model-package-group-modules.yaml @@ -0,0 +1,33 @@ +name: source-model-package-group +path: modules/sagemaker/sagemaker-model-package-group +targetAccount: primary +parameters: + - name: model_package_group_name + value: mlops-test-model-group-source + - name: target_event_bus_arn + valueFrom: + moduleMetadata: + group: events + name: event-bus + key: EventBusArn + - name: target_account_ids + value: '["444333222555"]' # Accounts that must have read-only permissions on the model pkg group + - name: sagemaker_project_id + value: 123123 + - name: sagemaker_project_name + value: test +--- +name: target-model-package-group +path: modules/sagemaker/sagemaker-model-package-group +targetAccount: tooling +parameters: + - name: model_package_group_name + value: mlops-test-model-group-tooling + - name: model_package_group_description + value: Test model package group module - Target + - name: target_account_ids + value: '["111222333444"]' # Accounts that must have read-only permissions on the model pkg group + - name: sagemaker_project_id + value: 123123 + - name: sagemaker_project_name + value: test \ No newline at end of file diff --git a/examples/manifests/sagemaker-model-package-promote-pipeline-modules.yaml b/examples/manifests/sagemaker-model-package-promote-pipeline-modules.yaml new file mode 100644 index 00000000..0b77e2e9 --- /dev/null +++ b/examples/manifests/sagemaker-model-package-promote-pipeline-modules.yaml @@ -0,0 +1,24 @@ +name: rappi-b2 +path: modules/sagemaker/sagemaker-model-package-promote-pipeline +targetAccount: tooling +parameters: + - name: source_model_package_group_arn + valueFrom: + moduleMetadata: + group: registry + name: source-model-package-group + key: SagemakerModelPackageGroupArn + - name: target_bucket_name + value: my-bucket-name + - name: event_bus_name + valueFrom: + moduleMetadata: + group: events + name: event-bus + key: EventBusName + - name: target_model_package_group_name + valueFrom: + moduleMetadata: + group: registry + name: target-model-package-group + key: SagemakerModelPackageGroupName \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-event-bus/README.md b/modules/sagemaker/sagemaker-model-event-bus/README.md index 8e480f26..12c2053c 100644 --- a/modules/sagemaker/sagemaker-model-event-bus/README.md +++ b/modules/sagemaker/sagemaker-model-event-bus/README.md @@ -18,7 +18,7 @@ This module creates an Amazon EventBridge Bus for cross-account events. #### Optional -- `source_accounts`: A list of account ids which shall have write access to the eventbridge bus. Defaults None. +- `source_accounts`: A list of account ids which shall have write access to the event bridge bus. Defaults None. - `tags`: A dictionary of tags. Defaults None. ### Sample manifest declaration diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py index 65f3ed52..be9fa210 100644 --- a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py +++ b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py @@ -48,9 +48,9 @@ def __init__( def setup_resources(self) -> None: """Deploy resources.""" - self.event_bus = self.create_event_bus() + self.event_bus = self.setup_event_bus() - def create_event_bus(self) -> events.EventBus: + def setup_event_bus(self) -> events.EventBus: """Create an Amazon EventBridge bus.""" event_bus = events.EventBus( scope=self, @@ -90,14 +90,6 @@ def setup_outputs(self) -> None: } for key, value in metadata.items(): - CfnOutput( - scope=self, - id=key, - value=value, - ) + CfnOutput(scope=self, id=key, value=value) - CfnOutput( - scope=self, - id="metadata", - value=self.to_json_string(metadata), - ) + CfnOutput(scope=self, id="metadata", value=self.to_json_string(metadata)) diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index 98489eb3..e9794d50 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -71,11 +71,11 @@ def __init__( def setup_resources(self) -> None: """Deploy resources.""" - self.model_package_group = self.create_model_package_group() + self.model_package_group = self.setup_model_package_group() self.event_rule = self.setup_events() - def create_model_package_group(self) -> sagemaker.CfnModelPackageGroup: + def setup_model_package_group(self) -> sagemaker.CfnModelPackageGroup: """Create a Model Package Group.""" model_package_group_policy = self.get_model_package_group_resource_policy() @@ -207,14 +207,6 @@ def setup_outputs(self) -> None: ] = self.event_rule.rule_arn for key, value in metadata.items(): - CfnOutput( - scope=self, - id=key, - value=value, - ) + CfnOutput(scope=self, id=key, value=value) - CfnOutput( - scope=self, - id="metadata", - value=self.to_json_string(metadata), - ) + CfnOutput(scope=self, id="metadata", value=self.to_json_string(metadata)) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/README.md b/modules/sagemaker/sagemaker-model-package-promote-pipeline/README.md new file mode 100644 index 00000000..cd4173e7 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/README.md @@ -0,0 +1,61 @@ +# SageMaker Model Package Promote Pipeline Module + +## Description + +A Seedfarmer module to deploy a Pipeline to promote SageMaker Model Packages in a multi-account setup. The pipeline can be triggered through an EventBridge rule in reaction of a SageMaker Model Package Group state event change (Approved/Rejected). Once the pipeline is triggered, it will promote the latest approved model package, if one is found. + +### Architecture + +![SageMaker Model Package Pipeline](docs/_static/architecture.drawio.png "SageMaker Model Package Pipeline") + +## Inputs/Outputs + +### Input Paramenters + +#### Required + +- `source_model_package_group_arn`: The SageMaker Model Package Group ARN to get the latest approved model package. The model package can be in another account (Source AWS Account). +- `target_bucket_name`: The S3 bucket name in the target account (Target AWS Account) to store model artifacts. + +#### Optional + +- `event_bus_name`: The event bus name to listen for sagemaker model package group state changes and trigger the pipeline on Approved and Rejected states. Defaults None. The event bus must be in the target account. +- `target_model_package_group_name`: The target model package group name to register the model package being promoted, optional. Defaults None. If None, the target model package group name will be the same as the source model package group name. +- `sagemaker_project_id`: The SageMaker project id to associate with the model package group. +- `sagemaker_project_name`: The SageMaker project name to associate with the model package group. +- `kms_key_arn`: The KMS Key ARN to encrypt model artifacts. +- `retain_on_delete`: Wether or not to retain model package resources on delete. Defaults True. This applies only to the sagemaker model package resources and not to the resources in this stack. + +### Sample manifest declaration + +```yaml +name: promote-test +path: modules/sagemaker/sagemaker-model-package-promote-pipeline +targetAccount: tooling +parameters: + - name: source_model_package_group_arn + value: arn:aws:sagemaker:xx-xxxx-x:444333666777:model-package-group/my-model-pkg + - name: target_bucket_name + value: mlops-unified-modelartifacts-111222333444-xx-xxxx-x +``` + +### Module Metadata Outputs + +- `PipelineArn`: the CodePipeline ARN. +- `PipelineName`: the CodePipeline name. + +#### Optional Outputs + +- `EventRuleArn`: the Amazon EventBridge rule ARN. +- `EventRuleName`: the Amazon EventBridge rule Name. + +#### Output Example + +```json +{ + "PipelineArn":"arn:aws:codepipeline:xx-xxxx-x:1112223334444:mlops-model-package-Pipeline", + "PipelineName":"mlops-model-package-Pipeline", + "EventRuleArn":"arn:aws:events:xx-xxxx-x:1112223334444:rule/mlops-bus/mlops-SageMakerModelPackageStat-asdasdx12", + "EventRuleName":"mlops-bus|mlops-SageMakerModelPackageStat-asdasdx12" +} +``` diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/app.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/app.py new file mode 100644 index 00000000..6f896b27 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/app.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Create a Sagemaker Model Package Promote Pipeline Stack.""" + +import aws_cdk as cdk + +from sagemaker_model_package_promote_pipeline.settings import ApplicationSettings +from sagemaker_model_package_promote_pipeline.stack import ( + SagemakerModelPackagePipelineStack, +) + +# Load application settings from env vars. +app_settings = ApplicationSettings() + +env = cdk.Environment( + account=app_settings.default.account, + region=app_settings.default.region, +) + +app = cdk.App() + +stack = SagemakerModelPackagePipelineStack( + scope=app, + construct_id=app_settings.settings.app_prefix, + env=env, + **app_settings.parameters.model_dump(), +) + +app.synth() diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/deployspec.yaml b/modules/sagemaker/sagemaker-model-package-promote-pipeline/deployspec.yaml new file mode 100644 index 00000000..c0f572cc --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/deployspec.yaml @@ -0,0 +1,26 @@ +publishGenericEnvVariables: true +deploy: + phases: + install: + commands: + - env + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk deploy --require-approval never --progress events --app "python app.py" --outputs-file ./cdk-exports.json + # Export metadata + - seedfarmer metadata convert -f cdk-exports.json || true +destroy: + phases: + install: + commands: + # Install whatever additional build libraries + - npm install -g aws-cdk@2.126.0 + - pip install -r requirements.txt + build: + commands: + # execute the CDK + - cdk destroy --force --app "python app.py" \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio b/modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio new file mode 100644 index 00000000..d3f9dfc7 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio.png b/modules/sagemaker/sagemaker-model-package-promote-pipeline/docs/_static/architecture.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..8ba5e61263d5749b64ade201994e5e1ef0fcd262 GIT binary patch literal 249616 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 yq|hrpzTNbNSg< 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

OwF`!(&~RxsrvD@~>>e_Dqya^EVh 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 z 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⋘9d}ZwhWoU~1=5$v> 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%0A;_z8{>z(EUkbvW{LLl&uo~-uKzw<7>i1s~w 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 Dhyz+D literal 0 HcmV?d00001 diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/images/zip-image/Dockerfile b/modules/sagemaker/sagemaker-model-package-promote-pipeline/images/zip-image/Dockerfile new file mode 100644 index 00000000..eaa6cb07 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/images/zip-image/Dockerfile @@ -0,0 +1,3 @@ +FROM public.ecr.aws/docker/library/alpine:latest + +RUN apk --no-cache add zip make diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/pyproject.toml b/modules/sagemaker/sagemaker-model-package-promote-pipeline/pyproject.toml new file mode 100644 index 00000000..2596b06b --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/pyproject.toml @@ -0,0 +1,41 @@ +[tool.ruff] +exclude = [ + ".eggs", + ".git", + ".hg", + ".mypy_cache", + ".ruff_cache", + ".tox", + ".venv", + "_build", + "buck-out", + "build", + "dist", + "codeseeder", +] +line-length = 120 +target-version = "py38" + +[tool.ruff.lint] +select = ["F", "I", "E", "W"] +fixable = ["ALL"] + +[tool.mypy] +python_version = "3.8" +strict = true +ignore_missing_imports = true +disallow_untyped_decorators = false +exclude = "codeseeder.out/|example/|tests/|.venv" +warn_unused_ignores = false + +[tool.pytest.ini_options] +addopts = "-v --cov=. --cov-report term" +pythonpath = [ + "." +] + +[tool.coverage.run] +omit = ["tests/*"] + +[tool.coverage.report] +fail_under = 80 diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/requirements.txt b/modules/sagemaker/sagemaker-model-package-promote-pipeline/requirements.txt new file mode 100644 index 00000000..711f778f --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/requirements.txt @@ -0,0 +1,9 @@ +aws-cdk-lib~=2.126.0 +aws_cdk.aws_lambda_python_alpha~=2.126.0a0 +cdk-nag~=2.28.27 +constructs~=10.3.0 +pydantic~=2.5.3 +pydantic-settings~=2.0.3 +configobj~=5.0.8 +flatten-json~=0.1.14 +boto3~=1.34.49 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/__init__.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/app.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/app.py new file mode 100644 index 00000000..ec441a8f --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/app.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Create a Sagemaker Model Stack.""" + +import aws_cdk as cdk +from stack.settings import ApplicationSettings +from stack.stack import SagemakerModelPackageStack + +# Load application settings from env vars. +app_settings = ApplicationSettings() + +env = cdk.Environment( + account=app_settings.default.account, + region=app_settings.default.region, +) + +app = cdk.App() + +stack = SagemakerModelPackageStack( + scope=app, + construct_id=app_settings.parameters.app_prefix, + env=env, + **app_settings.parameters.model_dump(), +) + +app.synth() diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/requirements.txt b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/requirements.txt new file mode 100644 index 00000000..02c7baa0 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/requirements.txt @@ -0,0 +1,8 @@ +aws-cdk-lib~=2.126.0 +cdk-nag~=2.28.27 +constructs~=10.3.0 +pydantic~=2.5.3 +pydantic-settings~=2.0.3 +configobj~=5.0.8 +flatten-json~=0.1.14 +boto3~=1.34.49 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py new file mode 100644 index 00000000..da675584 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py @@ -0,0 +1,223 @@ +"""A module to get Sagemaker Models metadata and artifacts.""" +import argparse +import json +import logging +import os +import pathlib +import shutil +import tempfile +from typing import Any, Optional +from urllib.parse import unquote, urlparse + +import boto3 +from boto3.s3.transfer import TransferConfig +from flatten_json import flatten, unflatten_list + + +def get_latest_model_from_model_registry( + boto3_session: Any, + model_package_group_name: str, + status_approval: Optional[str], +) -> Optional[dict]: + """Get the latest registered model from SageMaker model registry + + Parameters + ---------- + sm_client : Any + The SageMaker boto3 client + model_package_group_name : str + The Model Package Group name to get model versions + status_approval : Optional[str] + To filter based on the model status (e.g. Approved, Rejected) + + Returns + ------- + Optional[dict] + The latest registered model. + """ + + sm_client = boto3_session.client("sagemaker") + + paginator = sm_client.get_paginator("list_model_packages") + + operator_args = { + "ModelPackageGroupName": model_package_group_name, + "SortBy": "CreationTime", + } + + if status_approval: + operator_args["ModelApprovalStatus"] = status_approval + + for p in paginator.paginate(**operator_args): + models = p["ModelPackageSummaryList"] + + approved_model_package = next(iter(models), None) + + if approved_model_package: + return sm_client.describe_model_package( + ModelPackageName=approved_model_package["ModelPackageArn"] + ) + + return None + + +def download_s3_object( + boto3_session: Any, + bucket: str, + object_key: str, + path: str = "", +) -> str: + """Download a object from S3 + + Parameters + ---------- + boto3_session : Any + The boto3 Session + bucket : str + The bucket name + object_key : str + The object key + path : str, optional + The local path to store the object, by default "" + + Returns + ------- + str + The location to the object. + """ + + s3 = boto3_session.resource("s3") + filepath = os.path.join(path, object_key) + + # Create directory + pathlib.Path(filepath).parent.mkdir(parents=True, exist_ok=True) + + # Configure multipart parameters + config = TransferConfig(multipart_threshold=1024**3, max_concurrency=100) # 1 GB + + # Download file + s3.Bucket(bucket).download_file(object_key, filepath, Config=config) + + return filepath + + +def parse_args() -> argparse.Namespace: + """Define script arguments + + Returns + ------- + ArgumentParser + Script keyword arguments. + """ + parser = argparse.ArgumentParser() + + # Required + parser.add_argument( + "-p", + "--model-artifacts-path", + type=str, + required=True, + help="The path to store model artifacts zip downloaded from S3 (e.g. '/tmp/model_artifacts').", + ) + + parser.add_argument( + "-o", + "--output-json-config", + type=str, + required=True, + help="The json file to save metadata (e.g. /tmp/model_config.json)", + ) + + parser.add_argument( + "-g", + "--model-package-group", + type=str, + required=True, + help="The model package group name", + ) + + ## Optional + parser.add_argument( + "--profile-name", + type=str, + required=False, + default=None, + help="Optional profile name for boto3 session.", + ) + + args = parser.parse_args() + + return args + + +def main(): + args = parse_args() + + boto3_session = boto3.Session(profile_name=args.profile_name) + logging.getLogger().setLevel(logging.INFO) + + # Get model metadata + logging.info(f"Getting model metadata from {args.model_package_group}") + + model_metadata = get_latest_model_from_model_registry( + boto3_session=boto3_session, + model_package_group_name=args.model_package_group, + status_approval="Approved", + ) + + logging.debug(f"Received response from sagemaker: {model_metadata}") + + if not model_metadata: + raise RuntimeError(f"Model metadata not found for `{args.model_package_group}`") + + # Get model artifacts + logging.info("Downloading model artifacts") + flatten_metadata = flatten(model_metadata, separator="___") + + tmp_dir = os.path.join( + tempfile.gettempdir(), "model_artifacts", args.model_package_group + ) + downloaded_files = [] + for k, v in flatten_metadata.items(): + if not (isinstance(v, str) and v.startswith("s3://")): + continue + if v in downloaded_files: + continue + + # Download model artifacts from S3 + logging.info(f"Downloading: {v}") + + url = urlparse(v) + + bucket, object_key = url.netloc, unquote(url.path[1:]) + + download_s3_object( + boto3_session=boto3_session, + bucket=bucket, + object_key=object_key, + path=tmp_dir, + ) + downloaded_files.append(v) + + # Replace bucket name with template variable + flatten_metadata[k] = os.path.join("s3://", "${bucket_name}", object_key) + + # Zip model artifacts. We need to zip because aws CDK has a limit of 2 GB (node). + logging.info("Compressing model artifacts. This may take a while!") + shutil.make_archive(args.model_artifacts_path, "zip", tmp_dir) + + # Dump model configs + logging.info("Dumping model metadata into JSON file") + model_metadata = unflatten_list(flatten_metadata, separator="___") + + json_config = { + "Artifacts": f"{args.model_artifacts_path}.zip", + "Model": model_metadata, + } + + with open(args.output_json_config, "w+") as f: + json.dump(json_config, f, default=str) + + +if __name__ == "__main__": + main() diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/requirements.txt b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/requirements.txt new file mode 100644 index 00000000..9508db9f --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/requirements.txt @@ -0,0 +1,2 @@ +flatten-json~=0.1.14 +boto3~=1.34.49 \ No newline at end of file diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/__init__.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py new file mode 100644 index 00000000..76d8c793 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py @@ -0,0 +1,397 @@ +"""Sagemaker Model Package models.""" +from abc import ABC +from datetime import datetime +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Field, field_serializer, model_serializer +from pydantic.alias_generators import to_camel +from pydantic.config import ConfigDict + + +class CdkBaseModel(BaseModel, ABC): + """Defines common configuration for model.""" + + model_config = ConfigDict( + # alias_generator=to_pascal, + populate_by_name=True, + protected_namespaces=(), + extra="ignore", + ) + + +class CdkModel(CdkBaseModel, ABC): + """Defines common configuration for CdkModel.""" + + @model_serializer + def ser_model(self) -> Dict[str, Any]: + """Convert field names to camelCase.""" + d = {to_camel(f): getattr(self, f) for f in self.model_fields.keys()} + return d + + +class ModelPackageContainerDefinitionProperty(CdkModel): + """ModelPackageContainerDefinitionProperty.""" + + image: str = Field(alias="Image") + + container_hostname: Optional[str] = Field(default=None, alias="ContainerHostname") + + environment: Optional[Dict[str, str]] = Field(default=None, alias="Environment") + + framework: Optional[str] = Field(default=None, alias="Framework") + framework_version: Optional[str] = Field(default=None, alias="FrameworkVersion") + model_data_url: Optional[str] = Field(default=None, alias="ModelDataUrl") + + model_input: Optional[Any] = Field(default=None, alias="ModelInput") + nearest_model_name: Optional[str] = Field(default=None, alias="NearestModelName") + + +class InferenceSpecificationProperty(CdkModel): + """Inference Specification settings.""" + + containers: List[ModelPackageContainerDefinitionProperty] = Field( + alias="Containers" + ) + supported_content_types: List[str] = Field(alias="SupportedContentTypes") + supported_response_mime_types: List[str] = Field( + alias="SupportedResponseMIMETypes", + ) + supported_realtime_inference_instance_types: Optional[List[str]] = Field( + default=None, + alias="SupportedRealtimeInferenceInstanceTypes", + ) + supported_transform_instance_types: Optional[List[str]] = Field( + default=None, + alias="SupportedTransformInstanceTypes", + ) + + +class AdditionalInferenceSpecificationDefinitionProperty( + InferenceSpecificationProperty +): + """AdditionalInferenceSpecificationDefinitionProperty.""" + + name: str = Field(alias="Name") + description: Optional[str] = Field(default=None, alias="Description") + + +class MetricsSourceProperty(CdkModel): + """MetricsSourceProperty.""" + + s3_uri: str = Field(alias="S3Uri") + content_type: str = Field(alias="ContentType") + + +class FileSourceProperty(CdkModel): + """FileSourceProperty.""" + + s3_uri: str = Field(alias="S3Uri") + content_type: Optional[str] = Field(default=None, alias="ContentType") + + +class DriftCheckBiasProperty(CdkModel): + """DriftCheckBiasProperty.""" + + config_file: Optional[FileSourceProperty] = Field(default=None, alias="ConfigFile") + post_training_constraints: Optional[MetricsSourceProperty] = Field( + default=None, + alias="PostTrainingConstraints", + ) + pre_training_constraints: Optional[MetricsSourceProperty] = Field( + default=None, + alias="PreTrainingConstraints", + ) + + +class DriftCheckExplainabilityProperty(CdkModel): + """DriftCheckExplainabilityProperty.""" + + config_file: Optional[FileSourceProperty] = Field(default=None, alias="ConfigFile") + constraints: Optional[MetricsSourceProperty] = Field( + default=None, alias="Constraints" + ) + + +class DriftCheckModelDataQualityProperty(CdkModel): + """DriftCheckModelDataQualityProperty.""" + + constraints: Optional[MetricsSourceProperty] = Field( + default=None, alias="Constraints" + ) + statistics: Optional[MetricsSourceProperty] = Field( + default=None, alias="Statistics" + ) + + +class DriftCheckModelQualityProperty(CdkModel): + """DriftCheckModelQualityProperty.""" + + constraints: Optional[MetricsSourceProperty] = Field( + default=None, alias="Constraints" + ) + statistics: Optional[MetricsSourceProperty] = Field( + default=None, alias="Statistics" + ) + + +class DriftCheckBaselinesProperty(CdkModel): + """DriftCheckBaselinesProperty.""" + + bias: Optional[DriftCheckBiasProperty] = Field(default=None, alias="Bias") + explainability: Optional[DriftCheckExplainabilityProperty] = Field( + default=None, alias="Explainability" + ) + model_data_quality: Optional[DriftCheckModelDataQualityProperty] = Field( + default=None, alias="ModelDataQuality" + ) + model_quality: Optional[DriftCheckModelQualityProperty] = Field( + default=None, alias="ModelQuality" + ) + + +class MetadataPropertiesProperty(CdkModel): + """MetadataPropertiesProperty.""" + + commit_id: Optional[str] = Field(default=None, alias="CommitId") + generated_by: Optional[str] = Field(default=None, alias="GeneratedBy") + project_id: Optional[str] = Field(default=None, alias="ProjectId") + repository: Optional[str] = Field(default=None, alias="Repository") + + +class BiasProperty(CdkModel): + """BiasProperty.""" + + post_training_report: Optional[MetricsSourceProperty] = Field( + default=None, + alias="PostTrainingReport", + ) + pre_training_report: Optional[MetricsSourceProperty] = Field( + default=None, alias="PreTrainingReport" + ) + report: Optional[MetricsSourceProperty] = Field(default=None, alias="Report") + + +class ExplainabilityProperty(CdkModel): + """ExplainabilityProperty.""" + + report: Optional[MetricsSourceProperty] = Field(default=None, alias="Report") + + +class ModelDataQualityProperty(CdkModel): + """ModelDataQualityProperty.""" + + constraints: Optional[MetricsSourceProperty] = Field( + default=None, alias="Constraints" + ) + statistics: Optional[MetricsSourceProperty] = Field( + default=None, alias="Statistics" + ) + + +class ModelQualityProperty(CdkModel): + """ModelQualityProperty.""" + + constraints: Optional[MetricsSourceProperty] = Field( + default=None, alias="Constraints" + ) + statistics: Optional[MetricsSourceProperty] = Field( + default=None, alias="Statistics" + ) + + +class ModelMetricsProperty(CdkModel): + """ModelMetricsProperty.""" + + bias: Optional[BiasProperty] = Field(default=None, alias="Bias") + explainability: Optional[ExplainabilityProperty] = Field( + default=None, alias="Explainability" + ) + model_data_quality: Optional[ModelDataQualityProperty] = Field( + default=None, alias="ModelDataQuality" + ) + model_quality: Optional[ModelQualityProperty] = Field( + default=None, alias="ModelQuality" + ) + + +class ModelPackageStatusItemProperty(CdkModel): + """ModelPackageStatusItemProperty.""" + + name: str = Field(alias="Name") + status: str = Field(alias="Status") + failure_reason: Optional[str] = Field(default=None, alias="FailureReason") + + +class ModelPackageStatusDetailsProperty(CdkModel): + """ModelPackageStatusDetailsProperty.""" + + validation_statuses: Optional[List[ModelPackageStatusItemProperty]] = Field( + default=None, + alias="ValidationStatuses", + ) + + +class SourceAlgorithmProperty(CdkModel): + """SourceAlgorithmProperty.""" + + algorithm_name: str = Field(alias="AlgorithmName") + model_data_url: Optional[str] = Field(default=None, alias="ModelDataUrl") + + +class SourceAlgorithmSpecificationProperty(CdkModel): + """SourceAlgorithmSpecificationProperty.""" + + source_algorithms: List[SourceAlgorithmProperty] = Field(alias="SourceAlgorithms") + + +class S3DataSourceProperty(CdkModel): + """S3DataSourceProperty.""" + + s3_data_type: str = Field(alias="S3DataType") + s3_uri: str = Field(alias="S3Uri") + + +class DataSourceProperty(CdkModel): + """DataSourceProperty.""" + + s3_data_source: Optional[S3DataSourceProperty] = Field( + default=None, alias="S3DataSource" + ) + + +class TransformInputProperty(CdkModel): + """TransformInputProperty.""" + + data_source: Optional[DataSourceProperty] = Field(default=None, alias="DataSource") + compression_type: Optional[str] = Field(default=None, alias="CompressionType") + content_type: Optional[str] = Field(default=None, alias="ContentType") + split_type: Optional[str] = Field(default=None, alias="SplitType") + + +class TransformOutputProperty(CdkModel): + """TransformOutputProperty.""" + + s3_output_path: str = Field(alias="S3OutputPath") + accept: Optional[str] = Field(default=None, alias="Accept") + assemble_with: Optional[str] = Field(default=None, alias="AssembleWith") + kms_key_id: Optional[str] = Field(default=None, alias="KmsKeyId") + + +class TransformResourcesProperty(CdkModel): + """TransformResourcesProperty.""" + + instance_count: int = Field(alias="InstanceCount") + instance_type: str = Field(alias="InstanceType") + volume_kms_key_id: Optional[str] = Field(default=None, alias="VolumeKmsKeyId") + + +class TransformJobDefinitionProperty(CdkModel): + """TransformJobDefinitionProperty.""" + + transform_input: TransformInputProperty = Field( + default=None, alias="TransformInput" + ) + transform_output: TransformOutputProperty = Field( + default=None, alias="TransformOutput" + ) + transform_resources: TransformResourcesProperty = Field( + default=None, + alias="TransformResources", + ) + batch_strategy: Optional[str] = Field(default=None, alias="BatchStrategy") + environment: Optional[Dict[str, str]] = Field(default=None, alias="Environment") + max_concurrent_transforms: Optional[int] = Field( + default=None, + alias="MaxConcurrentTransforms", + ) + max_payload_in_mb: Optional[int] = Field(default=None, alias="MaxPayloadInMB") + + +class ValidationProfileProperty(CdkModel): + """ValidationProfileProperty.""" + + profile_name: str = Field(alias="ProfileName") + transform_job_definition: TransformJobDefinitionProperty = Field( + alias="TransformJobDefinition" + ) + + +class ValidationSpecificationProperty(CdkModel): + """ValidationSpecificationProperty.""" + + validation_profiles: List[ValidationProfileProperty] = Field( + alias="ValidationProfiles" + ) + validation_role: str = Field(alias="ValidationRole") + + +class ModelPackageProperty(CdkBaseModel): + """Model Package settings.""" + + model_package_name: Optional[str] = Field(default=None, alias="ModelPackageName") + model_package_group_name: Optional[str] = Field( + default=None, alias="ModelPackageGroupName" + ) + model_package_version: Optional[int] = Field( + default=None, alias="ModelPackageVersion" + ) + model_package_description: Optional[str] = Field( + default=None, alias="ModelPackageDescription" + ) + + inference_specification: Optional[InferenceSpecificationProperty] = Field( + default=None, alias="InferenceSpecification" + ) + + additional_inference_specifications: Optional[ + List[AdditionalInferenceSpecificationDefinitionProperty] + ] = Field(default=None, alias="AdditionalInferenceSpecifications") + approval_description: Optional[str] = Field( + default=None, alias="ApprovalDescription" + ) + certify_for_marketplace: Optional[bool] = Field( + default=None, alias="CertifyForMarketplace" + ) + customer_metadata_properties: Optional[Dict[str, str]] = Field( + default=None, alias="CustomerMetadataProperties" + ) + domain: Optional[str] = Field(default=None, alias="Domain") + drift_check_baselines: Optional[DriftCheckBaselinesProperty] = Field( + default=None, alias="DriftCheckBaselines" + ) + last_modified_time: Optional[datetime] = Field( + default=None, alias="LastModifiedTime" + ) + + metadata_properties: Optional[MetadataPropertiesProperty] = Field( + default=None, alias="MetadataProperties" + ) + model_approval_status: Optional[str] = Field( + default=None, alias="ModelApprovalStatus" + ) + model_metrics: Optional[ModelMetricsProperty] = Field( + default=None, alias="ModelMetrics" + ) + model_package_status_details: Optional[ModelPackageStatusDetailsProperty] = Field( + default=None, alias="ModelPackageStatusDetails" + ) + sample_payload_url: Optional[str] = Field(default=None, alias="SamplePayloadUrl") + source_algorithm_specification: Optional[ + SourceAlgorithmSpecificationProperty + ] = Field(default=None, alias="SourceAlgorithmSpecification") + task: Optional[str] = Field(default=None, alias="Task") + validation_specification: Optional[ValidationSpecificationProperty] = Field( + default=None, alias="ValidationSpecification" + ) + + @field_serializer("last_modified_time") + def serialize_last_modified_time(self, dt: Optional[datetime], _info): + """Convert datetime to string.""" + return dt.isoformat() if dt else None + + +class ModelMetadata(CdkBaseModel): + """Model metadata settings.""" + + artifacts: str = Field(alias="Artifacts") + model: ModelPackageProperty = Field(alias="Model") diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py new file mode 100644 index 00000000..d6c847d6 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py @@ -0,0 +1,52 @@ +"""Sagemaker Model Package settings.""" +from abc import ABC +from typing import Optional + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class CdkBaseSettings(BaseSettings, ABC): + """Defines common configuration for settings.""" + + model_config = SettingsConfigDict( + case_sensitive=False, + env_nested_delimiter="__", + protected_namespaces=(), + extra="ignore", + populate_by_name=True, + ) + + +class StackParameters(CdkBaseSettings): + """Seedfarmer Parameters. + + These parameters are required for the module stack. + """ + + app_prefix: str = Field(exclude=True) + model_metadata_path: str + bucket_name: str + + retain_on_delete: bool = Field(default=True) + model_package_group_name: Optional[str] = Field(default=None) + kms_key_arn: Optional[str] = Field(default=None) + + +class CdkDefaultSettings(CdkBaseSettings): + """CDK Default Settings. + + These parameters comes from AWS CDK by default. + """ + + model_config = SettingsConfigDict(env_prefix="CDK_DEFAULT_") + + account: str + region: str + + +class ApplicationSettings(CdkBaseSettings): + """Application settings.""" + + parameters: StackParameters = Field(default_factory=StackParameters) + default: CdkDefaultSettings = Field(default_factory=CdkDefaultSettings) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py new file mode 100644 index 00000000..8f7c2542 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py @@ -0,0 +1,202 @@ +"""Sagemaker Model Package stack.""" +import json +import string +from typing import Optional + +import aws_cdk.aws_iam as iam +import aws_cdk.aws_kms as kms +import aws_cdk.aws_s3 as s3 +import aws_cdk.aws_s3_deployment as s3deploy +import aws_cdk.aws_sagemaker as sagemaker +from aws_cdk import CfnOutput, RemovalPolicy, Stack +from constructs import Construct +from stack.models import ModelMetadata + + +class SagemakerModelPackageStack(Stack): + """Create a Sagemaker Model Package.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + model_metadata_path: str, + bucket_name: str, + retain_on_delete: bool = True, + model_package_group_name: Optional[str] = None, + kms_key_arn: Optional[str] = None, + **kwargs, + ) -> None: + """Create a Sagemaker Model Package. + + Parameters + ---------- + scope + Parent of this stack, usually an ``App`` or a ``Stage``, but could be any construct + construct_id + The construct ID of this stack + model_metadata_path + The filepath for model metadata. + bucket_name + The S3 bucket name to store model artifacts. + retain_on_delete, optional + Wether or not to retain resources on delete. Defaults True. + model_package_group_name, optional + The SageMaker model package group name to register the model package. Defaults None. If None + it will use the model package group name that is in the model metadata. + kms_key_arn, optional + A KMS Key ARN to encrypt model artifacts in S3. Defaults None. + """ + super().__init__(scope, construct_id, **kwargs) + + self.model_metadata_path = model_metadata_path + self.bucket_name = bucket_name + + self.kms_key_arn = kms_key_arn + self.retain_on_delete = retain_on_delete + self.removal_policy = ( + RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY + ) + + self.model_metadata = self.load_model_metadata() + if not self.model_metadata.model: + return + + # Upd model package group name in metadata + if model_package_group_name: + self.model_metadata.model.model_package_group_name = ( + model_package_group_name + ) + + self.setup_resources() + + self.setup_outputs() + + def setup_outputs(self) -> None: + """Setups outputs and metadata.""" + metadata = {} + if self.model_package.attr_model_package_arn: + metadata[ + "SagemakerModelPackageArn" + ] = self.model_package.attr_model_package_arn + if self.model_package.model_package_name: + metadata[ + "SagemakerModelPackageName" + ] = self.model_package.model_package_name + if self.model_package.model_package_group_name: + metadata[ + "SagemakerModelPackageGroupName" + ] = self.model_package.model_package_group_name + + for key, value in metadata.items(): + CfnOutput(scope=self, id=key, value=value) + + CfnOutput(scope=self, id="metadata", value=self.to_json_string(metadata)) + + def setup_resources(self) -> None: + """Deploy resources.""" + self.kms_key = self.setup_kms_key() + + self.model_package = self.create_model_package() + + def setup_kms_key(self) -> Optional[kms.IKey]: + """Setup a KMS Key. + + Returns + ------- + A KMS Key instance or None. + """ + if not self.kms_key_arn: + return None + + kms_key = kms.Key.from_key_arn(self, "KMSKey", key_arn=self.kms_key_arn) + + return kms_key + + def create_model_package(self) -> sagemaker.CfnModelPackage: + """Create a Model Package. + + Returns + ------- + A ModelPackage instance. + """ + artifacts = self.deploy_model_artifacts() + + model_package = sagemaker.CfnModelPackage( + self, "ModelPackage", **self.model_metadata.model.model_dump() + ) + + model_package.node.add_dependency(artifacts) + + model_package.apply_removal_policy(self.removal_policy) + + return model_package + + def deploy_model_artifacts(self) -> s3deploy.BucketDeployment: + """Upload model artifacts to S3. + + Returns + ------- + A BucketDeployment instance. + """ + bucket_deployment_role = iam.Role( + self, + "BucketDeploymentRole", + assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), + managed_policies=[ + iam.ManagedPolicy.from_aws_managed_policy_name( + "service-role/AWSLambdaVPCAccessExecutionRole" + ), + ], + ) + + self.bucket = s3.Bucket.from_bucket_name( + self, + "ModelArtifactsBucket", + bucket_name=self.bucket_name, + ) + + self.bucket.grant_read_write(bucket_deployment_role) + + encryption, kms_key_arn = s3deploy.ServerSideEncryption.AES_256, None + if self.kms_key: + self.kms_key.grant_encrypt_decrypt(bucket_deployment_role) + encryption = s3deploy.ServerSideEncryption.AWS_KMS + kms_key_arn = self.kms_key.key_arn + + deployment = s3deploy.BucketDeployment( + self, + "DeployModelArtifacts", + sources=[s3deploy.Source.asset(self.model_metadata.artifacts)], + destination_bucket=self.bucket, + extract=True, + prune=False, + retain_on_delete=self.retain_on_delete, + server_side_encryption=encryption, + server_side_encryption_aws_kms_key_id=kms_key_arn, + role=bucket_deployment_role, + ) + + return deployment + + def load_model_metadata(self) -> ModelMetadata: + """Load model metadata template from a JSON file and replace the bucket name. + + The model metadata template contains model metadata with the bucket name as a variable. The + bucket name is replaced to reflect the target S3 bucket that model artifacts are going to be + deployed. + + Returns + ------- + A ModelMetadata instance. + """ + with open(self.model_metadata_path, "r") as f: + model_config = f.read() + + model_metadata = string.Template(model_config) + model_metadata = model_metadata.safe_substitute(bucket_name=self.bucket_name) + model_metadata = json.loads(model_metadata) + + settings = ModelMetadata(**model_metadata) + + return settings diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py new file mode 100644 index 00000000..d6c11e5e --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py @@ -0,0 +1,77 @@ +"""Defines the stack settings.""" +from abc import ABC +from typing import Optional + +from pydantic import Field, computed_field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class CdkBaseSettings(BaseSettings, ABC): + """Defines common configuration for settings.""" + + model_config = SettingsConfigDict( + case_sensitive=False, + env_nested_delimiter="__", + protected_namespaces=(), + extra="ignore", + populate_by_name=True, + ) + + +class SeedFarmerParameters(CdkBaseSettings): + """Seedfarmer Parameters. + + These parameters are required for the module stack. + """ + + model_config = SettingsConfigDict(env_prefix="SEEDFARMER_PARAMETER_") + + source_model_package_group_arn: str + target_bucket_name: str + + event_bus_name: Optional[str] = Field(default=None) + target_model_package_group_name: Optional[str] = Field(default=None) + sagemaker_project_id: Optional[str] = Field(default=None) + sagemaker_project_name: Optional[str] = Field(default=None) + kms_key_arn: Optional[str] = Field(default=None) + retain_on_delete: bool = Field(default=True) + + +class SeedFarmerSettings(CdkBaseSettings): + """Seedfarmer Settings. + + These parameters comes from seedfarmer by default. + """ + + model_config = SettingsConfigDict(env_prefix="SEEDFARMER_") + + project_name: str = Field(default="") + deployment_name: str = Field(default="") + module_name: str = Field(default="") + + @computed_field # type: ignore + @property + def app_prefix(self) -> str: + """Application prefix.""" + prefix = "-".join([self.project_name, self.deployment_name, self.module_name]) + return prefix + + +class CdkDefaultSettings(CdkBaseSettings): + """CDK Default Settings. + + These parameters comes from AWS CDK by default. + """ + + model_config = SettingsConfigDict(env_prefix="CDK_DEFAULT_") + + account: str + region: str + + +class ApplicationSettings(CdkBaseSettings): + """Application settings.""" + + settings: SeedFarmerSettings = Field(default_factory=SeedFarmerSettings) + parameters: SeedFarmerParameters = Field(default_factory=SeedFarmerParameters) + default: CdkDefaultSettings = Field(default_factory=CdkDefaultSettings) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py new file mode 100644 index 00000000..a79726dc --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -0,0 +1,496 @@ +"""Seedfarmer module to deploy a Pipeline to promote SageMaker Model Packages.""" +import os +from typing import Any, Optional + +import aws_cdk as cdk +import aws_cdk.aws_codebuild as codebuild +import aws_cdk.aws_codepipeline as codepipeline +import aws_cdk.aws_codepipeline_actions as codepipeline_actions +import aws_cdk.aws_events as events +import aws_cdk.aws_events_targets as events_targets +from aws_cdk import aws_iam as iam +from aws_cdk import aws_kms as kms +from aws_cdk import aws_s3 as s3 +from aws_cdk import aws_s3_assets as s3_assets +from constructs import Construct + + +class SagemakerModelPackagePipelineStack(cdk.Stack): + """Create a Pipeline to promote Sagemaker Model Packages.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + source_model_package_group_arn: str, + target_bucket_name: str, + event_bus_name: Optional[str] = None, + target_model_package_group_name: Optional[str] = None, + sagemaker_project_id: Optional[str] = None, + sagemaker_project_name: Optional[str] = None, + kms_key_arn: Optional[str] = None, + retain_on_delete: bool = True, + **kwargs: Any, + ) -> None: + """Create a Pipeline to promote Sagemaker Model Packages. + + Parameters + ---------- + scope + Parent of this cdk.Stack, usually an ``App`` or a ``Stage``, but could be any construct + construct_id + The construct ID of this cdk.Stack + source_model_package_group_arn + The SageMaker Model Package Group ARN to get the latest approved model package + target_bucket_name + The S3 bucket name to store model artifacts + event_bus_name + The event bus name to listen for sagemaker model package group state changes and trigger + the pipeline on Approved and Rejected states. + target_model_package_group_name + The target model package group name to register the model package being promoted, optional. Defaults None. + If None, the target model package group name will be the same as the source model package group name. + sagemaker_project_id + The SageMaker project id to associate with the model package group. + sagemaker_project_name + The SageMaker project name to associate with the model package group. + kms_key_arn + The KMS Key ARN to encrypt model artifacts. + retain_on_delete + Wether or not to retain model package resources on delete. Defaults True. This applies only + to the sagemaker model package resources and not to the resources in this stack. + """ + super().__init__(scope, construct_id, **kwargs) + + self.source_model_package_group_arn = source_model_package_group_arn + self.target_bucket_name = target_bucket_name + + self.event_bus_name = event_bus_name + self.target_model_package_group_name = target_model_package_group_name + self.sagemaker_project_id = sagemaker_project_id + self.sagemaker_project_name = sagemaker_project_name + self.kms_key_arn = kms_key_arn + self.retain_on_delete = retain_on_delete + self.pipeline_name = f"{construct_id}-Pipeline" + + components = self.split_arn( + arn=self.source_model_package_group_arn, + arn_format=cdk.ArnFormat.SLASH_RESOURCE_NAME, + ) + + self.source_account = components.account + self.source_model_package_group_name = components.resource_name + + self.setup_resources() + + self.setup_outputs() + + self.setup_tags() + + def setup_resources(self) -> None: + """Deploy resources.""" + + self.target_bucket = s3.Bucket.from_bucket_name( + self, "TargetBucket", self.target_bucket_name + ) + + self.kms_key = ( + kms.Key.from_key_arn(self, "KMSKey", self.kms_key_arn) + if self.kms_key_arn + else None + ) + + self.code_asset = self.setup_code_assets() + self.pipeline = self.setup_pipeline() + self.rule = self.setup_events() + + def setup_tags(self) -> None: + """Add cdk.Tags to all resources.""" + cdk.Tags.of(self).add( + "sagemaker:deployment-stage", cdk.Stack.of(self).stack_name + ) + + if self.sagemaker_project_id: + cdk.Tags.of(self).add("sagemaker:project-id", self.sagemaker_project_id) + + if self.sagemaker_project_name: + cdk.Tags.of(self).add("sagemaker:project-name", self.sagemaker_project_name) + + def setup_outputs(self) -> None: + """Setups outputs and metadata.""" + metadata = { + "PipelineArn": self.pipeline.pipeline_arn, + "PipelineName": self.pipeline.pipeline_name, + } + + if self.rule: + metadata["EventRuleArn"] = self.rule.rule_arn + metadata["EventRuleName"] = self.rule.rule_name + + for key, value in metadata.items(): + cdk.CfnOutput(scope=self, id=key, value=value) + + cdk.CfnOutput(scope=self, id="metadata", value=self.to_json_string(metadata)) + + def setup_pipeline(self) -> codepipeline.Pipeline: + """Create a CodePipeline to promote models.""" + metadata_path = "./model/model_config.json" + artifacts_path = "./model/model_artifacts" + + pipeline = codepipeline.Pipeline( + self, + "CodePipeline", + pipeline_name=self.pipeline_name, + enable_key_rotation=True, + ) + + # Source Stage + source_stage = pipeline.add_stage(stage_name="Source") + source_artifact = codepipeline.Artifact() + source_action = codepipeline_actions.S3SourceAction( + action_name="SourceAction", + bucket=self.code_asset.bucket, + bucket_key=self.code_asset.s3_object_key, + output=source_artifact, + trigger=codepipeline_actions.S3Trigger.POLL, + ) + source_stage.add_action(source_action) + + # Build Stage + build_stage = pipeline.add_stage(stage_name="Build") + build_artifact = codepipeline.Artifact() + build_project = self.setup_pipeline_build_project( + metadata_path=metadata_path, artifacts_path=artifacts_path + ) + build_action = codepipeline_actions.CodeBuildAction( + action_name="BuildAction", + input=source_artifact, + project=build_project, + outputs=[build_artifact], + ) + + build_stage.add_action(build_action) + + # Deploy Stage + deploy_stage = pipeline.add_stage(stage_name="Deploy") + deploy_project = self.setup_pipeline_deploy_project(metadata_path=metadata_path) + deploy_action = codepipeline_actions.CodeBuildAction( + action_name="DeployAction", + input=build_artifact, + project=deploy_project, + ) + + deploy_stage.add_action(deploy_action) + + return pipeline + + def setup_pipeline_build_project( + self, metadata_path: str, artifacts_path: str + ) -> codebuild.PipelineProject: + """Setup a build project + + Parameters + ---------- + metadata_path + The path to store the metadata JSON file. + artifacts_path + The path to store model downloaded artifacts. + + Returns + ------- + A PipelineProject instance. + """ + env_vars = { + "MODEL_PACKAGE_GROUP_ARN": codebuild.BuildEnvironmentVariable( + value=self.source_model_package_group_arn + ), + "MODEL_METADATA_PATH": codebuild.BuildEnvironmentVariable( + value=metadata_path + ), + "MODEL_ARTIFACTS_PATH": codebuild.BuildEnvironmentVariable( + value=artifacts_path + ), + } + + build_spec = { + "version": "0.2", + "phases": { + "install": { + "commands": [ + "pip install -U pip", + "pip install -r script/requirements.txt", + ], + }, + "build": { + "commands": [ + "python3 script/get_model.py -p $MODEL_ARTIFACTS_PATH -o $MODEL_METADATA_PATH -g $MODEL_PACKAGE_GROUP_ARN", + ], + }, + }, + "artifacts": { + "base-directory": ".", + "files": ["**/*"], + }, + } + + role = self.get_pipeline_build_project_role() + + build_project = codebuild.PipelineProject( + self, + "BuildProject", + build_spec=codebuild.BuildSpec.from_object(build_spec), + environment_variables=env_vars, + description="Get model metadata and model artifacts from the latest approved model in a SageMaker Model Package Group.", + timeout=cdk.Duration.minutes(30), + role=role, + environment=codebuild.BuildEnvironment( + build_image=codebuild.LinuxBuildImage.STANDARD_7_0 + ), + ) + + return build_project + + def setup_pipeline_deploy_project( + self, metadata_path: str + ) -> codebuild.PipelineProject: + """Setup a deploy project + + Parameters + ---------- + metadata_path + The metadata JSON filepath. + + Returns + ------- + A PipelineProject instance. + """ + env_vars = { + "app_prefix": codebuild.BuildEnvironmentVariable( + value=f"{self.pipeline_name}-DeployProject" + ), + "model_metadata_path": codebuild.BuildEnvironmentVariable( + value=metadata_path + ), + "bucket_name": codebuild.BuildEnvironmentVariable( + value=self.target_bucket.bucket_name + ), + "retain_on_delete": codebuild.BuildEnvironmentVariable( + value=self.retain_on_delete + ), + "CDK_DEFAULT_ACCOUNT": codebuild.BuildEnvironmentVariable( + value=self.account + ), + "CDK_DEFAULT_REGION": codebuild.BuildEnvironmentVariable(value=self.region), + } + + if self.target_model_package_group_name: + env_vars["model_package_group_name"] = codebuild.BuildEnvironmentVariable( + value=self.target_model_package_group_name + ) + + if self.kms_key: + env_vars["kms_key_arn"] = codebuild.BuildEnvironmentVariable( + value=self.kms_key.key_arn + ) + + build_spec = { + "version": "0.2", + "phases": { + "build": { + "commands": [ + "pip install -U pip", + "npm install -g aws-cdk@2.126.0", + "pip install -r requirements.txt", + 'cdk deploy --require-approval never --progress events --app "python app.py"', + ] + } + }, + } + + role = self.get_pipeline_deploy_project_role() + project = codebuild.PipelineProject( + self, + "DeployProject", + build_spec=codebuild.BuildSpec.from_object(build_spec), + environment_variables=env_vars, + description="Deploy model metadata and model artifacts to another SageMaker Model Package Group.", + timeout=cdk.Duration.minutes(30), + role=role, + environment=codebuild.BuildEnvironment( + build_image=codebuild.LinuxBuildImage.STANDARD_7_0 + ), + ) + + return project + + def setup_code_assets(self) -> s3_assets.Asset: + """Deploy seed code to an S3 bucket. + + Returns + ------- + A Asset instance. + """ + zip_image = cdk.DockerImage.from_build("images/zip-image") + path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "seed_code") + + bundling = cdk.BundlingOptions( + image=zip_image, + command=[ + "sh", + "-c", + """zip -r /asset-output/code_asset.zip .""", + ], + output_type=cdk.BundlingOutput.ARCHIVED, + ) + + code_asset = s3_assets.Asset( + self, + "CodeAsset", + path=path, + bundling=bundling, + ) + + return code_asset + + def setup_events(self) -> Optional[events.Rule]: + """Setup an event rule + + The event rule will send SageMaker Model Package State Change events to another EventBus. + + Returns + ------- + An event rule or None if not event bus name is defined. + """ + if not self.event_bus_name: + return None + + eventbus = events.EventBus.from_event_bus_name( + scope=self, id="EventBus", event_bus_name=self.event_bus_name + ) + + event_pattern = events.EventPattern( + source=["aws.sagemaker"], + account=[self.source_account], + detail_type=["SageMaker Model Package State Change"], + detail={ + "ModelPackageGroupName": [self.source_model_package_group_name], + "ModelApprovalStatus": ["Approved", "Rejected"], + }, + ) + + rule = events.Rule( + self, + "SageMakerModelPackageStateChangeRule", + event_bus=eventbus, + event_pattern=event_pattern, + description="Rule to trigger a CICD pipeline when SageMaker Model Package state changes", + ) + + target_role = iam.Role( + self, + "SageMakerModelPackageStateChangeRuleTargetRole", + assumed_by=iam.ServicePrincipal("events.amazonaws.com"), + path="/service-role/", + ) + + target_role.add_to_policy( + iam.PolicyStatement( + actions=[ + "codepipeline:StartPipelineExecution", + ], + effect=iam.Effect.ALLOW, + resources=[self.pipeline.pipeline_arn], + ) + ) + + target = events_targets.CodePipeline( + pipeline=self.pipeline, + event_role=target_role, + retry_attempts=3, + ) + + rule.add_target(target) + + return rule + + def get_pipeline_build_project_role(self) -> iam.Role: + """Get an IAM Role for the build project""" + build_project_role = iam.Role( + scope=self, + id="BuildProjectRole", + assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"), + path="/service-role/", + ) + + build_project_role_policy = iam.Policy( + self, + "BuildProjectRolePolicy", + document=iam.PolicyDocument( + statements=[ + iam.PolicyStatement( + sid="GrantKMSKeyReadOnlyPermissions", + actions=[ + "kms:GenerateDataKey*", + "kms:Decrypt", + "kms:DescribeKey", + ], + effect=iam.Effect.ALLOW, + resources=[ + f"arn:{self.partition}:kms:{self.region}:{self.source_account}:key/*", + f"arn:{self.partition}:kms:{self.region}:{self.account}:key/*", + ], + ), + iam.PolicyStatement( + sid="GrantS3ReadOnlyPermissions", + actions=["s3:ListBucket", "s3:GetObject*"], + effect=iam.Effect.ALLOW, + resources=["arn:aws:s3:::*"], + conditions={ + "StringEquals": {"s3:ResourceAccount": self.source_account} + }, + ), + iam.PolicyStatement( + sid="GrantSageMakerModelPkgReadOnlyPermissions", + actions=[ + "sagemaker:DescribeModelPackageGroup", + "sagemaker:DescribeModelPackage", + "sagemaker:ListModelPackages", + ], + effect=iam.Effect.ALLOW, + resources=[ + f"arn:aws:sagemaker:{self.region}:{self.source_account}:model-package/*", + self.source_model_package_group_arn, + ], + ), + ] + ), + ) + + build_project_role_policy.attach_to_role(build_project_role) + + if self.kms_key: + self.kms_key.grant_encrypt_decrypt(build_project_role) + + return build_project_role + + def get_pipeline_deploy_project_role(self) -> iam.Role: + """Get an IAM Role for the deploy project""" + role = iam.Role( + scope=self, + id="DeployProjectRole", + assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"), + path="/service-role/", + ) + + role.add_to_policy( + iam.PolicyStatement( + sid="GrantAssumeRoleOnCDKRoles", + actions=["sts:AssumeRole"], + effect=iam.Effect.ALLOW, + resources=[ + f"arn:{self.partition}:iam::{self.account}:role/cdk-*-{self.account}-{self.region}" + ], + ) + ) + + return role diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/__init__.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py new file mode 100644 index 00000000..2561d585 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py @@ -0,0 +1,27 @@ +import os +import sys + +import pytest + + +@pytest.fixture(scope="function") +def stack_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ[ + "SEEDFARMER_PARAMETER_source_model_package_group_arn" + ] = "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + os.environ["SEEDFARMER_PARAMETER_target_bucket_name"] = "dummy321" + + # Unload the app import so that subsequent tests don't reuse + if "app" in sys.modules: + del sys.modules["app"] + + +def test_app(stack_defaults): + import app # noqa: F401 diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py new file mode 100644 index 00000000..7a1c3ff1 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py @@ -0,0 +1,58 @@ +import os +from unittest import mock + +import pytest + +from sagemaker_model_package_promote_pipeline.settings import ApplicationSettings + + +@pytest.fixture(scope="function") +def env_defaults(): + os.environ["SEEDFARMER_PROJECT_NAME"] = "test-project" + os.environ["SEEDFARMER_DEPLOYMENT_NAME"] = "test-deployment" + os.environ["SEEDFARMER_MODULE_NAME"] = "test-module" + + os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" + os.environ["CDK_DEFAULT_REGION"] = "us-east-1" + + os.environ[ + "SEEDFARMER_PARAMETER_source_model_package_group_arn" + ] = "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + os.environ["SEEDFARMER_PARAMETER_target_bucket_name"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_event_bus_name"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_target_model_package_group_name"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" + os.environ["SEEDFARMER_PARAMETER_sagemaker_project_name"] = "dummy321" + os.environ[ + "SEEDFARMER_PARAMETER_kms_key_arn" + ] = "arn:aws:xxx:us-east-1:111111111111:xxx/asd2313-asdx-123-xa-asdasd12334123" + os.environ["SEEDFARMER_PARAMETER_retain_on_delete"] = "False" + + +@mock.patch("time.time") +def test_settings_inputs(mock_time, env_defaults) -> None: + mock_time.return_value = 1234567 + + settings = ApplicationSettings() + + param_value = os.environ["SEEDFARMER_PARAMETER_source_model_package_group_arn"] + assert settings.parameters.source_model_package_group_arn == param_value + + project_name = os.environ["SEEDFARMER_PROJECT_NAME"] + deployment_name = os.environ["SEEDFARMER_DEPLOYMENT_NAME"] + module_name = os.environ["SEEDFARMER_MODULE_NAME"] + prefix = f"{project_name}-{deployment_name}-{module_name}" + assert settings.settings.app_prefix == prefix + + account = os.environ["CDK_DEFAULT_ACCOUNT"] + assert settings.default.account == account + + +def test_settings_required_parameters(env_defaults) -> None: + del os.environ["SEEDFARMER_PARAMETER_source_model_package_group_arn"] + del os.environ["SEEDFARMER_PARAMETER_target_bucket_name"] + + with pytest.raises(ValueError) as excinfo: + ApplicationSettings() + + assert "2 validation errors for SeedFarmerParameters" in str(excinfo.value) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_stack.py new file mode 100644 index 00000000..500be6d3 --- /dev/null +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_stack.py @@ -0,0 +1,54 @@ +import aws_cdk as cdk +from aws_cdk.assertions import Template + + +def test_synthesize_stack() -> None: + from sagemaker_model_package_promote_pipeline import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelPackagePipelineStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + source_model_package_group_arn="arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123", + target_bucket_name="dummy123", + event_bus_name="dummy123", + target_model_package_group_name="dummy123", + sagemaker_project_id="dummy123", + sagemaker_project_name="dummy123", + kms_key_arn="arn:aws:xxx:us-east-1:111111111111:xxx/asd2313-asdx-123-xa-asdasd12334123", + retain_on_delete=False, + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::CodePipeline::Pipeline", 1) + template.resource_count_is("AWS::Events::Rule", 1) + + +def test_synthesize_stack_without_eventbus() -> None: + from sagemaker_model_package_promote_pipeline import stack + + app = cdk.App() + + project_name = "test-project" + dep_name = "test-deployment" + mod_name = "test-module" + app_prefix = f"{project_name}-{dep_name}-{mod_name}" + + stack = stack.SagemakerModelPackagePipelineStack( + scope=app, + construct_id=app_prefix, + env=cdk.Environment(account="111111111111", region="us-east-1"), + source_model_package_group_arn="arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123", + target_bucket_name="dummy123", + ) + + template = Template.from_stack(stack) + template.resource_count_is("AWS::CodePipeline::Pipeline", 1) + template.resource_count_is("AWS::Events::Rule", 0) From 4f2d2007ea4cb08bb8a6cd0863114812e7b6d804 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Tue, 16 Apr 2024 13:52:25 -0300 Subject: [PATCH 06/16] Fix linging --- .../sagemaker-model-event-bus/tests/test_settings.py | 1 + .../tests/test_settings.py | 1 + .../seed_code/stack/stack.py | 1 + .../sagemaker_model_package_promote_pipeline/stack.py | 10 ++++++---- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py b/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py index 020cfd91..770d67b4 100644 --- a/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py +++ b/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py @@ -1,6 +1,7 @@ import os import pytest + from sagemaker_model_event_bus.settings import ApplicationSettings diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py index 05f558ed..11a286d7 100644 --- a/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py +++ b/modules/sagemaker/sagemaker-model-package-group/tests/test_settings.py @@ -1,6 +1,7 @@ import os import pytest + from sagemaker_model_package_group.settings import ApplicationSettings diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py index 8f7c2542..9f18b712 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py @@ -10,6 +10,7 @@ import aws_cdk.aws_sagemaker as sagemaker from aws_cdk import CfnOutput, RemovalPolicy, Stack from constructs import Construct + from stack.models import ModelMetadata diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py index a79726dc..752587c3 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -212,6 +212,7 @@ def setup_pipeline_build_project( ), } + cmd = "python3 script/get_model.py -p $MODEL_ARTIFACTS_PATH -o $MODEL_METADATA_PATH -g $MODEL_PACKAGE_GROUP_ARN" build_spec = { "version": "0.2", "phases": { @@ -222,9 +223,7 @@ def setup_pipeline_build_project( ], }, "build": { - "commands": [ - "python3 script/get_model.py -p $MODEL_ARTIFACTS_PATH -o $MODEL_METADATA_PATH -g $MODEL_PACKAGE_GROUP_ARN", - ], + "commands": [cmd], }, }, "artifacts": { @@ -240,7 +239,10 @@ def setup_pipeline_build_project( "BuildProject", build_spec=codebuild.BuildSpec.from_object(build_spec), environment_variables=env_vars, - description="Get model metadata and model artifacts from the latest approved model in a SageMaker Model Package Group.", + description=( + "Get model metadata and artifacts from the latest", + "approved model in a SageMaker Model Package Group.", + ), timeout=cdk.Duration.minutes(30), role=role, environment=codebuild.BuildEnvironment( From 87c88e94c3f65663e24ac60b7c8c6f3702e40766 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Tue, 16 Apr 2024 14:31:17 -0300 Subject: [PATCH 07/16] Fix linting --- .../sagemaker_model_package_group/stack.py | 4 ++-- .../seed_code/script/get_model.py | 12 ++++++------ .../seed_code/stack/models.py | 4 +++- .../seed_code/stack/stack.py | 8 ++++---- .../stack.py | 7 ++----- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index e9794d50..092f8e75 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -1,5 +1,5 @@ """Seedfarmer module to deploy a SageMaker Model Package Group.""" -from typing import Any, List, Optional +from typing import Any, Dict, List, Optional import aws_cdk.aws_events as events import aws_cdk.aws_events_targets as events_targets @@ -91,7 +91,7 @@ def setup_model_package_group(self) -> sagemaker.CfnModelPackageGroup: return model_package_group - def get_model_package_group_resource_policy(self) -> Optional[dict]: + def get_model_package_group_resource_policy(self) -> Optional[Dict[str, Any]]: """Get a resource policy to enable cross account access into Sagemaker Model Package Group. Returns diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py index da675584..6c1a14ff 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py @@ -6,7 +6,7 @@ import pathlib import shutil import tempfile -from typing import Any, Optional +from typing import Any, Dict, Optional from urllib.parse import unquote, urlparse import boto3 @@ -18,7 +18,7 @@ def get_latest_model_from_model_registry( boto3_session: Any, model_package_group_name: str, status_approval: Optional[str], -) -> Optional[dict]: +) -> Optional[Dict[str, Any]]: """Get the latest registered model from SageMaker model registry Parameters @@ -54,10 +54,10 @@ def get_latest_model_from_model_registry( approved_model_package = next(iter(models), None) if approved_model_package: - return sm_client.describe_model_package( + model: Dict[str, Any] = sm_client.describe_model_package( ModelPackageName=approved_model_package["ModelPackageArn"] ) - + return model return None @@ -150,7 +150,7 @@ def parse_args() -> argparse.Namespace: return args -def main(): +def main(): # type: ignore args = parse_args() boto3_session = boto3.Session(profile_name=args.profile_name) @@ -220,4 +220,4 @@ def main(): if __name__ == "__main__": - main() + main() # type: ignore diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py index 76d8c793..1ae7874e 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py @@ -385,7 +385,9 @@ class ModelPackageProperty(CdkBaseModel): ) @field_serializer("last_modified_time") - def serialize_last_modified_time(self, dt: Optional[datetime], _info): + def serialize_last_modified_time( + self, dt: Optional[datetime], _info: Any + ) -> Optional[str]: """Convert datetime to string.""" return dt.isoformat() if dt else None diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py index 9f18b712..69eee6f1 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py @@ -1,7 +1,7 @@ """Sagemaker Model Package stack.""" import json import string -from typing import Optional +from typing import Any, Optional import aws_cdk.aws_iam as iam import aws_cdk.aws_kms as kms @@ -26,7 +26,7 @@ def __init__( retain_on_delete: bool = True, model_package_group_name: Optional[str] = None, kms_key_arn: Optional[str] = None, - **kwargs, + **kwargs: Any, ) -> None: """Create a Sagemaker Model Package. @@ -194,8 +194,8 @@ def load_model_metadata(self) -> ModelMetadata: with open(self.model_metadata_path, "r") as f: model_config = f.read() - model_metadata = string.Template(model_config) - model_metadata = model_metadata.safe_substitute(bucket_name=self.bucket_name) + template = string.Template(model_config) + model_metadata = template.safe_substitute(bucket_name=self.bucket_name) model_metadata = json.loads(model_metadata) settings = ModelMetadata(**model_metadata) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py index 752587c3..ae8928e9 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -239,10 +239,7 @@ def setup_pipeline_build_project( "BuildProject", build_spec=codebuild.BuildSpec.from_object(build_spec), environment_variables=env_vars, - description=( - "Get model metadata and artifacts from the latest", - "approved model in a SageMaker Model Package Group.", - ), + description="Get latest approved model metadata and artifacts from a a SageMaker Model Package Group", timeout=cdk.Duration.minutes(30), role=role, environment=codebuild.BuildEnvironment( @@ -372,7 +369,7 @@ def setup_events(self) -> Optional[events.Rule]: event_pattern = events.EventPattern( source=["aws.sagemaker"], - account=[self.source_account], + account=[self.source_account] if self.source_account else None, detail_type=["SageMaker Model Package State Change"], detail={ "ModelPackageGroupName": [self.source_model_package_group_name], From 7cd1f17ed17ad52f6d1a287bb53fe57fa6312fd1 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Tue, 16 Apr 2024 14:42:34 -0300 Subject: [PATCH 08/16] Upd changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74441308..9f347eed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added batch inference project template to `sagemaker-templates-service-catalog` module - added EFS removal policy to `mlflow-fargate` module - added `mwaa` module with example dag which demonstrates the MLOps in Airflow +- added `sagemaker-model-event-bus` module. +- added `sagemaker-model-package-group` module. +- added `sagemaker-model-package-promote-pipeline` module. ### **Changed** From 52511731660ce74fef0d4ade6dbea9e67c15bd34 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Tue, 23 Apr 2024 10:22:53 -0300 Subject: [PATCH 09/16] Fix typos --- modules/sagemaker/sagemaker-model-package-group/README.md | 4 ++-- .../sagemaker_model_package_group/stack.py | 6 ++++-- .../sagemaker_model_package_promote_pipeline/stack.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/sagemaker/sagemaker-model-package-group/README.md b/modules/sagemaker/sagemaker-model-package-group/README.md index 922b2940..d72535c3 100644 --- a/modules/sagemaker/sagemaker-model-package-group/README.md +++ b/modules/sagemaker/sagemaker-model-package-group/README.md @@ -18,9 +18,9 @@ This module creates a SageMaker Model Package Group to register and version Sage #### Optional -- `retain_on_delete`: Wether or not to retain resources on delete. Defaults True. +- `retain_on_delete`: Whether or not to retain resources on delete. Default True. - `target_event_bus_arn`: The event bus arn in to send events model package group state change events to. It can be a bus located in another account. Defaults None. -- `model_package_group_description`: The model package group description. Defaults None. +- `model_package_group_description`: The model package group description. Default None. - `target_account_ids`: A list of account ids which shall have read-only access to the model package group. Defaults None. - `sagemaker_project_id`: SageMaker project ID. - `sagemaker_project_name`: SageMaker project name. diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index 092f8e75..c6bc99a3 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -101,8 +101,10 @@ def get_model_package_group_resource_policy(self) -> Optional[Dict[str, Any]]: if not self.target_account_ids: return None - sagemaker_arn = f"arn:aws:sagemaker:{self.region}:{self.account}" - target_accounts = [f"arn:aws:iam::{a}:root" for a in self.target_account_ids] + sagemaker_arn = f"arn:{self.partition}:sagemaker:{self.region}:{self.account}" + target_accounts = [ + f"arn:{self.partition}:iam::{a}:root" for a in self.target_account_ids + ] model_package_group_policy = { "Version": "2012-10-17", diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py index ae8928e9..fbb776f6 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -443,7 +443,7 @@ def get_pipeline_build_project_role(self) -> iam.Role: sid="GrantS3ReadOnlyPermissions", actions=["s3:ListBucket", "s3:GetObject*"], effect=iam.Effect.ALLOW, - resources=["arn:aws:s3:::*"], + resources=[f"arn:{self.partition}:s3:::*"], conditions={ "StringEquals": {"s3:ResourceAccount": self.source_account} }, @@ -457,7 +457,7 @@ def get_pipeline_build_project_role(self) -> iam.Role: ], effect=iam.Effect.ALLOW, resources=[ - f"arn:aws:sagemaker:{self.region}:{self.source_account}:model-package/*", + f"arn:{self.partition}:sagemaker:{self.region}:{self.source_account}:model-package/*", self.source_model_package_group_arn, ], ), From b0fe6600528dd5e303871611d890dc6900691624 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Wed, 24 Apr 2024 11:14:35 -0300 Subject: [PATCH 10/16] Fix linting problems --- .../sagemaker_model_package_group/stack.py | 12 +- .../seed_code/script/get_model.py | 4 +- .../seed_code/stack/models.py | 150 +++++------------- .../seed_code/stack/stack.py | 28 +--- .../stack.py | 82 +++------- 5 files changed, 71 insertions(+), 205 deletions(-) diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index c6bc99a3..b47b1d66 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -54,9 +54,7 @@ def __init__( self.target_account_ids = target_account_ids self.model_package_group_name = model_package_group_name - self.removal_policy = ( - RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY - ) + self.removal_policy = RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY self.model_package_group_description = model_package_group_description self.sagemaker_project_name = sagemaker_project_name @@ -102,9 +100,7 @@ def get_model_package_group_resource_policy(self) -> Optional[Dict[str, Any]]: return None sagemaker_arn = f"arn:{self.partition}:sagemaker:{self.region}:{self.account}" - target_accounts = [ - f"arn:{self.partition}:iam::{a}:root" for a in self.target_account_ids - ] + target_accounts = [f"arn:{self.partition}:iam::{a}:root" for a in self.target_account_ids] model_package_group_policy = { "Version": "2012-10-17", @@ -204,9 +200,7 @@ def setup_outputs(self) -> None: } if self.event_rule: - metadata[ - "SagemakerModelPackageGroupEventRuleArn" - ] = self.event_rule.rule_arn + metadata["SagemakerModelPackageGroupEventRuleArn"] = self.event_rule.rule_arn for key, value in metadata.items(): CfnOutput(scope=self, id=key, value=value) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py index 6c1a14ff..d11a95f5 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py @@ -174,9 +174,7 @@ def main(): # type: ignore logging.info("Downloading model artifacts") flatten_metadata = flatten(model_metadata, separator="___") - tmp_dir = os.path.join( - tempfile.gettempdir(), "model_artifacts", args.model_package_group - ) + tmp_dir = os.path.join(tempfile.gettempdir(), "model_artifacts", args.model_package_group) downloaded_files = [] for k, v in flatten_metadata.items(): if not (isinstance(v, str) and v.startswith("s3://")): diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py index 1ae7874e..405ba8c4 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py @@ -49,9 +49,7 @@ class ModelPackageContainerDefinitionProperty(CdkModel): class InferenceSpecificationProperty(CdkModel): """Inference Specification settings.""" - containers: List[ModelPackageContainerDefinitionProperty] = Field( - alias="Containers" - ) + containers: List[ModelPackageContainerDefinitionProperty] = Field(alias="Containers") supported_content_types: List[str] = Field(alias="SupportedContentTypes") supported_response_mime_types: List[str] = Field( alias="SupportedResponseMIMETypes", @@ -66,9 +64,7 @@ class InferenceSpecificationProperty(CdkModel): ) -class AdditionalInferenceSpecificationDefinitionProperty( - InferenceSpecificationProperty -): +class AdditionalInferenceSpecificationDefinitionProperty(InferenceSpecificationProperty): """AdditionalInferenceSpecificationDefinitionProperty.""" name: str = Field(alias="Name") @@ -107,46 +103,30 @@ class DriftCheckExplainabilityProperty(CdkModel): """DriftCheckExplainabilityProperty.""" config_file: Optional[FileSourceProperty] = Field(default=None, alias="ConfigFile") - constraints: Optional[MetricsSourceProperty] = Field( - default=None, alias="Constraints" - ) + constraints: Optional[MetricsSourceProperty] = Field(default=None, alias="Constraints") class DriftCheckModelDataQualityProperty(CdkModel): """DriftCheckModelDataQualityProperty.""" - constraints: Optional[MetricsSourceProperty] = Field( - default=None, alias="Constraints" - ) - statistics: Optional[MetricsSourceProperty] = Field( - default=None, alias="Statistics" - ) + constraints: Optional[MetricsSourceProperty] = Field(default=None, alias="Constraints") + statistics: Optional[MetricsSourceProperty] = Field(default=None, alias="Statistics") class DriftCheckModelQualityProperty(CdkModel): """DriftCheckModelQualityProperty.""" - constraints: Optional[MetricsSourceProperty] = Field( - default=None, alias="Constraints" - ) - statistics: Optional[MetricsSourceProperty] = Field( - default=None, alias="Statistics" - ) + constraints: Optional[MetricsSourceProperty] = Field(default=None, alias="Constraints") + statistics: Optional[MetricsSourceProperty] = Field(default=None, alias="Statistics") class DriftCheckBaselinesProperty(CdkModel): """DriftCheckBaselinesProperty.""" bias: Optional[DriftCheckBiasProperty] = Field(default=None, alias="Bias") - explainability: Optional[DriftCheckExplainabilityProperty] = Field( - default=None, alias="Explainability" - ) - model_data_quality: Optional[DriftCheckModelDataQualityProperty] = Field( - default=None, alias="ModelDataQuality" - ) - model_quality: Optional[DriftCheckModelQualityProperty] = Field( - default=None, alias="ModelQuality" - ) + explainability: Optional[DriftCheckExplainabilityProperty] = Field(default=None, alias="Explainability") + model_data_quality: Optional[DriftCheckModelDataQualityProperty] = Field(default=None, alias="ModelDataQuality") + model_quality: Optional[DriftCheckModelQualityProperty] = Field(default=None, alias="ModelQuality") class MetadataPropertiesProperty(CdkModel): @@ -165,9 +145,7 @@ class BiasProperty(CdkModel): default=None, alias="PostTrainingReport", ) - pre_training_report: Optional[MetricsSourceProperty] = Field( - default=None, alias="PreTrainingReport" - ) + pre_training_report: Optional[MetricsSourceProperty] = Field(default=None, alias="PreTrainingReport") report: Optional[MetricsSourceProperty] = Field(default=None, alias="Report") @@ -180,38 +158,24 @@ class ExplainabilityProperty(CdkModel): class ModelDataQualityProperty(CdkModel): """ModelDataQualityProperty.""" - constraints: Optional[MetricsSourceProperty] = Field( - default=None, alias="Constraints" - ) - statistics: Optional[MetricsSourceProperty] = Field( - default=None, alias="Statistics" - ) + constraints: Optional[MetricsSourceProperty] = Field(default=None, alias="Constraints") + statistics: Optional[MetricsSourceProperty] = Field(default=None, alias="Statistics") class ModelQualityProperty(CdkModel): """ModelQualityProperty.""" - constraints: Optional[MetricsSourceProperty] = Field( - default=None, alias="Constraints" - ) - statistics: Optional[MetricsSourceProperty] = Field( - default=None, alias="Statistics" - ) + constraints: Optional[MetricsSourceProperty] = Field(default=None, alias="Constraints") + statistics: Optional[MetricsSourceProperty] = Field(default=None, alias="Statistics") class ModelMetricsProperty(CdkModel): """ModelMetricsProperty.""" bias: Optional[BiasProperty] = Field(default=None, alias="Bias") - explainability: Optional[ExplainabilityProperty] = Field( - default=None, alias="Explainability" - ) - model_data_quality: Optional[ModelDataQualityProperty] = Field( - default=None, alias="ModelDataQuality" - ) - model_quality: Optional[ModelQualityProperty] = Field( - default=None, alias="ModelQuality" - ) + explainability: Optional[ExplainabilityProperty] = Field(default=None, alias="Explainability") + model_data_quality: Optional[ModelDataQualityProperty] = Field(default=None, alias="ModelDataQuality") + model_quality: Optional[ModelQualityProperty] = Field(default=None, alias="ModelQuality") class ModelPackageStatusItemProperty(CdkModel): @@ -254,9 +218,7 @@ class S3DataSourceProperty(CdkModel): class DataSourceProperty(CdkModel): """DataSourceProperty.""" - s3_data_source: Optional[S3DataSourceProperty] = Field( - default=None, alias="S3DataSource" - ) + s3_data_source: Optional[S3DataSourceProperty] = Field(default=None, alias="S3DataSource") class TransformInputProperty(CdkModel): @@ -288,12 +250,8 @@ class TransformResourcesProperty(CdkModel): class TransformJobDefinitionProperty(CdkModel): """TransformJobDefinitionProperty.""" - transform_input: TransformInputProperty = Field( - default=None, alias="TransformInput" - ) - transform_output: TransformOutputProperty = Field( - default=None, alias="TransformOutput" - ) + transform_input: TransformInputProperty = Field(default=None, alias="TransformInput") + transform_output: TransformOutputProperty = Field(default=None, alias="TransformOutput") transform_resources: TransformResourcesProperty = Field( default=None, alias="TransformResources", @@ -311,17 +269,13 @@ class ValidationProfileProperty(CdkModel): """ValidationProfileProperty.""" profile_name: str = Field(alias="ProfileName") - transform_job_definition: TransformJobDefinitionProperty = Field( - alias="TransformJobDefinition" - ) + transform_job_definition: TransformJobDefinitionProperty = Field(alias="TransformJobDefinition") class ValidationSpecificationProperty(CdkModel): """ValidationSpecificationProperty.""" - validation_profiles: List[ValidationProfileProperty] = Field( - alias="ValidationProfiles" - ) + validation_profiles: List[ValidationProfileProperty] = Field(alias="ValidationProfiles") validation_role: str = Field(alias="ValidationRole") @@ -329,65 +283,41 @@ class ModelPackageProperty(CdkBaseModel): """Model Package settings.""" model_package_name: Optional[str] = Field(default=None, alias="ModelPackageName") - model_package_group_name: Optional[str] = Field( - default=None, alias="ModelPackageGroupName" - ) - model_package_version: Optional[int] = Field( - default=None, alias="ModelPackageVersion" - ) - model_package_description: Optional[str] = Field( - default=None, alias="ModelPackageDescription" - ) + model_package_group_name: Optional[str] = Field(default=None, alias="ModelPackageGroupName") + model_package_version: Optional[int] = Field(default=None, alias="ModelPackageVersion") + model_package_description: Optional[str] = Field(default=None, alias="ModelPackageDescription") inference_specification: Optional[InferenceSpecificationProperty] = Field( default=None, alias="InferenceSpecification" ) - additional_inference_specifications: Optional[ - List[AdditionalInferenceSpecificationDefinitionProperty] - ] = Field(default=None, alias="AdditionalInferenceSpecifications") - approval_description: Optional[str] = Field( - default=None, alias="ApprovalDescription" - ) - certify_for_marketplace: Optional[bool] = Field( - default=None, alias="CertifyForMarketplace" - ) - customer_metadata_properties: Optional[Dict[str, str]] = Field( - default=None, alias="CustomerMetadataProperties" + additional_inference_specifications: Optional[List[AdditionalInferenceSpecificationDefinitionProperty]] = Field( + default=None, alias="AdditionalInferenceSpecifications" ) + approval_description: Optional[str] = Field(default=None, alias="ApprovalDescription") + certify_for_marketplace: Optional[bool] = Field(default=None, alias="CertifyForMarketplace") + customer_metadata_properties: Optional[Dict[str, str]] = Field(default=None, alias="CustomerMetadataProperties") domain: Optional[str] = Field(default=None, alias="Domain") - drift_check_baselines: Optional[DriftCheckBaselinesProperty] = Field( - default=None, alias="DriftCheckBaselines" - ) - last_modified_time: Optional[datetime] = Field( - default=None, alias="LastModifiedTime" - ) + drift_check_baselines: Optional[DriftCheckBaselinesProperty] = Field(default=None, alias="DriftCheckBaselines") + last_modified_time: Optional[datetime] = Field(default=None, alias="LastModifiedTime") - metadata_properties: Optional[MetadataPropertiesProperty] = Field( - default=None, alias="MetadataProperties" - ) - model_approval_status: Optional[str] = Field( - default=None, alias="ModelApprovalStatus" - ) - model_metrics: Optional[ModelMetricsProperty] = Field( - default=None, alias="ModelMetrics" - ) + metadata_properties: Optional[MetadataPropertiesProperty] = Field(default=None, alias="MetadataProperties") + model_approval_status: Optional[str] = Field(default=None, alias="ModelApprovalStatus") + model_metrics: Optional[ModelMetricsProperty] = Field(default=None, alias="ModelMetrics") model_package_status_details: Optional[ModelPackageStatusDetailsProperty] = Field( default=None, alias="ModelPackageStatusDetails" ) sample_payload_url: Optional[str] = Field(default=None, alias="SamplePayloadUrl") - source_algorithm_specification: Optional[ - SourceAlgorithmSpecificationProperty - ] = Field(default=None, alias="SourceAlgorithmSpecification") + source_algorithm_specification: Optional[SourceAlgorithmSpecificationProperty] = Field( + default=None, alias="SourceAlgorithmSpecification" + ) task: Optional[str] = Field(default=None, alias="Task") validation_specification: Optional[ValidationSpecificationProperty] = Field( default=None, alias="ValidationSpecification" ) @field_serializer("last_modified_time") - def serialize_last_modified_time( - self, dt: Optional[datetime], _info: Any - ) -> Optional[str]: + def serialize_last_modified_time(self, dt: Optional[datetime], _info: Any) -> Optional[str]: """Convert datetime to string.""" return dt.isoformat() if dt else None diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py index 69eee6f1..87f7f5fd 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py @@ -55,9 +55,7 @@ def __init__( self.kms_key_arn = kms_key_arn self.retain_on_delete = retain_on_delete - self.removal_policy = ( - RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY - ) + self.removal_policy = RemovalPolicy.RETAIN if retain_on_delete else RemovalPolicy.DESTROY self.model_metadata = self.load_model_metadata() if not self.model_metadata.model: @@ -65,9 +63,7 @@ def __init__( # Upd model package group name in metadata if model_package_group_name: - self.model_metadata.model.model_package_group_name = ( - model_package_group_name - ) + self.model_metadata.model.model_package_group_name = model_package_group_name self.setup_resources() @@ -77,17 +73,11 @@ def setup_outputs(self) -> None: """Setups outputs and metadata.""" metadata = {} if self.model_package.attr_model_package_arn: - metadata[ - "SagemakerModelPackageArn" - ] = self.model_package.attr_model_package_arn + metadata["SagemakerModelPackageArn"] = self.model_package.attr_model_package_arn if self.model_package.model_package_name: - metadata[ - "SagemakerModelPackageName" - ] = self.model_package.model_package_name + metadata["SagemakerModelPackageName"] = self.model_package.model_package_name if self.model_package.model_package_group_name: - metadata[ - "SagemakerModelPackageGroupName" - ] = self.model_package.model_package_group_name + metadata["SagemakerModelPackageGroupName"] = self.model_package.model_package_group_name for key, value in metadata.items(): CfnOutput(scope=self, id=key, value=value) @@ -123,9 +113,7 @@ def create_model_package(self) -> sagemaker.CfnModelPackage: """ artifacts = self.deploy_model_artifacts() - model_package = sagemaker.CfnModelPackage( - self, "ModelPackage", **self.model_metadata.model.model_dump() - ) + model_package = sagemaker.CfnModelPackage(self, "ModelPackage", **self.model_metadata.model.model_dump()) model_package.node.add_dependency(artifacts) @@ -145,9 +133,7 @@ def deploy_model_artifacts(self) -> s3deploy.BucketDeployment: "BucketDeploymentRole", assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), managed_policies=[ - iam.ManagedPolicy.from_aws_managed_policy_name( - "service-role/AWSLambdaVPCAccessExecutionRole" - ), + iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaVPCAccessExecutionRole"), ], ) diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py index fbb776f6..7d69982e 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -90,15 +90,9 @@ def __init__( def setup_resources(self) -> None: """Deploy resources.""" - self.target_bucket = s3.Bucket.from_bucket_name( - self, "TargetBucket", self.target_bucket_name - ) + self.target_bucket = s3.Bucket.from_bucket_name(self, "TargetBucket", self.target_bucket_name) - self.kms_key = ( - kms.Key.from_key_arn(self, "KMSKey", self.kms_key_arn) - if self.kms_key_arn - else None - ) + self.kms_key = kms.Key.from_key_arn(self, "KMSKey", self.kms_key_arn) if self.kms_key_arn else None self.code_asset = self.setup_code_assets() self.pipeline = self.setup_pipeline() @@ -106,9 +100,7 @@ def setup_resources(self) -> None: def setup_tags(self) -> None: """Add cdk.Tags to all resources.""" - cdk.Tags.of(self).add( - "sagemaker:deployment-stage", cdk.Stack.of(self).stack_name - ) + cdk.Tags.of(self).add("sagemaker:deployment-stage", cdk.Stack.of(self).stack_name) if self.sagemaker_project_id: cdk.Tags.of(self).add("sagemaker:project-id", self.sagemaker_project_id) @@ -159,9 +151,7 @@ def setup_pipeline(self) -> codepipeline.Pipeline: # Build Stage build_stage = pipeline.add_stage(stage_name="Build") build_artifact = codepipeline.Artifact() - build_project = self.setup_pipeline_build_project( - metadata_path=metadata_path, artifacts_path=artifacts_path - ) + build_project = self.setup_pipeline_build_project(metadata_path=metadata_path, artifacts_path=artifacts_path) build_action = codepipeline_actions.CodeBuildAction( action_name="BuildAction", input=source_artifact, @@ -184,9 +174,7 @@ def setup_pipeline(self) -> codepipeline.Pipeline: return pipeline - def setup_pipeline_build_project( - self, metadata_path: str, artifacts_path: str - ) -> codebuild.PipelineProject: + def setup_pipeline_build_project(self, metadata_path: str, artifacts_path: str) -> codebuild.PipelineProject: """Setup a build project Parameters @@ -201,15 +189,9 @@ def setup_pipeline_build_project( A PipelineProject instance. """ env_vars = { - "MODEL_PACKAGE_GROUP_ARN": codebuild.BuildEnvironmentVariable( - value=self.source_model_package_group_arn - ), - "MODEL_METADATA_PATH": codebuild.BuildEnvironmentVariable( - value=metadata_path - ), - "MODEL_ARTIFACTS_PATH": codebuild.BuildEnvironmentVariable( - value=artifacts_path - ), + "MODEL_PACKAGE_GROUP_ARN": codebuild.BuildEnvironmentVariable(value=self.source_model_package_group_arn), + "MODEL_METADATA_PATH": codebuild.BuildEnvironmentVariable(value=metadata_path), + "MODEL_ARTIFACTS_PATH": codebuild.BuildEnvironmentVariable(value=artifacts_path), } cmd = "python3 script/get_model.py -p $MODEL_ARTIFACTS_PATH -o $MODEL_METADATA_PATH -g $MODEL_PACKAGE_GROUP_ARN" @@ -242,16 +224,12 @@ def setup_pipeline_build_project( description="Get latest approved model metadata and artifacts from a a SageMaker Model Package Group", timeout=cdk.Duration.minutes(30), role=role, - environment=codebuild.BuildEnvironment( - build_image=codebuild.LinuxBuildImage.STANDARD_7_0 - ), + environment=codebuild.BuildEnvironment(build_image=codebuild.LinuxBuildImage.STANDARD_7_0), ) return build_project - def setup_pipeline_deploy_project( - self, metadata_path: str - ) -> codebuild.PipelineProject: + def setup_pipeline_deploy_project(self, metadata_path: str) -> codebuild.PipelineProject: """Setup a deploy project Parameters @@ -264,21 +242,11 @@ def setup_pipeline_deploy_project( A PipelineProject instance. """ env_vars = { - "app_prefix": codebuild.BuildEnvironmentVariable( - value=f"{self.pipeline_name}-DeployProject" - ), - "model_metadata_path": codebuild.BuildEnvironmentVariable( - value=metadata_path - ), - "bucket_name": codebuild.BuildEnvironmentVariable( - value=self.target_bucket.bucket_name - ), - "retain_on_delete": codebuild.BuildEnvironmentVariable( - value=self.retain_on_delete - ), - "CDK_DEFAULT_ACCOUNT": codebuild.BuildEnvironmentVariable( - value=self.account - ), + "app_prefix": codebuild.BuildEnvironmentVariable(value=f"{self.pipeline_name}-DeployProject"), + "model_metadata_path": codebuild.BuildEnvironmentVariable(value=metadata_path), + "bucket_name": codebuild.BuildEnvironmentVariable(value=self.target_bucket.bucket_name), + "retain_on_delete": codebuild.BuildEnvironmentVariable(value=self.retain_on_delete), + "CDK_DEFAULT_ACCOUNT": codebuild.BuildEnvironmentVariable(value=self.account), "CDK_DEFAULT_REGION": codebuild.BuildEnvironmentVariable(value=self.region), } @@ -288,9 +256,7 @@ def setup_pipeline_deploy_project( ) if self.kms_key: - env_vars["kms_key_arn"] = codebuild.BuildEnvironmentVariable( - value=self.kms_key.key_arn - ) + env_vars["kms_key_arn"] = codebuild.BuildEnvironmentVariable(value=self.kms_key.key_arn) build_spec = { "version": "0.2", @@ -315,9 +281,7 @@ def setup_pipeline_deploy_project( description="Deploy model metadata and model artifacts to another SageMaker Model Package Group.", timeout=cdk.Duration.minutes(30), role=role, - environment=codebuild.BuildEnvironment( - build_image=codebuild.LinuxBuildImage.STANDARD_7_0 - ), + environment=codebuild.BuildEnvironment(build_image=codebuild.LinuxBuildImage.STANDARD_7_0), ) return project @@ -363,9 +327,7 @@ def setup_events(self) -> Optional[events.Rule]: if not self.event_bus_name: return None - eventbus = events.EventBus.from_event_bus_name( - scope=self, id="EventBus", event_bus_name=self.event_bus_name - ) + eventbus = events.EventBus.from_event_bus_name(scope=self, id="EventBus", event_bus_name=self.event_bus_name) event_pattern = events.EventPattern( source=["aws.sagemaker"], @@ -444,9 +406,7 @@ def get_pipeline_build_project_role(self) -> iam.Role: actions=["s3:ListBucket", "s3:GetObject*"], effect=iam.Effect.ALLOW, resources=[f"arn:{self.partition}:s3:::*"], - conditions={ - "StringEquals": {"s3:ResourceAccount": self.source_account} - }, + conditions={"StringEquals": {"s3:ResourceAccount": self.source_account}}, ), iam.PolicyStatement( sid="GrantSageMakerModelPkgReadOnlyPermissions", @@ -486,9 +446,7 @@ def get_pipeline_deploy_project_role(self) -> iam.Role: sid="GrantAssumeRoleOnCDKRoles", actions=["sts:AssumeRole"], effect=iam.Effect.ALLOW, - resources=[ - f"arn:{self.partition}:iam::{self.account}:role/cdk-*-{self.account}-{self.region}" - ], + resources=[f"arn:{self.partition}:iam::{self.account}:role/cdk-*-{self.account}-{self.region}"], ) ) From 2bb40f0ce894c860ee23dc4c2d3e6466cf5125ac Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 16:13:35 -0300 Subject: [PATCH 11/16] Fix format --- .../sagemaker_model_event_bus/settings.py | 1 + .../sagemaker_model_event_bus/stack.py | 1 + .../sagemaker_model_package_group/settings.py | 1 + .../sagemaker_model_package_group/stack.py | 1 + .../sagemaker-model-package-group/tests/conftest.py | 6 +++--- .../seed_code/script/get_model.py | 1 + .../seed_code/stack/models.py | 1 + .../seed_code/stack/settings.py | 1 + .../seed_code/stack/stack.py | 1 + .../settings.py | 1 + .../stack.py | 1 + .../tests/test_app.py | 6 +++--- .../tests/test_settings.py | 12 ++++++------ 13 files changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py index 5d3f8791..bd1da6d7 100644 --- a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py +++ b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py @@ -1,4 +1,5 @@ """Defines the stack settings.""" + from abc import ABC from typing import Dict, List, Optional diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py index be9fa210..7ac046fe 100644 --- a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py +++ b/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py @@ -1,4 +1,5 @@ """Seedfarmer module to deploy an EventBridge Bus for SageMaker Model Package events.""" + from typing import Any, Dict, List, Optional import aws_cdk.aws_events as events diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py index e9be331b..f38a99ac 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/settings.py @@ -1,4 +1,5 @@ """Defines the stack settings.""" + from abc import ABC from typing import List, Optional diff --git a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py index b47b1d66..172d1439 100644 --- a/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py +++ b/modules/sagemaker/sagemaker-model-package-group/sagemaker_model_package_group/stack.py @@ -1,4 +1,5 @@ """Seedfarmer module to deploy a SageMaker Model Package Group.""" + from typing import Any, Dict, List, Optional import aws_cdk.aws_events as events diff --git a/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py b/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py index 8f2dd16b..effab534 100644 --- a/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py +++ b/modules/sagemaker/sagemaker-model-package-group/tests/conftest.py @@ -14,9 +14,9 @@ def env_defaults(): os.environ["SEEDFARMER_PARAMETER_model_package_group_name"] = "dummy123" os.environ["SEEDFARMER_PARAMETER_retain_on_delete"] = "False" - os.environ[ - "SEEDFARMER_PARAMETER_target_event_bus_arn" - ] = "arn:aws:events:xx-xxxxx-xx:xxxxxxxxxxxx:event-bus/default" + os.environ["SEEDFARMER_PARAMETER_target_event_bus_arn"] = ( + "arn:aws:events:xx-xxxxx-xx:xxxxxxxxxxxx:event-bus/default" + ) os.environ["SEEDFARMER_PARAMETER_model_package_group_description"] = "dummy123" os.environ["SEEDFARMER_PARAMETER_target_account_ids"] = '["dummy123"]' os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py index d11a95f5..1216ae3c 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/script/get_model.py @@ -1,4 +1,5 @@ """A module to get Sagemaker Models metadata and artifacts.""" + import argparse import json import logging diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py index 405ba8c4..40c5faf7 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/models.py @@ -1,4 +1,5 @@ """Sagemaker Model Package models.""" + from abc import ABC from datetime import datetime from typing import Any, Dict, List, Optional diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py index d6c847d6..75775943 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/settings.py @@ -1,4 +1,5 @@ """Sagemaker Model Package settings.""" + from abc import ABC from typing import Optional diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py index 87f7f5fd..06b62e74 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/seed_code/stack/stack.py @@ -1,4 +1,5 @@ """Sagemaker Model Package stack.""" + import json import string from typing import Any, Optional diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py index d6c11e5e..0ada17fc 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/settings.py @@ -1,4 +1,5 @@ """Defines the stack settings.""" + from abc import ABC from typing import Optional diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py index 7d69982e..41fa25d6 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/sagemaker_model_package_promote_pipeline/stack.py @@ -1,4 +1,5 @@ """Seedfarmer module to deploy a Pipeline to promote SageMaker Model Packages.""" + import os from typing import Any, Optional diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py index 2561d585..b76b3b19 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_app.py @@ -13,9 +13,9 @@ def stack_defaults(): os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" os.environ["CDK_DEFAULT_REGION"] = "us-east-1" - os.environ[ - "SEEDFARMER_PARAMETER_source_model_package_group_arn" - ] = "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + os.environ["SEEDFARMER_PARAMETER_source_model_package_group_arn"] = ( + "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + ) os.environ["SEEDFARMER_PARAMETER_target_bucket_name"] = "dummy321" # Unload the app import so that subsequent tests don't reuse diff --git a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py index 7a1c3ff1..5357cbdf 100644 --- a/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py +++ b/modules/sagemaker/sagemaker-model-package-promote-pipeline/tests/test_settings.py @@ -15,17 +15,17 @@ def env_defaults(): os.environ["CDK_DEFAULT_ACCOUNT"] = "111111111111" os.environ["CDK_DEFAULT_REGION"] = "us-east-1" - os.environ[ - "SEEDFARMER_PARAMETER_source_model_package_group_arn" - ] = "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + os.environ["SEEDFARMER_PARAMETER_source_model_package_group_arn"] = ( + "arn:aws:sagemaker:us-east-1:111111111111:model-package-group/dummy123" + ) os.environ["SEEDFARMER_PARAMETER_target_bucket_name"] = "dummy321" os.environ["SEEDFARMER_PARAMETER_event_bus_name"] = "dummy321" os.environ["SEEDFARMER_PARAMETER_target_model_package_group_name"] = "dummy321" os.environ["SEEDFARMER_PARAMETER_sagemaker_project_id"] = "dummy321" os.environ["SEEDFARMER_PARAMETER_sagemaker_project_name"] = "dummy321" - os.environ[ - "SEEDFARMER_PARAMETER_kms_key_arn" - ] = "arn:aws:xxx:us-east-1:111111111111:xxx/asd2313-asdx-123-xa-asdasd12334123" + os.environ["SEEDFARMER_PARAMETER_kms_key_arn"] = ( + "arn:aws:xxx:us-east-1:111111111111:xxx/asd2313-asdx-123-xa-asdasd12334123" + ) os.environ["SEEDFARMER_PARAMETER_retain_on_delete"] = "False" From 0431b499cdfa2b46d4a6d3090a742d4bbb98d7a2 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 16:17:23 -0300 Subject: [PATCH 12/16] Move event bus module to another location --- .../events}/sagemaker-model-event-bus/README.md | 0 .../events}/sagemaker-model-event-bus/app.py | 0 .../sagemaker-model-event-bus/deployspec.yaml | 0 .../docs/_static/architecture.drawio | 0 .../docs/_static/architecture.drawio.png | Bin .../sagemaker-model-event-bus/pyproject.toml | 0 .../sagemaker-model-event-bus/requirements.txt | 0 .../sagemaker_model_event_bus/__init__.py | 0 .../sagemaker_model_event_bus/settings.py | 0 .../sagemaker_model_event_bus/stack.py | 0 .../sagemaker-model-event-bus/tests/__init__.py | 0 .../sagemaker-model-event-bus/tests/conftest.py | 0 .../sagemaker-model-event-bus/tests/test_app.py | 0 .../tests/test_settings.py | 0 .../sagemaker-model-event-bus/tests/test_stack.py | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/README.md (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/app.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/deployspec.yaml (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/docs/_static/architecture.drawio (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/docs/_static/architecture.drawio.png (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/pyproject.toml (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/requirements.txt (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/tests/__init__.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/tests/conftest.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/tests/test_app.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/tests/test_settings.py (100%) rename {modules/sagemaker => examples/modules/events}/sagemaker-model-event-bus/tests/test_stack.py (100%) diff --git a/modules/sagemaker/sagemaker-model-event-bus/README.md b/examples/modules/events/sagemaker-model-event-bus/README.md similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/README.md rename to examples/modules/events/sagemaker-model-event-bus/README.md diff --git a/modules/sagemaker/sagemaker-model-event-bus/app.py b/examples/modules/events/sagemaker-model-event-bus/app.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/app.py rename to examples/modules/events/sagemaker-model-event-bus/app.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml b/examples/modules/events/sagemaker-model-event-bus/deployspec.yaml similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/deployspec.yaml rename to examples/modules/events/sagemaker-model-event-bus/deployspec.yaml diff --git a/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio b/examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio rename to examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio diff --git a/modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio.png b/examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio.png similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/docs/_static/architecture.drawio.png rename to examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio.png diff --git a/modules/sagemaker/sagemaker-model-event-bus/pyproject.toml b/examples/modules/events/sagemaker-model-event-bus/pyproject.toml similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/pyproject.toml rename to examples/modules/events/sagemaker-model-event-bus/pyproject.toml diff --git a/modules/sagemaker/sagemaker-model-event-bus/requirements.txt b/examples/modules/events/sagemaker-model-event-bus/requirements.txt similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/requirements.txt rename to examples/modules/events/sagemaker-model-event-bus/requirements.txt diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py b/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py rename to examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py b/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py rename to examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py b/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py rename to examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/__init__.py b/examples/modules/events/sagemaker-model-event-bus/tests/__init__.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/tests/__init__.py rename to examples/modules/events/sagemaker-model-event-bus/tests/__init__.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py b/examples/modules/events/sagemaker-model-event-bus/tests/conftest.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/tests/conftest.py rename to examples/modules/events/sagemaker-model-event-bus/tests/conftest.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py b/examples/modules/events/sagemaker-model-event-bus/tests/test_app.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/tests/test_app.py rename to examples/modules/events/sagemaker-model-event-bus/tests/test_app.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py b/examples/modules/events/sagemaker-model-event-bus/tests/test_settings.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/tests/test_settings.py rename to examples/modules/events/sagemaker-model-event-bus/tests/test_settings.py diff --git a/modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py b/examples/modules/events/sagemaker-model-event-bus/tests/test_stack.py similarity index 100% rename from modules/sagemaker/sagemaker-model-event-bus/tests/test_stack.py rename to examples/modules/events/sagemaker-model-event-bus/tests/test_stack.py From a86fd3b2e58e731205e1fe3c5644da6b386b4e30 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 16:23:15 -0300 Subject: [PATCH 13/16] Rename event bus module --- ...agemaker-model-event-bus.yaml => event-bus.yaml} | 2 +- .../README.md | 8 ++++---- .../{sagemaker-model-event-bus => event-bus}/app.py | 8 ++++---- .../deployspec.yaml | 0 .../docs/_static/architecture.drawio | 0 .../docs/_static/architecture.drawio.png | Bin .../event_bus}/__init__.py | 0 .../event_bus}/settings.py | 0 .../event_bus}/stack.py | 6 ++---- .../pyproject.toml | 0 .../requirements.txt | 0 .../tests/__init__.py | 0 .../tests/conftest.py | 0 .../tests/test_app.py | 0 .../tests/test_settings.py | 2 +- .../tests/test_stack.py | 4 ++-- 16 files changed, 14 insertions(+), 16 deletions(-) rename examples/manifests/{sagemaker-model-event-bus.yaml => event-bus.yaml} (84%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/README.md (77%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/app.py (63%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/deployspec.yaml (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/docs/_static/architecture.drawio (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/docs/_static/architecture.drawio.png (100%) rename examples/modules/events/{sagemaker-model-event-bus/sagemaker_model_event_bus => event-bus/event_bus}/__init__.py (100%) rename examples/modules/events/{sagemaker-model-event-bus/sagemaker_model_event_bus => event-bus/event_bus}/settings.py (100%) rename examples/modules/events/{sagemaker-model-event-bus/sagemaker_model_event_bus => event-bus/event_bus}/stack.py (92%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/pyproject.toml (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/requirements.txt (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/tests/__init__.py (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/tests/conftest.py (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/tests/test_app.py (100%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/tests/test_settings.py (92%) rename examples/modules/events/{sagemaker-model-event-bus => event-bus}/tests/test_stack.py (86%) diff --git a/examples/manifests/sagemaker-model-event-bus.yaml b/examples/manifests/event-bus.yaml similarity index 84% rename from examples/manifests/sagemaker-model-event-bus.yaml rename to examples/manifests/event-bus.yaml index cf017398..57d5faba 100644 --- a/examples/manifests/sagemaker-model-event-bus.yaml +++ b/examples/manifests/event-bus.yaml @@ -1,5 +1,5 @@ name: event-bus -path: modules/sagemaker/sagemaker-model-event-bus +path: examples/modules/events/event-bus targetAccount: tooling parameters: - name: event_bus_name diff --git a/examples/modules/events/sagemaker-model-event-bus/README.md b/examples/modules/events/event-bus/README.md similarity index 77% rename from examples/modules/events/sagemaker-model-event-bus/README.md rename to examples/modules/events/event-bus/README.md index 12c2053c..2dd77dce 100644 --- a/examples/modules/events/sagemaker-model-event-bus/README.md +++ b/examples/modules/events/event-bus/README.md @@ -1,4 +1,4 @@ -# SageMaker Model Event Bus +# Event Bus ## Description @@ -6,7 +6,7 @@ This module creates an Amazon EventBridge Bus for cross-account events. ### Architecture -![SageMaker Model Event Bus Architecture](docs/_static/architecture.drawio.png "SageMaker Model Event Bus Architecture") +![Event Bus Architecture](docs/_static/architecture.drawio.png "Event Bus Architecture") ## Inputs/Outputs @@ -24,8 +24,8 @@ This module creates an Amazon EventBridge Bus for cross-account events. ### Sample manifest declaration ```yaml -name: sagemaker-model-event-bus -path: modules/sagemaker/sagemaker-model-event-bus/sagemaker_model_event_bus +name: event-bus +path: examples/modules/events/event-bus targetAccount: primary parameters: - name: event_bus_name diff --git a/examples/modules/events/sagemaker-model-event-bus/app.py b/examples/modules/events/event-bus/app.py similarity index 63% rename from examples/modules/events/sagemaker-model-event-bus/app.py rename to examples/modules/events/event-bus/app.py index d79b227d..abeef001 100644 --- a/examples/modules/events/sagemaker-model-event-bus/app.py +++ b/examples/modules/events/event-bus/app.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Create a Sagemaker Model Event Bus Stack.""" +"""Create a Event Bus Stack.""" import aws_cdk as cdk -from sagemaker_model_event_bus.settings import ApplicationSettings -from sagemaker_model_event_bus.stack import SagemakerModelEventBusStack +from event_bus.settings import ApplicationSettings +from event_bus.stack import EventBusStack # Load application settings from env vars. app_settings = ApplicationSettings() @@ -16,7 +16,7 @@ app = cdk.App() -stack = SagemakerModelEventBusStack( +stack = EventBusStack( scope=app, construct_id=app_settings.settings.app_prefix, env=env, diff --git a/examples/modules/events/sagemaker-model-event-bus/deployspec.yaml b/examples/modules/events/event-bus/deployspec.yaml similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/deployspec.yaml rename to examples/modules/events/event-bus/deployspec.yaml diff --git a/examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio b/examples/modules/events/event-bus/docs/_static/architecture.drawio similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio rename to examples/modules/events/event-bus/docs/_static/architecture.drawio diff --git a/examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio.png b/examples/modules/events/event-bus/docs/_static/architecture.drawio.png similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/docs/_static/architecture.drawio.png rename to examples/modules/events/event-bus/docs/_static/architecture.drawio.png diff --git a/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py b/examples/modules/events/event-bus/event_bus/__init__.py similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/__init__.py rename to examples/modules/events/event-bus/event_bus/__init__.py diff --git a/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py b/examples/modules/events/event-bus/event_bus/settings.py similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/settings.py rename to examples/modules/events/event-bus/event_bus/settings.py diff --git a/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py b/examples/modules/events/event-bus/event_bus/stack.py similarity index 92% rename from examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py rename to examples/modules/events/event-bus/event_bus/stack.py index 7ac046fe..57c668da 100644 --- a/examples/modules/events/sagemaker-model-event-bus/sagemaker_model_event_bus/stack.py +++ b/examples/modules/events/event-bus/event_bus/stack.py @@ -1,4 +1,4 @@ -"""Seedfarmer module to deploy an EventBridge Bus for SageMaker Model Package events.""" +"""Seedfarmer module to deploy an EventBridge Bus.""" from typing import Any, Dict, List, Optional @@ -8,7 +8,7 @@ from constructs import Construct -class SagemakerModelEventBusStack(Stack): +class EventBusStack(Stack): """Create an EventBridge Bus.""" def __init__( @@ -78,8 +78,6 @@ def setup_event_bus(self) -> events.EventBus: def setup_tags(self) -> None: """Add tags to all resources.""" - Tags.of(self).add("sagemaker:deployment-stage", Stack.of(self).stack_name) - for k, v in (self.additional_tags or {}).items(): Tags.of(self).add(k, v) diff --git a/examples/modules/events/sagemaker-model-event-bus/pyproject.toml b/examples/modules/events/event-bus/pyproject.toml similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/pyproject.toml rename to examples/modules/events/event-bus/pyproject.toml diff --git a/examples/modules/events/sagemaker-model-event-bus/requirements.txt b/examples/modules/events/event-bus/requirements.txt similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/requirements.txt rename to examples/modules/events/event-bus/requirements.txt diff --git a/examples/modules/events/sagemaker-model-event-bus/tests/__init__.py b/examples/modules/events/event-bus/tests/__init__.py similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/tests/__init__.py rename to examples/modules/events/event-bus/tests/__init__.py diff --git a/examples/modules/events/sagemaker-model-event-bus/tests/conftest.py b/examples/modules/events/event-bus/tests/conftest.py similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/tests/conftest.py rename to examples/modules/events/event-bus/tests/conftest.py diff --git a/examples/modules/events/sagemaker-model-event-bus/tests/test_app.py b/examples/modules/events/event-bus/tests/test_app.py similarity index 100% rename from examples/modules/events/sagemaker-model-event-bus/tests/test_app.py rename to examples/modules/events/event-bus/tests/test_app.py diff --git a/examples/modules/events/sagemaker-model-event-bus/tests/test_settings.py b/examples/modules/events/event-bus/tests/test_settings.py similarity index 92% rename from examples/modules/events/sagemaker-model-event-bus/tests/test_settings.py rename to examples/modules/events/event-bus/tests/test_settings.py index 770d67b4..68af035b 100644 --- a/examples/modules/events/sagemaker-model-event-bus/tests/test_settings.py +++ b/examples/modules/events/event-bus/tests/test_settings.py @@ -2,7 +2,7 @@ import pytest -from sagemaker_model_event_bus.settings import ApplicationSettings +from event_bus.settings import ApplicationSettings def test_settings_inputs(env_defaults) -> None: diff --git a/examples/modules/events/sagemaker-model-event-bus/tests/test_stack.py b/examples/modules/events/event-bus/tests/test_stack.py similarity index 86% rename from examples/modules/events/sagemaker-model-event-bus/tests/test_stack.py rename to examples/modules/events/event-bus/tests/test_stack.py index b3057cb4..c1dd41e2 100644 --- a/examples/modules/events/sagemaker-model-event-bus/tests/test_stack.py +++ b/examples/modules/events/event-bus/tests/test_stack.py @@ -3,7 +3,7 @@ def test_synthesize_stack() -> None: - from sagemaker_model_event_bus import stack + from event_bus import stack app = cdk.App() @@ -12,7 +12,7 @@ def test_synthesize_stack() -> None: mod_name = "test-module" app_prefix = f"{project_name}-{dep_name}-{mod_name}" - stack = stack.SagemakerModelEventBusStack( + stack = stack.EventBusStack( scope=app, construct_id=app_prefix, env=cdk.Environment(account="111111111111", region="us-east-1"), From 93fad1bacd0f07225c9995f6c7a6a972c71ac924 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 16:46:56 -0300 Subject: [PATCH 14/16] Fix event bus module location --- examples/manifests/event-bus.yaml | 2 +- .../examples}/events/event-bus/README.md | 6 ++---- .../examples}/events/event-bus/app.py | 0 .../examples}/events/event-bus/deployspec.yaml | 0 .../event-bus/docs/_static/architecture.drawio | 0 .../event-bus/docs/_static/architecture.drawio.png | Bin .../events/event-bus/event_bus/__init__.py | 0 .../events/event-bus/event_bus/settings.py | 0 .../examples}/events/event-bus/event_bus/stack.py | 0 .../examples}/events/event-bus/pyproject.toml | 0 .../examples}/events/event-bus/requirements.txt | 0 .../examples}/events/event-bus/tests/__init__.py | 0 .../examples}/events/event-bus/tests/conftest.py | 0 .../examples}/events/event-bus/tests/test_app.py | 0 .../events/event-bus/tests/test_settings.py | 0 .../examples}/events/event-bus/tests/test_stack.py | 0 16 files changed, 3 insertions(+), 5 deletions(-) rename {examples/modules => modules/examples}/events/event-bus/README.md (95%) rename {examples/modules => modules/examples}/events/event-bus/app.py (100%) rename {examples/modules => modules/examples}/events/event-bus/deployspec.yaml (100%) rename {examples/modules => modules/examples}/events/event-bus/docs/_static/architecture.drawio (100%) rename {examples/modules => modules/examples}/events/event-bus/docs/_static/architecture.drawio.png (100%) rename {examples/modules => modules/examples}/events/event-bus/event_bus/__init__.py (100%) rename {examples/modules => modules/examples}/events/event-bus/event_bus/settings.py (100%) rename {examples/modules => modules/examples}/events/event-bus/event_bus/stack.py (100%) rename {examples/modules => modules/examples}/events/event-bus/pyproject.toml (100%) rename {examples/modules => modules/examples}/events/event-bus/requirements.txt (100%) rename {examples/modules => modules/examples}/events/event-bus/tests/__init__.py (100%) rename {examples/modules => modules/examples}/events/event-bus/tests/conftest.py (100%) rename {examples/modules => modules/examples}/events/event-bus/tests/test_app.py (100%) rename {examples/modules => modules/examples}/events/event-bus/tests/test_settings.py (100%) rename {examples/modules => modules/examples}/events/event-bus/tests/test_stack.py (100%) diff --git a/examples/manifests/event-bus.yaml b/examples/manifests/event-bus.yaml index 57d5faba..ff9418df 100644 --- a/examples/manifests/event-bus.yaml +++ b/examples/manifests/event-bus.yaml @@ -1,5 +1,5 @@ name: event-bus -path: examples/modules/events/event-bus +path: modules/examples/events/event-bus targetAccount: tooling parameters: - name: event_bus_name diff --git a/examples/modules/events/event-bus/README.md b/modules/examples/events/event-bus/README.md similarity index 95% rename from examples/modules/events/event-bus/README.md rename to modules/examples/events/event-bus/README.md index 2dd77dce..62408e92 100644 --- a/examples/modules/events/event-bus/README.md +++ b/modules/examples/events/event-bus/README.md @@ -25,7 +25,7 @@ This module creates an Amazon EventBridge Bus for cross-account events. ```yaml name: event-bus -path: examples/modules/events/event-bus +path: modules/examples/events/event-bus targetAccount: primary parameters: - name: event_bus_name @@ -48,6 +48,4 @@ parameters: "EventBusArn": "arn:aws:events:xx-xxxx-x:xxxxxxxxxx:event-bus/mlops-bus", "EventBusName": "mlops-bus", } -``` - - +``` \ No newline at end of file diff --git a/examples/modules/events/event-bus/app.py b/modules/examples/events/event-bus/app.py similarity index 100% rename from examples/modules/events/event-bus/app.py rename to modules/examples/events/event-bus/app.py diff --git a/examples/modules/events/event-bus/deployspec.yaml b/modules/examples/events/event-bus/deployspec.yaml similarity index 100% rename from examples/modules/events/event-bus/deployspec.yaml rename to modules/examples/events/event-bus/deployspec.yaml diff --git a/examples/modules/events/event-bus/docs/_static/architecture.drawio b/modules/examples/events/event-bus/docs/_static/architecture.drawio similarity index 100% rename from examples/modules/events/event-bus/docs/_static/architecture.drawio rename to modules/examples/events/event-bus/docs/_static/architecture.drawio diff --git a/examples/modules/events/event-bus/docs/_static/architecture.drawio.png b/modules/examples/events/event-bus/docs/_static/architecture.drawio.png similarity index 100% rename from examples/modules/events/event-bus/docs/_static/architecture.drawio.png rename to modules/examples/events/event-bus/docs/_static/architecture.drawio.png diff --git a/examples/modules/events/event-bus/event_bus/__init__.py b/modules/examples/events/event-bus/event_bus/__init__.py similarity index 100% rename from examples/modules/events/event-bus/event_bus/__init__.py rename to modules/examples/events/event-bus/event_bus/__init__.py diff --git a/examples/modules/events/event-bus/event_bus/settings.py b/modules/examples/events/event-bus/event_bus/settings.py similarity index 100% rename from examples/modules/events/event-bus/event_bus/settings.py rename to modules/examples/events/event-bus/event_bus/settings.py diff --git a/examples/modules/events/event-bus/event_bus/stack.py b/modules/examples/events/event-bus/event_bus/stack.py similarity index 100% rename from examples/modules/events/event-bus/event_bus/stack.py rename to modules/examples/events/event-bus/event_bus/stack.py diff --git a/examples/modules/events/event-bus/pyproject.toml b/modules/examples/events/event-bus/pyproject.toml similarity index 100% rename from examples/modules/events/event-bus/pyproject.toml rename to modules/examples/events/event-bus/pyproject.toml diff --git a/examples/modules/events/event-bus/requirements.txt b/modules/examples/events/event-bus/requirements.txt similarity index 100% rename from examples/modules/events/event-bus/requirements.txt rename to modules/examples/events/event-bus/requirements.txt diff --git a/examples/modules/events/event-bus/tests/__init__.py b/modules/examples/events/event-bus/tests/__init__.py similarity index 100% rename from examples/modules/events/event-bus/tests/__init__.py rename to modules/examples/events/event-bus/tests/__init__.py diff --git a/examples/modules/events/event-bus/tests/conftest.py b/modules/examples/events/event-bus/tests/conftest.py similarity index 100% rename from examples/modules/events/event-bus/tests/conftest.py rename to modules/examples/events/event-bus/tests/conftest.py diff --git a/examples/modules/events/event-bus/tests/test_app.py b/modules/examples/events/event-bus/tests/test_app.py similarity index 100% rename from examples/modules/events/event-bus/tests/test_app.py rename to modules/examples/events/event-bus/tests/test_app.py diff --git a/examples/modules/events/event-bus/tests/test_settings.py b/modules/examples/events/event-bus/tests/test_settings.py similarity index 100% rename from examples/modules/events/event-bus/tests/test_settings.py rename to modules/examples/events/event-bus/tests/test_settings.py diff --git a/examples/modules/events/event-bus/tests/test_stack.py b/modules/examples/events/event-bus/tests/test_stack.py similarity index 100% rename from examples/modules/events/event-bus/tests/test_stack.py rename to modules/examples/events/event-bus/tests/test_stack.py From 76f94a0bf071ccdefc94a86e311fe7de5b8192c1 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 17:14:27 -0300 Subject: [PATCH 15/16] Upd readme with new modules --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b5a8f23e..a1274d2b 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,15 @@ See deployment steps in the [Deployment Guide](DEPLOYMENT.md). ### SageMaker Modules -| Type | Description | -|---------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [SageMaker Studio Module](modules/sagemaker/sagemaker-studio/README.md) | Provisions secure SageMaker Studio Domain environment, creates example User Profiles for Data Scientist and Lead Data Scientist linked to IAM Roles, and adds lifecycle config | -| [SageMaker Endpoint Module](modules/sagemaker/sagemaker-endpoint/README.md) | Creates SageMaker real-time inference endpoint for the specified model package or latest approved model from the model package group | +| Type | Description | +|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [SageMaker Studio Module](modules/sagemaker/sagemaker-studio/README.md) | Provisions secure SageMaker Studio Domain environment, creates example User Profiles for Data Scientist and Lead Data Scientist linked to IAM Roles, and adds lifecycle config | +| [SageMaker Endpoint Module](modules/sagemaker/sagemaker-endpoint/README.md) | Creates SageMaker real-time inference endpoint for the specified model package or latest approved model from the model package group | | [SageMaker Project Templates via Service Catalog Module](modules/sagemaker/sagemaker-templates-service-catalog/README.md) | Provisions SageMaker Project Templates for an organization. The templates are available using SageMaker Studio Classic or Service Catalog. Available templates:
- [Train a model on Abalone dataset using XGBoost](modules/sagemaker/sagemaker-templates-service-catalog/README.md#train-a-model-on-abalone-dataset-with-xgboost-template)
- [Perform batch inference](modules/sagemaker/sagemaker-templates-service-catalog/README.md#batch-inference-template)
- [Multi-account model deployment](modules/sagemaker/sagemaker-templates-service-catalog/README.md#multi-account-model-deployment-template) | -| [SageMaker Notebook Instance Module](modules/sagemaker/sagemaker-notebook/README.md) | Creates secure SageMaker Notebook Instance for the Data Scientist, clones the source code to the workspace | -| [SageMaker Custom Kernel Module](modules/sagemaker/sagemaker-custom-kernel/README.md) | Builds custom kernel for SageMaker Studio from a Dockerfile | +| [SageMaker Notebook Instance Module](modules/sagemaker/sagemaker-notebook/README.md) | Creates secure SageMaker Notebook Instance for the Data Scientist, clones the source code to the workspace | +| [SageMaker Custom Kernel Module](modules/sagemaker/sagemaker-custom-kernel/README.md) | Builds custom kernel for SageMaker Studio from a Dockerfile | +| [SageMaker Model Package Group Module](modules/sagemaker/sagemaker-model-package-group/README.md) | Creates a SageMaker Model Package Group to register and version SageMaker Machine Learning (ML) models and setups an Amazon EventBridge Rule to send model package group state change events to an Amazon EventBridge Bus | +| [SageMaker Model Package Promote Pipeline Module](modules/sagemaker/sagemaker-model-package-promote-pipeline/README.md) | Deploy a Pipeline to promote SageMaker Model Packages in a multi-account setup. The pipeline can be triggered through an EventBridge rule in reaction of a SageMaker Model Package Group state event change (Approved/Rejected). Once the pipeline is triggered, it will promote the latest approved model package, if one is found. | ### Mlflow Modules @@ -56,3 +58,9 @@ See deployment steps in the [Deployment Guide](DEPLOYMENT.md). ### Industry Data Framework (IDF) Modules The modules in this repository are compatible with [Industry Data Framework (IDF) Modules](https://github.com/awslabs/idf-modules) and can be used together within the same deployment. Refer to `examples/manifests` for examples. + +### Events Modules + +| Type | Description | +|-------------------------------------------------------------------------|------------------------------------------------------------------------| +| [Event Bus Module](modules/examples/events/event-busREADME.md) | Creates an Amazon EventBridge Bus for cross-account events. | From 1084d453a51919181c881e804ded159a8b173a61 Mon Sep 17 00:00:00 2001 From: Lucas Casagrande Date: Fri, 3 May 2024 17:22:03 -0300 Subject: [PATCH 16/16] Fix module path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1274d2b..a47daa98 100644 --- a/README.md +++ b/README.md @@ -63,4 +63,4 @@ The modules in this repository are compatible with [Industry Data Framework (IDF | Type | Description | |-------------------------------------------------------------------------|------------------------------------------------------------------------| -| [Event Bus Module](modules/examples/events/event-busREADME.md) | Creates an Amazon EventBridge Bus for cross-account events. | +| [Event Bus Module](modules/examples/events/event-bus/README.md) | Creates an Amazon EventBridge Bus for cross-account events. |