From 5ddc96ce49d7680555dfae994e7ba2e9b9e82037 Mon Sep 17 00:00:00 2001 From: Alex Demidoff Date: Thu, 30 Jul 2026 17:04:30 +0300 Subject: [PATCH 1/4] PMM-15266 Bring back the update status endpoint PMM-14969 (#5394) removed StartUpdate and UpdateStatus together with the GUI-triggered upgrade flow. Pre-3.9 clients, however, keep polling /v1/server/updates:getStatus after they trigger an update - that is how they learn the freshly started server has finished initializing. Without it they never leave the "in progress" state when migrating to 3.9.0. Restore UpdateStatus only. StartUpdate stays removed, so the auth token file it used to write (/srv/pmm-update.json) is gone as well and the token can no longer be verified: it was issued by the instance this one replaced. Requests carrying one are accepted as is. Completion now comes from the supervisord state of pmm-init, the program that runs the initialization and upgrade tasks. Unlike the maintenance mode marker, supervisord reports a program as running from the moment it is spawned, so there is no window right after a restart in which the endpoint would wrongly report an update as complete. The nginx maintenance mode bypass listed /v1/updates/Status, a PMM 2 path that has matched nothing in 3.x, so the progress log never reached pmm-managed while an upgrade was running. It now names the actual endpoint. --- api-tests/server/updates_test.go | 19 + api/descriptor.bin | Bin 819175 -> 821684 bytes .../server_service/server_service_client.go | 46 ++ .../update_status_parameters.go | 141 ++++ .../server_service/update_status_responses.go | 661 ++++++++++++++++++ api/server/v1/json/v1.json | 95 +++ api/server/v1/server.pb.go | 300 +++++--- api/server/v1/server.pb.gw.go | 66 ++ api/server/v1/server.pb.validate.go | 214 ++++++ api/server/v1/server.proto | 31 + api/server/v1/server_grpc.pb.go | 47 ++ api/swagger/swagger-dev.json | 95 +++ api/swagger/swagger.json | 95 +++ .../roles/initialization/tasks/main.yml | 3 +- .../ansible/roles/nginx/files/conf.d/pmm.conf | 4 +- managed/cmd/pmm-managed/main.go | 2 +- managed/services/grafana/auth_server.go | 4 + managed/services/server/deps.go | 1 + .../server/mock_supervisord_service_test.go | 18 + managed/services/server/server.go | 50 ++ managed/services/server/server_test.go | 61 ++ managed/services/server/updater.go | 61 +- managed/services/server/updater_test.go | 65 +- .../services/supervisord/devcontainer_test.go | 15 +- managed/services/supervisord/supervisord.go | 58 +- 25 files changed, 2042 insertions(+), 110 deletions(-) create mode 100644 api/server/v1/json/client/server_service/update_status_parameters.go create mode 100644 api/server/v1/json/client/server_service/update_status_responses.go diff --git a/api-tests/server/updates_test.go b/api-tests/server/updates_test.go index f506c966bb0..dbddf242f66 100644 --- a/api-tests/server/updates_test.go +++ b/api-tests/server/updates_test.go @@ -16,6 +16,7 @@ package server import ( + "net/url" "strings" "testing" "time" @@ -182,3 +183,21 @@ func TestListUpdates(t *testing.T) { pmmapitests.AssertAPIErrorf(t, err, 400, codes.FailedPrecondition, `PMM updates are disabled`) }) } + +// TestUpdateStatus covers the endpoint pre-3.9 clients poll after triggering an update: on a server +// that has finished initializing it must report the update as done, without authentication. +func TestUpdateStatus(t *testing.T) { + baseURL, err := url.Parse(pmmapitests.BaseURL.String()) + require.NoError(t, err) + baseURL.User = nil + noAuthClient := serverClient.New(pmmapitests.Transport(baseURL, true), nil) + + res, err := noAuthClient.ServerService.UpdateStatus(&server.UpdateStatusParams{ + Body: server.UpdateStatusBody{ + AuthToken: "issued-by-the-previous-instance", + }, + Context: pmmapitests.Context, + }) + require.NoError(t, err) + assert.True(t, res.Payload.Done) +} diff --git a/api/descriptor.bin b/api/descriptor.bin index e66dfa7c6f4d6140eb73b5a24ad7d69b5eba6284..bdb3305f10a84f4549d656a0b06cb2a9f11ccbf1 100644 GIT binary patch delta 71666 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 D=)zqe delta 69207 zcmY(sd4N^bmG58NI>Wu^-pg=NMCN&}VyMEXaf-$yWzGuRnkr;|>1`t^J7 z_qq!lQ4tY@BQgt!h=_!c3PA)!#1RBhQ4tXl6%i2;5fR^Kt-ZF}-+%Qz`>eJ0IcKl$ z{_b`5x%KBMqu%;_+^Oi>Mv}au&jRK-xr=KwRWpS6Cyp9-B!uEUi9mq{q9%)nD?$ko$}p3jCSU8henh0 z=f4`=-}?KG(LWXPb9zR<%`4|cm*lT?idtJAIxQ;yJiBjplBKn{uI}U3t^Ye3)~(Hl zttY2O|4}OckE{?Swd81bEDt%INGMzIjd%IloaaXT6C;Up_rYQMFK(jxM|VZ z&HUX)no1q~ne_rsIn`hr43CT9g=)yT$J^v>wnXP**+x<6i&L>Y-lp^EW?8`UM4LjA zU80r+LHWpEl`hEV=26s?6bhK1Xp^?{=VN}NO|7HBddyEs;J#{3yags3Nm^%$OP zQ~KgeX=~HzG&77a99zKfEH#Wk`G~utuU%{V()G`s2cep(M zWj=O(lr&sdNIUs6F}$vDdKZH!r`}K~oSF?&!xWTH8j=p;`cop_Q0UaxpNZiOg#iN% z7Ku1sBA%&+T-x~jXZJ_VbhU`#_(Iy%pNZl4LXVyXOWdlP3x&)svLq-!@2+UrMM-R=ypuEkBxX&3$p~{8jg|ZJ;#WD}U+7fJffoFY&TDDcxC;yjUl!kuA zhCw?_W)x2M&!{~nvn1qI zYSLa%e!&mpzoWD52{x-x>}B5DV>zqP*FTgFT;`lYp=&luEjuV0J?-o9P)s{uI;YTW zxS4jqbWUMZv%wms^9qG7mO?c}P5IUz#-sWtg&MZ=3SArg$=J>-j2M;u8vaRZ$-+Y2 zGx-hoM!)K@uweX?D*r5!$0Ym%tVN6Rv90hAf8%bEPfZ~17Wvdn3&KB(#ZdSMD9G^- zK#}ne5X;2{?N~1<82zLLp`WD^SLg=_%71oW z)PKa%f)Nh~QrDveDAPm$h5ru((AYq`h8(Tg1a1fJL z&pl~DxM!8;9-tt{JpkOT^4tTY&#OH50CBX+b5B|j?pf!#2PjCnhpWx7T<6ILh~+w; z+-X6`=Mi~)?hZpfj|llRU|7cR5l=xt3?B)GLP3v;p-@m-fPx+s3hK?+u3-46Cn6w* zk9s0X3qnMXc_IP|QX(Rx5Yc0vh=5o=7D5UUJuY`xh$JmQM2`!ReCG`Q#_;j*NQHqO z_jHmL+7a?ePYOUqjub$cKIusTi0PA_6w*R_OrQ3o090h80L1obPYOV6pB7TM8d50M zl4lEbNAtIqMZf6rY@z68yrPoAHo0*^3cy;l&5;6s<8GVJ79j4n`D`gFDQp)*Aq8M9 z+HRx(P-LV4#B#eQg`$$e3v%Ox6o9qpg)fmp6Gt$A!LtGo^A|iT6qOZrNMKCPMz>K zhI>3i05RO-8KS5RvDY&Muomq#h5&Hwdp$z{akSSn1Q17iJwp_gA>Q>20jx#unmXD1 zXRi5OPY^U=_^wa7q7uY=@(kVmh9KU{+pI*LyyvNbb1-=?mw2ut3GWYAw*Z>tS~y4tw5 z-sbv}CieC9T#*bkU@f}7-p%y@x^#WLtu}zTzP{d88zoKb8^u!QdSETOvEI$~04#5; zx8(*9%Ny%$xlt;lM7c>WM)DE}%E$hVIs2yil3Ss15R;qgO(K;vm2U}mj;p$bZVrfh z{D?M!-4gDA>jw-n zM?woT>uo7k5?Yv9@8l2w%bE4I76W2Av)H>oD*Pcez@Hd9D>uq@j z#Bg@KEw4(d6Xw?2@(Ngs=GHq^1Hf`_y)CbRSk4V0WyLaIF7s>+$% zcN$<9BrBEq^@E3I{|f(PwPaC!-S+x6U9RqxuU}h#N}fLx^~r~|Mx*-5lU!79E3r%| zX|Y_6P!f=(LVZ52H9D&qz^RMV6lMalOyGK?0s>HE%ml=8NxiK= zGG(Tv^1OwafUFkk!wRH{BbYC(w9X9XOQvkJLast`5y%q3*a*M{ ztnh3E#Ptf#MwznFO0g6+0b;Wh{*tqQk6C}~x=4Ms_;!fg;rS}mU4ZGe(M zj*{{@_`YQCvevUuRuC3iFTsR`fFNO^Gn&_X{^1}d>plNu zs!29T6gRBkA5h;ejlF)5pVuWDmcKtCj(IdfMH}jij_!as*-+oBk7jPB3T2}wF<>p) zXe0)}a-%0PAeI|FiDfEKp7taLWNG0@tObDO)1JhDSU&9;B`ZkvWV1Z9)Ad^6B2cd` z!q>^TjHpUs}rvVt(vv!2s{in?^kqZM&ao{>~jp7pc_ z#Q53J38|($=V>h~P)&JGXzh&t{Egvrp4@;KJ}2b%A0%SAmb_SBcPww&6#dV3FV-Wj zzM9oXWd`1k`uz0|N=-kja8czFRJNnOT+zD>!sC}Aq@%DbwhXM7>I?ldHOw%4slM#y zwz3lBPPtV=kU$m;MvwrU?)0exq?XelrcZsDCB#?!I5hOs75hM`HU7jGzN|3u{ zC<;LWSt)#pAe%XY`EE~;K+JdhWG^d0?vcPkkU-W8Cfxv7?(sYc)JEgUvhw6!vHYr9 z!jpUR^Ph~)y>xH5O5w@9;VOkE_xdEpgX77)K8ef9ll$aigdl*mXrJ*UfZpu$JPE}0 zKF^b7<;l0jQg{+ri{5rTc?JN>w>?h+v3%R}WLbG~zZWFHS_JBz&-=IdJKQ~?%Ke@P zfY|P@@9Juku; zOtPm^OFpfyn;Es~@|#}y@(ZHVL>m0JcAxq@s?hBdU&^D3@(kOUWZ3?-8bL ziN4-&O=S7QF)Xf$Okh+rf3Axxe}KF|$p5e<`cA`jk%%xGYJW z@sWvrPIieiKCM>7>YHI1tN8k;(9irc5503!u92VyxrG96yg%$X6H4hQmDA=1q055RIpWI7y( zB?0g>pW z)pd2?9i#NBQkT(t&3HxPVrl zAV4fzBYSDtR#R(LRH*3NP9S1DVj|mmBJQ7$-9c8aimL8iHjS9B@>$tdb8EHaRzJOK zBp4^mEcgY+ps zNZaWkeMYWEmO;Q;^i1BcGrFwdnaC@OeLZw9|3A(@!2i#J8I~xJVhNJN*a;V!6|g z@OHX7+$B#-CJ!LuJ72pCy|v5F8XUxAm!CD-6)N1q-99ORf^^mx03g_IpAY_BgIeI5bXbma1g%ae3C?h^@! z<#C@#?Nt$ti%o?ADemX%_VQX{Tx{}-gP4qqO@6i4V#&2J4_Gn?Naa3O`IV1(lUs6a zZ0d_946lt%eYMw;?e(##FCb<5eB7JS;GWmVCc8L@$@LQJXXH02S zyeTfa-Ax@de{PW;Nd5pRq{pss3E*;XiETXr#NREkttUEY{@f~-l0QJo;;}1S0u!vM%`Wj_Q-2D<&q8$n`1Al64avccD+Z>7poyp(ye`;)M9f;?tv5Aomg#^QyGET16 z&N$Si%Q^Y-yP{L39gV(;2T7!ve%Jut{3CLk_m2NzPwTqw`rGIhblF3h*Q6Mes7VVt@OTL;alg|SuGI%rNU5|c(X zA*UAQWA;ZQMl6a`w@HX|h_WcQO+p=Xd9hebq|gdPImdE=|@KOMGqsv0UPFql3;dOMPwtMOl*@eb3-;443*0 z0b;l`+!J{>x-4elCi|~CNK%2^{;PcdyHVD#EVhC_jhHTrZR=GBtv)V~Z8sDUHJ$f9 z5HpPP@;@GkzSnbQY-_@r=3}c|w&Wua8SC=#3;?}v z_4x?IU#ri@n&#swv6PL8KvZmu#(h0X8}t8skKV6}OHSj`i0P`>R-QFY$kpe z`U4TplKzb&Hu}WpAh9<3#INZ*wn<`1B?5?Mjti&f9e!OJaJKC4;}B16lh0Wo_M76< z{8N*61W)*Es>!4VvTQnZh-`YoXA_N>KH;;eriu8JgqCaqB9$$h00e!?XA=<1r+hZm zbXI%Dk8Gf*W=3}7nf#66Gk$ynF?>eGx4Ra35wJyiE4x5yl7^seU)}}M#8F&qiLH_X z#Ko4_b|=!DQhpFsAy)?u5KqN2e97iSrUl#R!Qf7Cc!$^lIP>PkMdg%N5Ad) zd~B}`I%|xH8=VYtgGR-F0i? zZwz1clO9mo{G``W^X)Y;9Il4s+iUsXjzl9zycU<7eC8ZXUWPkmVqu0em(g;|K zKtu9LGfHRc4pa^i^7Xjnc7X!1e?1;FSf6=Eo!Z{;Ndc@yZ@4*;zcGBnrv?ziH+*Vz z)NI=C=R_dNS!T2^=EU!f+V7t;t=R7OwB1qD>VR}wWgZIYl$Z2ZFKW z)IoVT7pXBhb#U6p(Z!7ieJXX-R66KWsiUUSAqgj`1VkHKDgn62Lq3&&|tsiUUS zVLveeML5gE#FLa%I_xJVAcluSCuP-i#OGi~Nu?v2gZ=p%!y`Tiffyc<9Q+4T;FMZ& zEUp`u|NdBXRnKFw?VmZNP{&X>F8#QYD*y`0x4su&b`gO4aXc=&JsCjUACKF+EpVc5?#-h@%tnFt^F~l!6qaPKqPh zObis1@9q^h;7CT{$stPsQT;r0DjKpQ`s}Jlou}scq;cn96hh z4$Fha@rg|n9K`MT#I)|I${M$b%^YWg$6N9TK8@xz-jbMFe8 z+>VL)f1ZdIG)zova^)m!CMGtyo~jo> zWE;7i3d~E~|(s%f=R0cP7-bT^|tu(e&VoVGkPR8|l>a z#L}sg((v@8o68>{o~9=wM%u{Zr}8A;g>3|Vo^Ncx`ZTk$lbCAr{hhKsnG8C=2BpmTg9u4X1>w==4f3Za=v zudn!4T+K|*{;EOQ&OIx5g0w*w`9?g=`l3@Uy4;@?dL_bWR&s&AgbY`+gDcn^b z@7Z6tQg)vmJPEtc4xY+*niD+1?x4$CtXJ})aZXb9qe%FEPBP>>z7xfiDxl++^tYd@G*jCYOERV6_ET^MWfR6sXC!;%Z(P2_mQFg^?g~ zYF@%#OgB8+;_2?-i6sumUyp1AyE`d6rs<>`F78fFgQQzD{bcELcVh3WIu+XCYJPCV zjtkIv7Dx7w&riypB4h`~{G|7pz71p&rR8(u`qCg?8sOcI6-z~EKG)Z0;%EY9`n>f z`mrSg=uc$wKrjkQHrGIJe7)URVC^H-Gr|f6SjL#*Hl;$7*R}wHzQ}mL^uq0g{YM zlTPPk9kP-*;U!vI_;qP=+E;b5=v+%4OzIx9$)Yp99!yxWwk2feBCqO}CxwkkyYz*)EzzG6UTe!_(ph>4avTrhrXO8$(ZY`^TUdSoh8ncb3tx7 zM5Drrn}>jyuJ99LXPprKoJ8%#v;|jO4v-P4j%9xMbJD?Wh@=tQKPSB%k#*J?U?n7K zVQB=pQAH!BE0d02oU5(<-6-lTpGZc3NurKs%18jUxHO>^+rK2IxJ@xYZ2yuBbh{}# zYelIwiB2(FCIFC=<}_m3nw;w62;;XVgB=TWR^k2EU`i)I-ZW{!_OHQKDE_a(R#<$M zJP~)3p?Hv^+Rj=3~LTSBJ1d+N+bZe5Zt$9}1?BHprWb27f4+3QM;<6l{fv z*CbIN>oPPA`nGjhXn9T2_v;>FyC(VOB?d)?KP+R&T`jZ>`mS%p^x>r6H$24j;p7rW z@GYHnDZ4g2Ab1$$lAcCv*M{dK#Je_OpOzb|!maC)sGWs{ctL*n$=0@YNqe{JrnAa1Ja)-(Q2H(Qt%=w?+~v0a~ZajF4G*!4-Xf7J3p&Lc_G)=Z&RkYnY}N~Mn^ z?R=gJl|GVKb-J_eXWJ0+r$Y->l_c`RT*z}nNFX844M~5WUP6zLh6I8;LH9YH=V1SwDHbQSuW#i?wS1hT;|zpNDhpk;GX za$oCm5T~1y4#F#is7o!`n$&GKPVIuh*2Kz-T~vT?^OF&f?~`OzBcVVr!?vXCzLo`I zx-F?W3I!5mo2Sq&g$v{p$LFMDGHSa>$3TuZKniU+55)F4k;!M%!LGIB<)rRl{_`w8 ztL0^xafI)?3g5qy6y7tw2jk-v&-Y!0@1vbbbgIS!Dsqem;@q7{CnxiO#M_zJ>(;JH z_pc_A8?8Xf8I~k15m0d#H&B4szM5F+qO0=#F3$--UOOg{6XGCDcO~82Falz_%M0(W zO82iNQ8()`jUYE&(}?M7NlSMpwZYnKQoF12{q7{{Znp3}$Wa`P*zQhxI1vHFba%pv zQyybirTf=|DUS){O|{MvOk1?j5^P1o?+LaJd0SERdxEXd{hnYdbpM9u?5>!n zXEzGE2VHEYLicYb{kFZL(Wi*e{eh4K&^^eHsP>{D4kX>3!tJX2mk%T(o9JU9>Q+k* zC3V-Ou9DD=GY@%j*iFddgQRe6>ck%9#C6N;B`~7IO;_JkXP1zZUbR@)QbQhrboR<>(-)d@o}() zEkKUYx~Vw)IM@nVeEfy2io;{Ulm`v+ra~6Sf~k@d?_aMMq;dVGE#JL4mOS z#ETCgwx1+Dokr-UWO3ZHIgrv}5;=7N!t}V;9zaZwC(TYec2lzW)Q@8z`44mo4~Xrj zUf}_;{nQJ*Zu$cMgy%OPg~kw8$l`=*QXOGJ7AKM>A6CfXvk(@t0BO_~2AHtLXCbVx z#b+U`u*Jz>3t51CkA*Bw22&x6lfhKT;&acc-GnSaPLhH!{XA(gO$x^LbFWFeDO-$9 zO_KsCai*q8LD-H>O_KsK9h;ga?WW>z9Q7T0w2%eJi8LB99hcfFr(2;#*kW92Dy^F? zt*=RKnFD0saQelNl6Ceqsi`y?F})^5r3qPduO&C6b+e5ux^w0Ysg)hOtFXC|kN34l zKt3T%U4`h|5s;H_Ol=7Z#O;l#En$I-uN%`&U)3e7K&Q}dOiw#ozl!cIRCH5Xn3Z4A zF>Y?TDYXS^cjb`@sZIYtHuI*Do3lX*ySw=jNYDvsgX6*O$|JX?Q4i}dJOXm_JB^s$ zn)dwSTug6GM><{6(p`CEVjA@{TUtSGB+`iO#I%17QHT!Ex0qg-PiV#Vj&zWBB|LIRdhs`P_UW!XGBq{9 z0;Col!U~T}P5Zn2rV-Pr>G`hG*4@)pNZ9G=l|M2lqT{YKlGh3{8sQO;e|W+pccooj*@8w)?@9*`Z82L>9Wz1# z!6P82I=bs3b4ExY;gK0BqQl)sDap+Y`9r-96gpG?h6S=tH(^{6Ft zd!==MF%Ig%sdG~nhlA;U4`GRUX<=nP_q2FW%RJ8#JyfYINL@WC0#Z8^+LgY5@Ub8@ zwE?833sNg5^-zLY=!q6cu}~hcGZlha=!up_Oc$mmHd=Zp!`$PE7D#n4HMIf4_MX(r zNkDAxNlk6^P=Z;MS~&^G&K%FtAWRpfR!#zHtCf>_D8bwdOSn=LxE2AOWB_4%Z?F}H zxi|g7R`x|L4q+h}kT(^ASsYA-U={~cA(;D8t90}zB(Ma~sSO~u_oY_q0AhPzYHFj0 zzCm7++S(CFku){60m5`iYLyNkrb|*&8$Fa@?)T&btVJM6ZCC<`?ft1$z<}7^pIRxZ zhce94)GA;=il!l~5X{n4DquQFgkYAYrZ##g!8{PcLNFl5C^TaFKuZWK4D&z;D@tWq zu!Uhj-c}f9SuhoXSr$x%U>;1Z0Mni=U%UG1UYC9Edtdw2KmN|D@I5j8TWYU{d#ZAINR*3gz5-H$ObZve%~uWG;_R}A zQrmn*BVis&ZSxh7@%B*K{X!Kk0-bPqDDB~c&xcdn zX-E^UA5Ph6IFKvoRZBLcbqK1m|oh~jnY@y$O2@KQkst&6QBD-0M0k2 z_A(oYzm2KA%m&icjcJ$jwW|VMS2w0z-EzB^c6F2QDv)WqKyK9Qy!{h1g(k=mQ@UivQgDSmD#`?_8ZjZftd_Kq*O zm?p^QsniN(K>R$F)?6(LD5rY5?4_LdjQEk?Z3EUK(CPWs$D&?&`<`(!>Y3EG*3yXS zGpTLN>ZRLewn*P)hYyfVOesHj^VNK2pZM(j(wo`a#!;egNo}hW5Eolgdl}!mmTXJw zUbh*kH)n23?T6QTYb|iQbWFBu0QtqC)NR)Q5NmsC+ckh#ZclBy29OxrC5GJM-nw1m z1#u+XHGur~P^#NCE(E{|FQoQ<8K{l6T?2@t7gBra-CN(p?+{0_T?5ER!1-&v;^uR9 zgszDD9jWcp;2b>c@K3w9&P^|KkIVy&FXxx`j++}^PHlq*=g862%4NOvP5Vv>D$f~6 zlARc!5C)~RrFW7g92YJFkMSb)Ma|dMJ+LhY21|Y6>rM9gB zh^t+xZENUVkT>kR#nrdf6{frMyifey#@*o#$b7XsZQs-GzRXv&aos4%{xTr%RxQwSme!)m97j-DLJ*5IQ z&Y{%yl=jj6bB86XWxFPj;#2B=CkKG*!>N5a3dHr{)V>_;qkM2gEal5lAO)mUeCYI~S~dh)3k*;BM1yf~hAa;vRAx|{qYzwMVDl}%1yN+M}tNSC+T^tvMNFevVt zKl|H^A9MYe(rw%|QBo(473ST;aAfsVQ+Z}Zi-4&yryVx*81p|8`p`U{2C*W zub_+h$QPKe09amEwD}5%<#okQ?zMHFLY0AZL(zB)D4$609vpw;+#8BEQ*jUvHxzBA z0^;F@;un`IKhHOwUog|1wS7cNjW6cw$7E;ZbI*;VD>#Dr_@aH;0K|NJv5UWwwmh2g z#qQs+N7L3l_VL9YP6YN*xplMjPw%mGD8Dv z^-<0MWJl$NAf8rUv?&^hXjCd8pyV( zqHd);6M*HcqRrGmZM2!Xug=tSqzgjOK=w8j3u7SY5#u4~*BayQKcC~rI?ec+Q>;0W z0>syxVt2PQ1IS>VQ|$RY9jpSCrRNlTx%cjUbyA-ff^mf)3HHTo@N1b3I8mD{#ejsJ z7s3h;&kJE=!p;j}V;6Q_2rB|$zJwKO>?=e)znFKpHy(BofJpPhZI*E~zu4Z%S6YcQ zzi9HcZ$UQLEfhyGR{#a&WABa6ycmF^g~5@mViy)oP|%8_g++UL+P5Iv>lTS4H*~lF zQ1|?6pT;8@g+k?v!cCV&?V@7MaT2Y#T2!NjJ&vLI{+{t{NiG-G4|N(H;EP(d%KrjeG9VhZb`U*Q1+61^T@dACrd&Iq3$Kc zn%jtp2_cpgyZMok5n@SrM45X;OTr_P4S7o?#Q)H%fZLbmSB;8)d;Zd5xxKP_UnTja z#SX3z2_(c);R-ioWFOu#@gTE9UmIPRt6GjWzn=UkVq?w_T5kYDmD=EEn)U*S#(y+2zlImQ6u`q`qrY69Z=+2Zh@m}?X3&lX4gy}1^K z-zKhw|NF_q+s4Rb>Cg)eDm3H(y-mDXPPkF z?$vWY{S@W}xy8bdK#BySo(DC);B_+xF?qr3=6));cZiATW*}7o)6D>Sxx?#bAeKA4 zZthnQGJaWTL>Lq(B?Dzp{^rtO_QVIo&&!@cf%tjZGiX1h+g);>7RbT_%)&lD|D5=q z#$Cmdt2J{Fn_WfQ8{AKsb+_2a@4^6C$rtnU7Q}tIw?ebKizP?1KrDBAn(bGR-?`i) zmcrLSL0@R~?9x46rPGAr9X74xBOvgvvI^W&`Ht)i7VX~Oa? zKLz(w1^$k7L8b^GRRUQk$X$`0dhht<0uaM@{BogRp(7#Rl~Ewe1)v~ZE{q0X`EJO~ zj#$3ymka$0r!Wc*O2}#M>761lZ(pC?n(v++Mfn34#K}1a!=;~sw}Zu+dmV*2-VPSo zUM;h7zrv~X<4_pDr-JfB7sP``90~*YR7?(q0o(~XIPB-5eg!_G0Ugn~s0B#4!+tIT zVtd%nML_1F!^Nf}HW$eOor?|^n~&IBWXEJK`p~aE`qz?=i*@&u+}fi*O&=GF?qT-V zwZ|uNL$0KP5`o!+VQUWn*Y}B^Wr3J};@2KPdiRN^z5crP_*6UzfdJ)am05c<5L$8cnO}kQ*A>X; zegy(#Fgu4D@U4&q^4$<`e} zJd7*Zx&ui1jVsx@L!hSLxRR|q`s=#m+7N>40*(3dESOz%CvKB2r1LfwIQUe zIj#*MZOw6Q2q|lh>m{Vjh5cpCaec|HIRHeuzGSORAd#*w*_s1Lr0Ywz=IF0$jvK|1 zOpQQxPLy=bk&pOBoSb`O@FR25jU`)ha3+3kEZLHyzb-j$5I1U1BLBm;C>h-z`XZCw?IkN2aSk4CFImZ` zzZRXQhzEJe4P=)@DX-3q`*BgS{+JRR$@*hTspez|tvH%eYH<5u`s?~*syLFF5J(-Q zR2ZILupsW8|JNm4)YRZfX2z+dnyb=rE}o{A>?@i6`Vmkro@D+4QU}S`T@ruoCwcH7 z3yZvDOBl|HbUpj1N7!cnN zcv1!8`+<_J!2~+r50q>THb5u82gSD(EP=HIw4xMs8Jc%^AU-|cav#4V`tYT3^2-NH zwkYE)qC8l#S0+I2;e(}4E4W|s%0v#hILk}+%0v!nBK)ajuS^E$N^PY?meoJ${W_jJ?OYhJ{wB5rUPQRp;SA?o|^o8&PK89sqvY7 zHkPK{n03mR{2Of?%zr?{_21{BI%;>OZx7whyWP@ne3QfIdre}JyFHi_eY z>X^A`Q;FY=k9%H88(rE|vR6VtTyHAbe@+16dQ%uk73X?W7)UY^Jt3}TMi?NI)DxvI z)=ClnWomk&WbZYA_fxP`M`CkUQ`AXJE&y?)b9U3uxrerUQ2Ix|3i;N&y z>HvA`pRd0%zPw>e80s?VY$@3*kpTr+kZqM{SE&he&er_=AI3i&xwT|lu}&CeSz+c8PB?ZV6Q zN@QRy*-@$+m$_xhK$>=x>k=R<{E~Y! z0-#5`LXRZMu9CeOp%q8FJfRHKb;<6My%_txNWX5V9`WCn02AGEk^_pRG#(1l$*HtPG%i zeqBN!xgNOZoNitAZ90$aM@HddpUzEQYbh6wy5N@6@_yN!0wBqN0 zXYhggp5vhS5j_E<{7}m0{x$A_Bl*4ZgTavy`a!>%pcO|4{c2*Mt|ktNBl#I1AVmqt ziy!g28i1oi!I3N`4u!`l41cI(uVy%zyK$&wuVw`5h&fcUS2F{3wQ*P?3Bv;^Vek}I z#XT9DQfWIJA_?IiF4-FzT8VVHWN&DIL^>P>juHOhFmU9h$PtMogg;OS|A;IBzDgjO z3yy?9!u&`4LV#8R9r5f3B+wDR5D=)Bbi^+N2I{Kis00!P4P@tpDCoWy^EbowsMpaz z0v+`_8c3j{UPlkqHOn#aBUN-D`zK1g5R(7U`Atzv9HQUH{L%o3yJMve?xheAcgMW2 zAE@h-TT%GXCvVl62ob)pZu$F+vW~%V>IX{h)AD{FS39Z8^Mz8uhrM9TOh-+zb3cwdDG&Zc+Z(PZ9XnXD09m=`?X;h8_@s z52W^xf9?12c|YWD>~G9W<^$=+jhV@OAmic2%w)bmCy{T=Oy&<#nSYbGmb3&?j>z0z zD1ai9`9OT%lv(Wri0_*+llcOj@0&7{`Ga&WxkY>n$pdQ%Xkvcp&*L+HaZ6^xp5yqv zB{N|UCiDKkAkNavr){LdVvb08mM+Tn~;;u~eeBSFHL zVs}?~>LTZ7^6znUIv6C=!OSdweQ$8eV*}~H%*+ZgKpf4?tPnFuIe)f1 zFrhpkKOvI^jhnasCGVGJXC`rJ$9i^V5*J8^XJ;mH1?sb#oteZPq+g!Qm3|7*0r|8c zQ;FLQpks40lej=!&&^EY4pL^nTkeeLR3JatLdV#_^A!M=cZVk_G=Fzya&?ey3SW>} zO#oO+K=&A9{Qg&Q(y}14k^rsPF37AT0A$oJ$eQjk#*hQb7z?uIdyFycn2d>qnMu#V zwPbNt_qa*V!89$-OnMGh>A6Jif$YWs*6O1rnUkI%9@dh~>ZCwSmt-bAf%I;PAESd+ zdM*`DB0Yg@4#?bwSpXw;X=c(BsEsB)fjC;4ne-g2(sP+O66pzKYe4?_uh}rWEObTO zFUw4Gatvm&@a9d0W!D}oPJak(;t;NF79%vOmdDs!+9Mr-D(-vA=DhFd9Pr8TqaH?2f! z^|?G)Rpu&jBxD7o%)nD+$IV3m9IXnDgiKatR{o|HN2@X`e-Bobxmp~FMg&rI$nxdA zAq4=AR)_m6Vso`81zK^mI>i+(hA>^_f)^2kWo(Zt&~@tRIEf(J zynq4nFt%l#o;Lv_2V9uvynvBIIv}@u0W(Ae%!?9O-s}MR$<>TKB#p9tPs4T}^I`}o z4ELg!GMvqIz38QkK=uBjmoh_C%Dg0#ns5e?-(?jk)6m4eE(jOBF0S2O;B=7N z#(m`wtz*8PSU1oE#(NE ze4N*PxX2y*cLIT#JH}jrwpJl&30O^OjeRKrG+N>}}2v)j01+FiCr$>{od9kL*`@#|uUdV)9OA<+~xe4t-Zl z!b7eRHmdCv^0&1fvqoJydPDmGobbu^?ku6{1y3_KB|0_;@e!|bYoP)y?Uc3Nt zc*2VpAX#$4ix+{KB`3Ui8LC3*WQfRRf+V7+xzn!ZBt2|;2}sD3A*3+P$q-WX(#a6g z^wP-?QuNYT{^6Zw{ZOHzv1O;107M#FHWdsc(%7=;B_NT;mTkp5RQ1v|;z+m<$afHB zw?i3#qicdAA*yT2rk7~N(KTh$OG8yJT_=v**x>>|wnLd#TwPcG;_5{)U01eu>$KwP zy0Yz19;%Az25}{GAy7V|IPibip?pK|AjES+*|aU^;Nga{mBE07zoBepFoF8WZYWzB zY^X}5@e)E<6Uavs`S1QeUUQBQAw(mMFI)YKa|kiMZ1pc7A;y31$13VyG&jiQ+-15y+PoWm7~K1L@4f;7TOX#Ii{u zT5&b8Y`YbPYO!sSxDv_)Qf??0&=EVMZ(an%)uiA`sBluby%SEf;%ZWPsN0V{RO@Y% z!*xN0lglbsn`vW2PcEBW1>$IO+2kq^N0ZCQ)!|U%uv(Ir`FC$;|5x1DGcOliIc}IT z-*o-~rOGrQul~!rnfFQn{-&3$!UV+M^s-f$fIQmiWveg=bW&$}*(yxKRISYr-!ejh zvT@)aZi{>Vn7;*-O{@WNJ;SFP5Z5!xR$vn7T+b+5foYhs>Q zF{^9^CXVBGR@n+nK=j$Ha;LdvS74F@&hzZD6`16Z4$(PfD=-aHeRj9RlAHyy^3R8j zjnBUH?hr`W^==6y;+k`~fV<09U=pZ4?=D+`X_%_A1!d+`$t)lhfwJ4v3*g=?C|jKg zi0cJqt1}H#hP+2Cg^Gau-T?_SJvy@qfaN`9D>nhLyr*pCreVr>_lE0W?A)7=9T%V7 zcyHMh8RuYfZ`l;tFy*%U!V`q|?xUNc$Y>+jec=uW(cKsBfGD#2!W}S0c3-#y!gcqH zYj+1Y9b}4ZnES%n>^O zK-m--5dRO9O_2>#MfRZFW8o4Y+YicluRq3p@|J7a4g6r)bQw(;J}5cnp1UyC3W@t0 zHH6JpBi88<(jf- zIv|#7gqKCrHPn)I<+?Xa(=~ABy0R@Y8&uOhBHa;92jugD{PG*(AD#P%r&kVQ@<`e0 zTR@yY;*+95#oVLfL6RHD8kw10UabOnG>?|8z6I1q({w-_JzBQQa0Jvb7s+8%DBaMgA% z;y9UJZ1*BgpgywgUc@!1hJy7W$lZ3R9CVRXj1mb9qmxMqZ?UA`>6wKOKOWrEi zjnDscVmz|vTb@)KRT91wDrnyvEyRHBc^P{qT-37$-qq4m`1adha zmF?>;fqJh#D%;mxjmoDVi*Knb0QpcOZ=4*Tcj?Dv`>czD`24tRpLGH8`El7k>k_Cw zKQ7y6U5!etpNLP%V<4YwFd(TY<_9NJfcnJCSDNtriI=a9N~E8983^PXjW5Z-{P$C^ z{?w~L+OhuBtH4Hm8St4`We84%0QyfSN4visaCGaxVeoifXN-VrCm zKlj3nCJaCK!mLrNbJte5QvEOs%;yl5{DO1hIZXg!UR$y6vw)ahTe0nrwo77)YhEB3;qQLA(}^1sb>uM1GN82l&ytMQT>E2iZ*h{=r= zw45li;kD%EO5Ku58~wLA!*PCd#j0$>Rgp~)Z=%S6tgtKY#Up?rI-z1E7a*1sDkjW; z#F$XA)yi-cW)sDcJai!I>xvU*0EXxvU* z0D3ej^hlyis+cgN6-SdQCd`JbFq>R4VFqM%T~T3n1^~;+6%%GaZ8TvvT!q;k(gh(X zAiDr6sHWRLj(aIZ{eolkg|5ovZ;#R_Yj zh?~1AR#+RZ(rl)<5l#cL;~;;1dff2Snc<2>kng}#E1QI5iTd^V`tpu7|v6bv_Ew9}zengT1DN9sb zc@2Q0yDL^+1LElJij~)ZIJ(=@%W#!s3&fFpJp-gBkymEML(g9jx*`O%pkklRa1I_8 zRP3{v;i|^&5f4I8Kxz{e`)r1*lJeR;!Ie69eMt-ioc)hpQ;NPaKK915&fd&zr^Dko&^86D@XM#VUQA zgNOSn_VRN?Em>CK_vMgKUGfKJ#i#XLR3nN&E=1ZIoQs-j4bDwXwFc)xqpK>WrbYS`lYO|2G7;UOT!3o?be+8F>WS68gA2E=l;pQ%PD-K~)e66yg`yr?)ibtMSX zH5HRnKup(EOils0o;4MdQv&s7t*Mxt8liG(t@sws0kQ$1q94m&0l@Xzim56fuGdyf zRRM9mwqmMEpt@dLF;z7}Rn>ZNEffP}OG3VNUVL`L`ihkUIf&2o6)OjhP>$PBv9cPF zod|jFDe;(Q{-!etL6}d(z zNo|tHA{h!~=K+=!xkdx9+*Gk57ZA%$6)SR$P%ZV8T%T$wFrPD+mI7e( zH$uti88H>A0`fJ3XsIC$&v+HZK}??UDr$t%&K6-3*)a^1ZyZojy)NZ%Vr}tS3W(tr zucb!Tl5Lf`4^2yrd9_8Y%3tTXa;@IlIHX6cX9(6%^+XVz+1Xk*c8fhzDUV zAWM8vQ0JesCwLI9*yF_$=ip(F7f&NqJna<^!eBr?b`bG2@R$6})$a`#D-vq2mr%6g zXRnt~K(X`^N}%4^yUOZt!h_^j!ja2cpUp$C-0`hf)i6;Pk z-S5Q{5J&sHcmm>Rzvq*YDxMCABN0zP`H}+h)c<1s#?OJ!A>pM1UP94|p95Y(jZ_JB zQ2Yol0r@(@OQ`(8C45zPFnAILbOUox+DtfkXKMZ z93Aos3W%daUO|mi1$9^)2|okba#67gSu>EUI~+!#NU6hKO3{j|!y={5VpTb+mV8{P z%k!o8$7lBZxKeaqTaQwr`$V`{R1}Z}zNshx{yy<43W&c?yov%cLO$^-N}y9ZpLi8D zN>$XS;#*V{kmbKqQ9lMKG8F~H_oqJRfcXB@t0;la_orS(jZ!-NOnl4FqXKIQXl!16 zAim_+pLv19ar}NJ0!MzD56BzJ&nlh9R^1y)Ip92>tk@e$Iiy4V^NPKp9Ho-#nkuor zrLk}cy12{Pd7J;nH?{5m5)Zh7lenO3swUfjdX~vHAQyB^)nuDMjd4xYWZNiRpk7xs zWd&p}LVo!&w!~joHA%%md|p>INi|9()eT}I!~~Qb3d@(VCH{u0X(fD0I3HK72+a(vZR=c809-7LXmAOfjMRBcOq6A;^*t0onI*xp<< z88%8uZi3iKZ5Bu&B7f$=__D?cReKS}K};r8ZM`{4xoTq7q!5sjLp8i%B%-j_#HtA* zAm$URwk{i`i?~Vh#N5b(oF?VN_=2>VCJZN4O@aY2oK&@sT1P4GOcukQ+EqAda#cQR z{jt1&q#5JM;UNnTO|IIPt+aAwldJY+t3Z9oldJY+>nLTTJH)%F7$9Ya{JnK?FNTjW z(;ZcNBMZdy9aa0V6^Q3M!tk;0obCw2NB%Lxw5k!#D50)t`OntJeaG-Oo~Ko<)&#`! zw5rvbnrg}PYTaL}uGZAVnbWI9_uF($s*h%fPtiv}*%@Ic;jDY;~K-CqQ z01W&&p+^#BPSuJ`wBl%vAI428jOJCX$OPo42db{f1i*4$)rw3&Z8Txjq{3*vbU{b} z$nOqRDKfQtApTl@+e)@_&97Q{iAFrluUdHth^P5gD=z_gnDeVvUJ|IoWPa7kOHC?} z7KTt%_-5Eu3qvpwNDD(S$@qmKmAg-1KSE87fRBd+;t+-lJ9qN9JsYwOVQgI~`1IQ;6dAq;H zSDdr7YK0jN;$dmk3Nt`FEUjAIw@GErGVvfx0+jD6=KMA8f6lV-1cXwSRjo$EIe1v+ zxu{8H&T{b}pH%{>Jxp5__xZ{4;6P|)d3c0EE6c04j|>a$!1Ai?BNM3OWqH;1ku|CQ zSs@XGR)DfCqGeS);HniNgz(FXs_i4=973$9+CDNMAy$M@V*Iiqj1u{da;1b2LDMAs zva+frAs()9&&sNmgn&d^S+$Z7kVq@5RuXDbWz;H;WF7%haHx7g1LWbhhKDQKsI_Xf zAzE?OT1C*v|Bh^~B@b2WcI9jS7N6Dgp=#0n3yfwJG!F~Mil71V?+U7}ECj&c!&NH_ z0rB^6)yhIZZov9(%|8$m4%vB(5&;*3a}QftGaLc0E$e|0P(%fCkGJU>#9~3 z66k!dt6Ev8S!wJM@%>HhH-ZMVG4JtE{Ec5f;-?%=!|x+i6Er{s%_G%L8%@y20q1!` z)dYtc&g`>85Wd+1?tDlmZ zRq8w^Q6(dQ{BS~5OEBCd;il)jt^#8DT-8c2&8l*q7fT^ZU@dw+U$!>Btl@dDt2l_s z^Htkm)U5jE#V~>)qZdUO^>2L9OCrv}fPBReBoUY{z7$4?5X(zm z64A{r65 zlNHvho~)XMV_p?8H27PudKtt)e7@>s(CAvSyIMD{t&>5cxuV_GqIV@(B&8LH{)`Trz;?ad%PF|5@L@ZnWI$6fZAwc2#BM- zUJQ*^F|ck0i=IFNSEv(LO)&MynWl+lwI}t8)`W04(43VhE^> zCWc0<7}_sg5CQ?R92YUv>!J9(UjXUEen0g9akbyiZa`e^_W}jT)78t6a>VH)T_2q7Bipo9<&G+IdGplKig z0S|^dDhzVaYam*QbkNiMXw^W6#F1zqAZvN2fi48#=umJZbaKdRAX;&B$ZMd{s(}uR zBM~k@R`U6-jR=>+Ubt`&4~M;Q0pj6swV&I0JX)pC5%C~o2IL!y{CAJBVf#qvgV4*7 zs%_Zj96TJU+J^1XDt(TM2cZ`r-&j;_mo=9o@5zn^SE7NAdL2e9u8vl1m-T4XK*z+D zs3;&`S>*q)DeiynvG5E<1Re7#igWOA%&Vx;s&S5s2T@T#KE9~t>o>){uR0!F2p1g> z!$7#`xYtqGa2Jnz9VJjl+HtR=Mr-}&gv1ao0`kR0HNW8Txc^lrLJW~NC%lm2Bx0QK zLJCNX6Je+sH=PJWO}0G!zpl0*3b^AgDiK1|xEn(V;Z>u?C2G{)r@HIh zV}5_!=eghRuIlPO-PP6Ab^3fGb@<1EZu&+IsbS7D^i5(&0jcSm#E=3~(>IABHOzU2 zt_)!$%Lb4aD#FB&0`hdO40W3Zgw>Uy8d5-5T^V9X9fTn@yj)liW>(oNw-F9`LP zq2W%sEez4jVn_icaADi_{Ncwh4Ap`n5k?C`wV;5seqpEL#?v&qR zVJ<5bklPPJ`~Kf@b@pvoAGbKvm)^;O_2RJ3e+C!`>&0Q$@3||f1iF#2I6UwuXXp%f z2Gf$1Gt{8TcJR)8Cwt8EyimU+!qRkWQIEC_r2jp$mnc3P8SJhw9P*Liy`Z zT^b|Gg`2|6UAmT!pyW-Ve*9>JTgz7qQ(4P_gb9Q`dCQ*9aq7%z;D&g#NvK0KK^;RL=&`W$I!MgwdK%JsTt3Vt%VIlEoZIRDgZ^_q?}% zYbuJczco~s24&!IYcdK(xK;f2P+b~82_2aFBJb_r9;z>cL`e;G`5xhx?{!kCOcWre z|Mu<|^W9Hh7pf0~L@Heu>TBRYIIIhM`p@G4d4%i2-Y2*jBhby5b>Tt&3!Wp~s=Yp~ zg1%jE2kxYb^=TEdYOhbLkRiH0twL7q^=TElYOhbLkX3twR3RF0gsj>d?8cp3!L%Xm zIvL3u62(Uus@M=}Ov(tiTyGQ(GLnJ31!3b|L|tr5ImkM_F;oWwW#F(eR0qQdw@z;o z4l+uCyypafq|?G% zgsCjYK(^JPeS2-LemKA(-Nypqy(O8IBb|!9Uns{pN`@yhY1CnDSOF z`(VmjY|@8?_cSLsL+4@J>5usn2Rt0AFLj*GGj!t~>&A;BWu?kNGnX zeKc7BNkoiClLc_3Q>%{&$q+|UC>KCahI!lWyy8KvvT3Fao%C2(^cMvH;qX|b4Wx<}6OBE}Y3yCXM>IB&J-FA{hXI(U zyAq8JbeXELfiT*YXzWo=WA7G5qOpN&$wTjp1~5o=rxHmmyA#DtE{t|3ihGn(+%F}H z8^}gI^uA~SlrJUv8|X4se~)td`xOyEW+9OMddT)mX5raDgm@*+gM%j>DYGC^Js%l<1okig;fP(O7%$_?W;g@f$X zfNbMMm zje0wMkfOQYPL>gJVf3~vBZC;dqsxUoVdm@5p8q=Ue(njY{L2obo$B5z%0LWgAbao7 zc_RM{0K>hB8Uey^Z=yzk$g(%FqXl{mzBjR>M>`F^Pk4(R4P>*P*wNF04yz#zg!{h4 zkOsniUt&lL^xXF)hV*DRjQ0z7u@8Xk(Cq_nrm`@^LcQM!VhrCqg2j89;X0G{0_O!}y^=$m`R~7sb~eXniPi_Q5BJ-N zp#XS)mMDE7ygy6y(P*c^|0Nn+<^z!ZH=34MF#1C*?tdja0w9$C3e^!g+W7*$OqL5E z`)pY*hES#W1-?uc4IqSHCX2>schSbbWeSS<1Z4BY7c^eC*F@W;!z+HdF#$S+% zBJ3ALYU5J|4htgP>5Xwq#llE!e4y;byS=hyq4vQl7AORUWiz2=1NuZl`iz2=1X^dMM7E2W}rGads?Kkgo zMZ)5=3Rw;oM;a7P8LC(usR0h8ip7xzg$s05ERHlNe2iN!mZYtq*O%BU|B~-Mc1c== ztQSktR>;I#lD0z3o~6P;=J^b8cx=_8*F-3>=n5js9knF>ak$ zmU2MkWp>=2e4nOeDF>NZ%c8PZ{gA+6S=85m;b4sO#Vr>OGR%Q&rz3ZIb~=DIEsu1M z0EE%=N~uFeJy2d4kWoLB|2&zM z$xs7AxH1{_K&n_Nqh7um+*B^y9A%!e>;KAEgKv&>JtLeU$;c+6B6h()-93xgqqx1wpMt{FamN4Z|CjJ_db4Yq}vb@VY)U_ z=Kv6POhO_++u0myd&qF|lwn#TBK)Bx)>7g1B z8~nDY!@9`Z+mhggzCF@IHA!+q^^Qmn)tcM_w_a)-=4yoHdb{jz`4LmsrxnQ@T`v`h zu7`wXtdDd@B+&6(AL)*$$*pS}B2=;%J3w}?k&m|q(1s0>1_uFQy&=-zpeARPZ4^rJ zL<8BiMlRmk-nEa{vNlF)ijxK5#zgBixe%dEk-DRR>`^1{jsj5SrbyjUKqxmQN~*~% zad%6VvVH)0bw5hnQD^*}O7D);9YrRjcSpLwH#u|b9wGHli*C6ml1np(l0}{OMCv61 zLU>O)5aia(du7xg?&@S_-5dE^Gn?Fcc5gC8fiSu^(pxiuFuFI=TQi&761F)Vge+m3 zBYzJ*c~EYSbejl-a&x31Lrv~TZL3ga-WC>L%cN|0kVv3jWlG4T$;Ky z(vTs6ZUk(NG-RmBoxVLF+{Gsijxq=^MJ5^Akvs2Agmuq2ba3LA4mt6 zyc6<}u$CQLleoJdiqeZhr;`Hzhaz>F0^$Eqq&I~E;r~#i7N0;T`a_Xgd`<2!{}Fkp za^EMA({=dE7kdG)ek2`(;@f^C()&J}+%*W>5`78e>^yS5Kl}E3`NH6Bk-GlKf^b`e z>(5Upc|G^>NG}R);=1Dk=t(pc9}j5-Q|sfA-V_Ri`0+?@3I)npdNkz8$Zx$R!QJ)u zcr^4$R6)7AnpU_pnFmiqdQ)g~x$sPs`G;!dX0o1%s{D-=%}y(C_gc9smjlZQIBNN% zrtOJBCJ~zLi9!Y<*7jsVHaj!qd0`=D2$17$Z-yKOpuNv0W(d$_su===(esHJ((KHT z9l}V=5Fp#^$ZuQ$jD;PkL{iI+#2O(NMmv&;-t4TA7ZYm)$UZyr8&?37FD6C_&}FJo z((H_qT_S?ab6~ju`lD#&;jO!psY*Ibc15~n1;S)k#FkZDBtV|iuBh7|RXt0BSI>V) z)UzbH(YQNN&&|$Qd0DC!^$aBNJ|d{9Ps534T`+s*`oZp5k{gqftJ3&Qp^_o4=dZBr5yeNMohQrKWcyjg1-Hsy}AYd*m{wl^IcqRjWEV?&hr-bmv@nq64dKH(%5Di9BW zw@@bm7`yut3l#{XeTjt%gwZ~+Q2o4+JG1sD8n_wNT>!o3HL(5pr(CwWKT*KsLc2dv zz(6MU{%FX1s(>ZIDd7Fl(DzgUYnsfM{}To5E0yp6ejw6|<J`~|@8 z)5QD*!tc|>`~~t1KTXVEfnHU8nwY;W&iwscc*|@8$}9btzul(uUqE6617ZDnq8Na% z{yZ^)1$x$>Cq{6K)9POcYkAcWNWi|m_VfJMC=p?>*q##8@B|nr|aq?J{awoEDxRW1k%D zXy7p49{WZ9n1SK3PO7YK>yVj!FJ*uTXHK)?mDn$ z9tUT*PZv%DBG#foN$7&F_IBmK_I!R_DknMQv-zfwj&5~HR0YE61#Og)O` zkBGylyj`N%8nBgRwX&^gfn(%)9*uwzy$jVrKNI)o8#_B@? zQpd{Vv5#}c+bUrs-3R2Q`q=r9CIT37t77#b0bQmVZ$KEWiq(fS&KYkv2_sR^K#sLz zJ!rHSf0Hk?+!U)1flN5v6sr#b2&bE3b$gEsvP`Sh!by|@(4q9Ug!Vtk(!kZRI*BL% zx7D#4V?alsb`X#Ov^rKhNT3@)t7El;#yOL3OKN*8~8JZcm@SEJU}*YH^VZquXP(xW+ltZJjWZNe|@A{KnrG2AsJr zZL?^Nb+P)#C<23ZvHHh=w0T{u{xN~Bch|-09~?p`%jDj+_I zII$amH0tj3QH%b$J65}aTv**5V>fg_nT#(N?u#?`+v~E0_O18DRdueZc~^D4t;rWUwr-0Hv9Ol{=%Q_LJ7)k2q_%DGfR-ee zQS(@wYm#P2Ztm{q9_k@UHDhH*T}gt;V{yY6+2Xl0w&HQ_ski`L8<%js?9Stwr{X*# z^pGk|gY1d8&{YPlrcnKpap6!IrKFg0;n_I&z1^8FjA(r}E`%zUf$-Tla=SSx2Gz51 zUl&Lw5TUonxg%9*qe5?wYmQK%eG*Kz#|@KIXs8O$#kn07p-oPNelD)rkqRwokUbw4 z>Qrb+q52n6p>bC3jDwf$eFcQxiBCg?7OlTCKEOGdky2Fm&Uo;|Bp0D~#knaebf7}- ziffKmp?wlecEt^oRcP6R{~^x3R1rFGLjNJIc_|fI(jeO%7rLp?l0x-=j0*$hOI1jd zEf-#mgLmz!HicoWuckenh49t1r)BwgReIV#XcnWwYjN&Psz1ODkgOM*#F{q|zz-3h zNc&!o3qAfL4VC&vT!_BI?X5WX=OZeu$-36O71#XPwMGVsBq7t=aiLKgBPodWPF(me ziS?iiQtgR@_w9{Uh0|O2#0B@|9Z839Pwah&0-@RyA2LC!&q4OrIJd9Tn4C7|uW`-3 zN@IK?lzZbse{GCUqcML=8zbEQ9_QXm8_D;=-xb zhq%TNJ@R23d}g;63jR(y05Nap4dZT2iR~lZErE3;f#ftJ*TZ zy0~k6cHODf_RB@pb+&!a>Z9%51FO2*w~j83GHbSUno`6yd;69yCl&87TWU@&{vcc2 zIi|WzIiJaFvklEv?d`=c6g$`@rqtGMI<45v+_2^Mrxjlg%pqIuIHUMfFjBN~Mn>e| zU}P2@ofl-v*}>Ias>A9wf<%R9hvek0ih{>L+y3-oui@lTb_o8_O4%%!sL}+Zp|isX z(HWt%6dz_UoKYM#bXawLS_-OR#5W{VST5NSP)$=RzJWF(*Qaix-E~I3oBeV|amW!P zs=rel@)5lcLZeG#a^>u3{GYxp7*yHO)g3)@Ey02AEJ)Go*(TfmrCc}rO+&G|v59ra z5s3oNHn9wOBu!u&{dJy;m1=+ok)Yi0(7ct+c5sz znHT=3;YXKWJab@mQW9q4Ip9$`xQxdF^9U0(dl+2m9T&0=3ntgKYZ!mh2@T_C%$PZM zuDyI_vE5OJ(f3LqW$1ZDRUpE7qv|Sz@HT1oFrs;VHfCpbB5fMrxYJ`3?e3YyLmDSm z+Qh`pPOP+v&1Lq8Agmth%0qmF?SD>jc;gXK(g)0)>=Bjn1wvXm!}OyCUfEM#F+16Q^z&Tbf!K!G$(*)mDQG5hBJ2?kyX+L& zAaA2)u=}!8?BH{Y-)o%GCQ3?BehR^CzC6}jb}E+6AXmN!u+6J-2ir5R!DEsU11>ui zlV1x{@l0gF6WG-GJS?#6bY21%LotJSmYr@NKCjrXaXRmPYYECv zcRM<%ixZ9PNtE{;*%n~Svx)=ypM*i?%d-W{o`loTBVISmo=jWh4uK3#FF+St|5|>W z{mm@q#L1j#YC*_O?vyYgxITL-Wa3B7;Nr@jDgjZG8c(eZM;u$(Q#mQ|)U4py(}URg z12cH1vZvdr=NHE}p3Y0BT7vSYSDM|9@@FvKWc!ifQ;XR%>;vZ)TN=-(j5ofhm_37S zv!^a2&soSL*H&PS0c_9N#m2_7I2zItls}7;A&(BA{0zp9d{`;N$KJCu>}9iy#}Ax= z^Fm8deuj(AlWyhv?b(?X{TkhF&g9F@- zA;YKK0sZUxHB3AD1bf!`#V+>z3yK{Z&#g@!Hy?A)p3CbeUWN|TI*VGxqmv0b0PH(2 z7H&>3JKo>Ls# zcu|`~FLtKb65z&S6b1VG20PNKl7RUCV z+oqRt>_YjuoMw9dT`7MleJ5MBOwd(8z5R2p4PPO=s90#cl+a-<2-&5T`Ewv-mqF$g z*MR_g>_x?1{V%Ji=>sW$S*643DStWT#dDbn>IJm7AI-~k8m8>)>9fo66Zy*OA-)_p zkw@Ji{uz^1_FkEw8^FGQQSsnIe^!+|ooS*5ZD%C}PfBjX4f1P!YDi)ol<+a1ip zc;3auCs@&Cz&22AYgMAL8er3^9^`~ns3zq2ZVOM(Id@F&=cAPhQ5-JO(4h) z{9$pz)CER&fFwe)!06d9kR9Lxqi4ec-Hv>L(X-)9&DiyReraJN`G-2iiA& zSganj(6HWk$$PUr3gjptExaWP-jRd)JSO0|zZL zbv~MoL`aqy)-q4h2+4ATxg_6F%><1O4Y9jUZDR18Yr_3kb?@A|6`xKtT?Xze~kB)lPJE*-^c@Hh6)7PgMU(N zssD}fenb+*%lKO-9;5!9tb6YFCUX#24s!R}v6mMc4!t*d@T5X> zuRM5PxA-(S3k@4P>ZS`o9qYQ=Wp`E|VAoz=>^xwzDLQw7WJA2!wC|wASswLPgBc4J`B4#Q8rh_U-nlsqv0@5+QliboTLfKx%z-%RN6W-g80r&9T*X z@h_{p%69ML+}mxdJKA0QiygOIx373k!AAd^9d93cBHOU#)_)XNn&NYP<&%<`%q6ya zb5*V1?#=nAIK|A`^593s?gzAr%ZtipXD%_cDVt;HWoPq>qpTB5MeLIc7>BZTlGzII ziw1y#7u5Up+RfSQ1$bpW8IF7G1o5Hy84DPiWI;Bk+aU>RW$Bqi^n<6vlbpRU@H;gl zd!q}3aY+_*7xo#GAjmFkZc)U8!?;Mu$hxRPCY#lZDr7vp>_rta4%4!Y7gxl@{=Ycz z>#~tP$zI%TkX9hOor@RFD^&BV*)p$CI=OFf!$SMUA*Ipw$Y%=mTaF!6nx5Zs-tdxX z8!T$OW%gmEvvOO0ab$^acJ7+yH{5pycGn-Py4iF8S?FL-Y_9518d-g=QyRINOxbpx zTspUZP2O$Sm~_&+rJ%d?pdbOx2oylSlzR2HYhS7w-r8A9kQUMsx{(snb0K5YBk6(& zt?yaUlBiM;*HbpC6qGi{ESA0?-4O@QID;cPH5=EvT>*#zooyF%qF_&v4zr`3(`!;U zK9g)a-n2TFamCPXpXq9Uadhd}uI(#Ei}(vVGDW@FO4B;p505S#)Y!3Nj5FM`9i1{4 zqe3>=ohjeQU~(Jm&UW0CQvb%z6`L9*knK!7r3@lk&tvUEGbEhVZLqu86;n!mCUohV z7?(`#Y!?Qrj{W*f_jK&nS9uekAx*sv)GPLg8~Y9R+bN|96B>%44hxa9p&cGL+KQ^_ znHk{upsTVytNhNJB?CUa;UhMqEI8TTcK$J?UX8uAy`;gXx3-tFDdg`Hv~{+*oK$7m z4IWb()z~NWh9PN`@53prw3mlXdA^D2Y%cfc``V#XOD+BShJHIk8s+>t|O_Ev+2Vul>QwQszm&e*LS9G>pH8WV$%rAS3Azc9v(7jP4tp;R5_)OQ-df zfisvTUW(J$VW>n=B{EEgp%JAxN@zr8loUU6=m=DybQ#{fF`03`%VZ}%21mOzsf{{f z3hvAXB#xM?jw>}cHsP|@5{T7=$KIpF2Xh!LY9n3NV)q|cnmD0_%Sf~YA=Rr^-+-qpTqT0Rk@)#iSn{wfXdlP zcI)w_9<7s#UQW^|KS|}Ra*ygrhGth+o|$rF&{!8W96$4dnP<(N*>Lt*KT5nml!e`q zI%GlcIa2j&m4p~%zo$bJB%oW}kP07r@(HD3jo;I~9|fd@Te_u@@`#Rxc)SyWX@0cb zdP3>22}c)w_fvxMM>E@{vpB_?nmNUH7N=I(sk$~4-P&**;|}k34kUh#rfH>-jmL$4 z)FB)*Mrrj^Du zPH&U&mzGbj@Rv?GiFS#ZSd^@j?0M5mlNwLby&P$hh;EXHU6e5??hI!Lf&lhU(@Wh? zK3Sbmq*4B4oKP~Z@#CD9X>jA3L$~Z{d`?TchcmVBXU_KBBaWKy6K*c-Yh;%gY{uD%IXA(i?sYMCQP~mE%vu4=)PAqlrIzxpcjq)=P zZaPwME_ZI`LOb4Ok89u*E_<%Jb4u1@`orr9Q2^goO+Xl6^H0!UaadkU|&0bag5|YZS;| zokk|4R~wv|z7mN=xZ0qA6tb{fZFt=%9b2chXA4dr9pXnT8Sn2rKy)4 zsytaS|9>S9Kz2kxkbUorQa>t{;DKM64&LVhgz#5pVA56z`Mg%7m2lutf(Jkc*$dAp z4Wv>D8@Sfsyz{k4*ub?$<1#}R7;v4?N+30`%xxXLv2sGfx)4H+ai*<)su&Y2n(vBPgNHU6!45M zgpBcVvk7EPLdLT z;wLx|7m1tT!}$;4ooS7tzV1wG#7qz`Ng^tl;xn;h&M5VU784h&6A|dLb%sYyx8XhU z6A|cqDGh!i0-^m)G7*8${>ETNdHF?u;Z^bTDH2D;Z|&@JN`q;sI0$~5OeP?NzfDvb zuZK|dE+Lf31O(X^Wft-u!n=|&2ZZo0_4QCvV&8w45~9j}X9t~I8rVt}guhD(MUnkZ z2*spfY~E`!vggm{WNh9m&NIni>}@ug-Y!GN-e$vF9OCXMmkV1><^kg!Ze_Bz8WwSB zcUczk-2I7C2bOcKK>t*M6u#eRU>*=b?ibgeusxt$c*tZPQMLzA@*$%EhzB^f4-=%~ zMk-KTENoY$P7q}uHV1eSfN*_So`i6%Ew>i7nau0Tww9vXjD8%o);VV$OQZ!7bYlYV zKn2mEj~R`T148;(;%x;|%VUOPYLTJVg~&Xfa-uBAEoQBf3#-RdRx;KfH?fzWTv$Dx zjDT9Vl0T8G)F-Cz6%C*1d`Hl&s`e$!-r^&OK#MnN|9EEB~Q< zDp|*Yl4o=suXQ`Tr&IYE>mcu0CKK_WPLw7PPEQ+s9{>oar^QL;dqM&wo=G`D3OaId zlFRsb#?({_!|EAR?=6v9Hy55U*jS#G+-S31Sc&(xR_4NXd&~KyE))0<-|cCS%GB7N zJ{*}E+tY_5@4P-Qd}QZVD{+U<+kc;5>Ixs3HP5GfWTrfCG=7m>_&jfVBqO2=+@H>pYTt_Z^b{KtOq}ILT`eL$519?Trt$UD!5zt z$aV!-&h56pxS-SdvW6x;UkAAKdP{_s)f338Jc*|34W#f}iK+u4=v$)dj_|RC@0!e=_Js>e zlgjUkFWtwCaiXxt1p95Ti%P><_n3lP!wC?Q^MXAl>?+ejAiVaNKEo5pL-?!7xyVS# z)ySm3nwrrXndFmT@>kQ4M4GW-E$lV9znlLIDcoyn{%$0saBvbp2-)9Ep_4`?Wqf4P zK2sQ6{VYm`3OW3F-vs|Ows>*r>DKoR;b&e>2`YSF)tr<=?DtJS7w#z#u|F_5cYmFO zJ`(Z+Q!`4%t^^f+U>ZhhK$Qd){=?)xu88e}3jbkhK2F8fpu!JLp%39&(hwh1nE%KW z9yQe`Ib? zf;+#VG2%vD5VUv4j{*^TK`?BxHc6b0R|mOC+S3|5NK(y2?P;F`ldFS@6A(V>LuEBga8Xd0B`-B04W~Lwf?&D*_obz#R3$pfb395=>SEmB9^F!3Mci70C;3aND3}RWKxxT+$$0 z889b(TO@_*e;pK#kbM%;v?&*E4!D(|%TG!pTV*M}IUOr)Aig;rE3z`*EMvvT&9cH5 zRtLGYh~VR9+i0li>Y!#V&)vt(`a~#i2?`DWk%mdICaA>C3b)a>2Dv*bEo$RhbZby^ chZ{ulC?pA)ZWC6%L6QRV+qaZ1FRhRNAB8}xjQ{`u diff --git a/api/server/v1/json/client/server_service/server_service_client.go b/api/server/v1/json/client/server_service/server_service_client.go index 4817c5653da..f67d198f937 100644 --- a/api/server/v1/json/client/server_service/server_service_client.go +++ b/api/server/v1/json/client/server_service/server_service_client.go @@ -93,6 +93,8 @@ type ClientService interface { Readiness(params *ReadinessParams, opts ...ClientOption) (*ReadinessOK, error) + UpdateStatus(params *UpdateStatusParams, opts ...ClientOption) (*UpdateStatusOK, error) + Version(params *VersionParams, opts ...ClientOption) (*VersionOK, error) SetTransport(transport runtime.ClientTransport) @@ -450,6 +452,50 @@ func (a *Client) Readiness(params *ReadinessParams, opts ...ClientOption) (*Read return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +UpdateStatus updates status + +Returns PMM Server initialization status. +*/ +func (a *Client) UpdateStatus(params *UpdateStatusParams, opts ...ClientOption) (*UpdateStatusOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewUpdateStatusParams() + } + op := &runtime.ClientOperation{ + ID: "UpdateStatus", + Method: "POST", + PathPattern: "/v1/server/updates:getStatus", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &UpdateStatusReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*UpdateStatusOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*UpdateStatusDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* Version versions diff --git a/api/server/v1/json/client/server_service/update_status_parameters.go b/api/server/v1/json/client/server_service/update_status_parameters.go new file mode 100644 index 00000000000..56b7eb403d5 --- /dev/null +++ b/api/server/v1/json/client/server_service/update_status_parameters.go @@ -0,0 +1,141 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package server_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewUpdateStatusParams creates a new UpdateStatusParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewUpdateStatusParams() *UpdateStatusParams { + return &UpdateStatusParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewUpdateStatusParamsWithTimeout creates a new UpdateStatusParams object +// with the ability to set a timeout on a request. +func NewUpdateStatusParamsWithTimeout(timeout time.Duration) *UpdateStatusParams { + return &UpdateStatusParams{ + timeout: timeout, + } +} + +// NewUpdateStatusParamsWithContext creates a new UpdateStatusParams object +// with the ability to set a context for a request. +func NewUpdateStatusParamsWithContext(ctx context.Context) *UpdateStatusParams { + return &UpdateStatusParams{ + Context: ctx, + } +} + +// NewUpdateStatusParamsWithHTTPClient creates a new UpdateStatusParams object +// with the ability to set a custom HTTPClient for a request. +func NewUpdateStatusParamsWithHTTPClient(client *http.Client) *UpdateStatusParams { + return &UpdateStatusParams{ + HTTPClient: client, + } +} + +/* +UpdateStatusParams contains all the parameters to send to the API endpoint + + for the update status operation. + + Typically these are written to a http.Request. +*/ +type UpdateStatusParams struct { + // Body. + Body UpdateStatusBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the update status params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *UpdateStatusParams) WithDefaults() *UpdateStatusParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the update status params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *UpdateStatusParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the update status params +func (o *UpdateStatusParams) WithTimeout(timeout time.Duration) *UpdateStatusParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the update status params +func (o *UpdateStatusParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the update status params +func (o *UpdateStatusParams) WithContext(ctx context.Context) *UpdateStatusParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the update status params +func (o *UpdateStatusParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the update status params +func (o *UpdateStatusParams) WithHTTPClient(client *http.Client) *UpdateStatusParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the update status params +func (o *UpdateStatusParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the update status params +func (o *UpdateStatusParams) WithBody(body UpdateStatusBody) *UpdateStatusParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the update status params +func (o *UpdateStatusParams) SetBody(body UpdateStatusBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *UpdateStatusParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/server/v1/json/client/server_service/update_status_responses.go b/api/server/v1/json/client/server_service/update_status_responses.go new file mode 100644 index 00000000000..7524dd78a6a --- /dev/null +++ b/api/server/v1/json/client/server_service/update_status_responses.go @@ -0,0 +1,661 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package server_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// UpdateStatusReader is a Reader for the UpdateStatus structure. +type UpdateStatusReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *UpdateStatusReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewUpdateStatusOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewUpdateStatusDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewUpdateStatusOK creates a UpdateStatusOK with default headers values +func NewUpdateStatusOK() *UpdateStatusOK { + return &UpdateStatusOK{} +} + +/* +UpdateStatusOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type UpdateStatusOK struct { + Payload *UpdateStatusOKBody +} + +// IsSuccess returns true when this update status Ok response has a 2xx status code +func (o *UpdateStatusOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this update status Ok response has a 3xx status code +func (o *UpdateStatusOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this update status Ok response has a 4xx status code +func (o *UpdateStatusOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this update status Ok response has a 5xx status code +func (o *UpdateStatusOK) IsServerError() bool { + return false +} + +// IsCode returns true when this update status Ok response a status code equal to that given +func (o *UpdateStatusOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the update status Ok response +func (o *UpdateStatusOK) Code() int { + return 200 +} + +func (o *UpdateStatusOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/server/updates:getStatus][%d] updateStatusOk %s", 200, payload) +} + +func (o *UpdateStatusOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/server/updates:getStatus][%d] updateStatusOk %s", 200, payload) +} + +func (o *UpdateStatusOK) GetPayload() *UpdateStatusOKBody { + return o.Payload +} + +func (o *UpdateStatusOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(UpdateStatusOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewUpdateStatusDefault creates a UpdateStatusDefault with default headers values +func NewUpdateStatusDefault(code int) *UpdateStatusDefault { + return &UpdateStatusDefault{ + _statusCode: code, + } +} + +/* +UpdateStatusDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type UpdateStatusDefault struct { + _statusCode int + + Payload *UpdateStatusDefaultBody +} + +// IsSuccess returns true when this update status default response has a 2xx status code +func (o *UpdateStatusDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this update status default response has a 3xx status code +func (o *UpdateStatusDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this update status default response has a 4xx status code +func (o *UpdateStatusDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this update status default response has a 5xx status code +func (o *UpdateStatusDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this update status default response a status code equal to that given +func (o *UpdateStatusDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the update status default response +func (o *UpdateStatusDefault) Code() int { + return o._statusCode +} + +func (o *UpdateStatusDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/server/updates:getStatus][%d] UpdateStatus default %s", o._statusCode, payload) +} + +func (o *UpdateStatusDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/server/updates:getStatus][%d] UpdateStatus default %s", o._statusCode, payload) +} + +func (o *UpdateStatusDefault) GetPayload() *UpdateStatusDefaultBody { + return o.Payload +} + +func (o *UpdateStatusDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(UpdateStatusDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +UpdateStatusBody update status body +swagger:model UpdateStatusBody +*/ +type UpdateStatusBody struct { + // Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified. + AuthToken string `json:"auth_token,omitempty"` + + // Progress log offset. + LogOffset int64 `json:"log_offset,omitempty"` +} + +// Validate validates this update status body +func (o *UpdateStatusBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update status body based on context it is used +func (o *UpdateStatusBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateStatusBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateStatusBody) UnmarshalBinary(b []byte) error { + var res UpdateStatusBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateStatusDefaultBody update status default body +swagger:model UpdateStatusDefaultBody +*/ +type UpdateStatusDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*UpdateStatusDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this update status default body +func (o *UpdateStatusDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateStatusDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("UpdateStatus default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("UpdateStatus default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this update status default body based on the context it is used +func (o *UpdateStatusDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateStatusDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("UpdateStatus default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("UpdateStatus default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateStatusDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateStatusDefaultBody) UnmarshalBinary(b []byte) error { + var res UpdateStatusDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateStatusDefaultBodyDetailsItems0 `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +swagger:model UpdateStatusDefaultBodyDetailsItems0 +*/ +type UpdateStatusDefaultBodyDetailsItems0 struct { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + AtType string `json:"@type,omitempty"` + + // update status default body details items0 + UpdateStatusDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *UpdateStatusDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv UpdateStatusDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.UpdateStatusDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o UpdateStatusDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.UpdateStatusDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.UpdateStatusDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this update status default body details items0 +func (o *UpdateStatusDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update status default body details items0 based on context it is used +func (o *UpdateStatusDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateStatusDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateStatusDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res UpdateStatusDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateStatusOKBody update status OK body +swagger:model UpdateStatusOKBody +*/ +type UpdateStatusOKBody struct { + // Progress log lines. + LogLines []string `json:"log_lines"` + + // Progress log offset for the next request. + LogOffset int64 `json:"log_offset,omitempty"` + + // True when update is done. + Done bool `json:"done,omitempty"` +} + +// Validate validates this update status OK body +func (o *UpdateStatusOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update status OK body based on context it is used +func (o *UpdateStatusOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateStatusOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateStatusOKBody) UnmarshalBinary(b []byte) error { + var res UpdateStatusOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/server/v1/json/v1.json b/api/server/v1/json/v1.json index 938b629839f..51d5fe3d7c3 100644 --- a/api/server/v1/json/v1.json +++ b/api/server/v1/json/v1.json @@ -1005,6 +1005,101 @@ } } }, + "/v1/server/updates:getStatus": { + "post": { + "description": "Returns PMM Server initialization status.", + "tags": [ + "ServerService" + ], + "summary": "Update status", + "operationId": "UpdateStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "auth_token": { + "description": "Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified.", + "type": "string", + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset.", + "type": "integer", + "format": "int64", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "log_lines": { + "description": "Progress log lines.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset for the next request.", + "type": "integer", + "format": "int64", + "x-order": 1 + }, + "done": { + "description": "True when update is done.", + "type": "boolean", + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }", + "type": "object", + "properties": { + "@type": { + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics.", + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, "/v1/server/version": { "get": { "description": "Returns PMM Server versions.", diff --git a/api/server/v1/server.pb.go b/api/server/v1/server.pb.go index ff6bf2cea36..67177e79230 100644 --- a/api/server/v1/server.pb.go +++ b/api/server/v1/server.pb.go @@ -19,6 +19,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" common "github.com/percona/pmm/api/common" + _ "github.com/percona/pmm/api/extensions/v1" ) const ( @@ -718,6 +719,123 @@ func (x *ListChangeLogsResponse) GetLastCheck() *timestamppb.Timestamp { return nil } +type UpdateStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified. + AuthToken string `protobuf:"bytes,1,opt,name=auth_token,json=authToken,proto3" json:"auth_token,omitempty"` + // Progress log offset. + LogOffset uint32 `protobuf:"varint,2,opt,name=log_offset,json=logOffset,proto3" json:"log_offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateStatusRequest) Reset() { + *x = UpdateStatusRequest{} + mi := &file_server_v1_server_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateStatusRequest) ProtoMessage() {} + +func (x *UpdateStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_server_v1_server_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateStatusRequest.ProtoReflect.Descriptor instead. +func (*UpdateStatusRequest) Descriptor() ([]byte, []int) { + return file_server_v1_server_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateStatusRequest) GetAuthToken() string { + if x != nil { + return x.AuthToken + } + return "" +} + +func (x *UpdateStatusRequest) GetLogOffset() uint32 { + if x != nil { + return x.LogOffset + } + return 0 +} + +type UpdateStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Progress log lines. + LogLines []string `protobuf:"bytes,1,rep,name=log_lines,json=logLines,proto3" json:"log_lines,omitempty"` + // Progress log offset for the next request. + LogOffset uint32 `protobuf:"varint,2,opt,name=log_offset,json=logOffset,proto3" json:"log_offset,omitempty"` + // True when update is done. + Done bool `protobuf:"varint,3,opt,name=done,proto3" json:"done,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateStatusResponse) Reset() { + *x = UpdateStatusResponse{} + mi := &file_server_v1_server_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateStatusResponse) ProtoMessage() {} + +func (x *UpdateStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_server_v1_server_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateStatusResponse.ProtoReflect.Descriptor instead. +func (*UpdateStatusResponse) Descriptor() ([]byte, []int) { + return file_server_v1_server_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateStatusResponse) GetLogLines() []string { + if x != nil { + return x.LogLines + } + return nil +} + +func (x *UpdateStatusResponse) GetLogOffset() uint32 { + if x != nil { + return x.LogOffset + } + return 0 +} + +func (x *UpdateStatusResponse) GetDone() bool { + if x != nil { + return x.Done + } + return false +} + // MetricsResolutions represents Prometheus exporters metrics resolutions. type MetricsResolutions struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -733,7 +851,7 @@ type MetricsResolutions struct { func (x *MetricsResolutions) Reset() { *x = MetricsResolutions{} - mi := &file_server_v1_server_proto_msgTypes[12] + mi := &file_server_v1_server_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -745,7 +863,7 @@ func (x *MetricsResolutions) String() string { func (*MetricsResolutions) ProtoMessage() {} func (x *MetricsResolutions) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[12] + mi := &file_server_v1_server_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -758,7 +876,7 @@ func (x *MetricsResolutions) ProtoReflect() protoreflect.Message { // Deprecated: Use MetricsResolutions.ProtoReflect.Descriptor instead. func (*MetricsResolutions) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{12} + return file_server_v1_server_proto_rawDescGZIP(), []int{14} } func (x *MetricsResolutions) GetHr() *durationpb.Duration { @@ -797,7 +915,7 @@ type AdvisorRunIntervals struct { func (x *AdvisorRunIntervals) Reset() { *x = AdvisorRunIntervals{} - mi := &file_server_v1_server_proto_msgTypes[13] + mi := &file_server_v1_server_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -809,7 +927,7 @@ func (x *AdvisorRunIntervals) String() string { func (*AdvisorRunIntervals) ProtoMessage() {} func (x *AdvisorRunIntervals) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[13] + mi := &file_server_v1_server_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -822,7 +940,7 @@ func (x *AdvisorRunIntervals) ProtoReflect() protoreflect.Message { // Deprecated: Use AdvisorRunIntervals.ProtoReflect.Descriptor instead. func (*AdvisorRunIntervals) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{13} + return file_server_v1_server_proto_rawDescGZIP(), []int{15} } func (x *AdvisorRunIntervals) GetStandardInterval() *durationpb.Duration { @@ -889,7 +1007,7 @@ type Settings struct { func (x *Settings) Reset() { *x = Settings{} - mi := &file_server_v1_server_proto_msgTypes[14] + mi := &file_server_v1_server_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -901,7 +1019,7 @@ func (x *Settings) String() string { func (*Settings) ProtoMessage() {} func (x *Settings) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[14] + mi := &file_server_v1_server_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -914,7 +1032,7 @@ func (x *Settings) ProtoReflect() protoreflect.Message { // Deprecated: Use Settings.ProtoReflect.Descriptor instead. func (*Settings) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{14} + return file_server_v1_server_proto_rawDescGZIP(), []int{16} } func (x *Settings) GetUpdatesEnabled() bool { @@ -1070,7 +1188,7 @@ type ReadOnlySettings struct { func (x *ReadOnlySettings) Reset() { *x = ReadOnlySettings{} - mi := &file_server_v1_server_proto_msgTypes[15] + mi := &file_server_v1_server_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1082,7 +1200,7 @@ func (x *ReadOnlySettings) String() string { func (*ReadOnlySettings) ProtoMessage() {} func (x *ReadOnlySettings) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[15] + mi := &file_server_v1_server_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1095,7 +1213,7 @@ func (x *ReadOnlySettings) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadOnlySettings.ProtoReflect.Descriptor instead. func (*ReadOnlySettings) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{15} + return file_server_v1_server_proto_rawDescGZIP(), []int{17} } func (x *ReadOnlySettings) GetUpdatesEnabled() bool { @@ -1162,7 +1280,7 @@ type GetSettingsRequest struct { func (x *GetSettingsRequest) Reset() { *x = GetSettingsRequest{} - mi := &file_server_v1_server_proto_msgTypes[16] + mi := &file_server_v1_server_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1174,7 +1292,7 @@ func (x *GetSettingsRequest) String() string { func (*GetSettingsRequest) ProtoMessage() {} func (x *GetSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[16] + mi := &file_server_v1_server_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1187,7 +1305,7 @@ func (x *GetSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSettingsRequest.ProtoReflect.Descriptor instead. func (*GetSettingsRequest) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{16} + return file_server_v1_server_proto_rawDescGZIP(), []int{18} } type GetReadOnlySettingsRequest struct { @@ -1198,7 +1316,7 @@ type GetReadOnlySettingsRequest struct { func (x *GetReadOnlySettingsRequest) Reset() { *x = GetReadOnlySettingsRequest{} - mi := &file_server_v1_server_proto_msgTypes[17] + mi := &file_server_v1_server_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1210,7 +1328,7 @@ func (x *GetReadOnlySettingsRequest) String() string { func (*GetReadOnlySettingsRequest) ProtoMessage() {} func (x *GetReadOnlySettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[17] + mi := &file_server_v1_server_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1223,7 +1341,7 @@ func (x *GetReadOnlySettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetReadOnlySettingsRequest.ProtoReflect.Descriptor instead. func (*GetReadOnlySettingsRequest) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{17} + return file_server_v1_server_proto_rawDescGZIP(), []int{19} } type GetSettingsResponse struct { @@ -1235,7 +1353,7 @@ type GetSettingsResponse struct { func (x *GetSettingsResponse) Reset() { *x = GetSettingsResponse{} - mi := &file_server_v1_server_proto_msgTypes[18] + mi := &file_server_v1_server_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1247,7 +1365,7 @@ func (x *GetSettingsResponse) String() string { func (*GetSettingsResponse) ProtoMessage() {} func (x *GetSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[18] + mi := &file_server_v1_server_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1260,7 +1378,7 @@ func (x *GetSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSettingsResponse.ProtoReflect.Descriptor instead. func (*GetSettingsResponse) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{18} + return file_server_v1_server_proto_rawDescGZIP(), []int{20} } func (x *GetSettingsResponse) GetSettings() *Settings { @@ -1279,7 +1397,7 @@ type GetReadOnlySettingsResponse struct { func (x *GetReadOnlySettingsResponse) Reset() { *x = GetReadOnlySettingsResponse{} - mi := &file_server_v1_server_proto_msgTypes[19] + mi := &file_server_v1_server_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1291,7 +1409,7 @@ func (x *GetReadOnlySettingsResponse) String() string { func (*GetReadOnlySettingsResponse) ProtoMessage() {} func (x *GetReadOnlySettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[19] + mi := &file_server_v1_server_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1304,7 +1422,7 @@ func (x *GetReadOnlySettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetReadOnlySettingsResponse.ProtoReflect.Descriptor instead. func (*GetReadOnlySettingsResponse) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{19} + return file_server_v1_server_proto_rawDescGZIP(), []int{21} } func (x *GetReadOnlySettingsResponse) GetSettings() *ReadOnlySettings { @@ -1345,7 +1463,7 @@ type ChangeSettingsRequest struct { func (x *ChangeSettingsRequest) Reset() { *x = ChangeSettingsRequest{} - mi := &file_server_v1_server_proto_msgTypes[20] + mi := &file_server_v1_server_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1357,7 +1475,7 @@ func (x *ChangeSettingsRequest) String() string { func (*ChangeSettingsRequest) ProtoMessage() {} func (x *ChangeSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[20] + mi := &file_server_v1_server_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1370,7 +1488,7 @@ func (x *ChangeSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeSettingsRequest.ProtoReflect.Descriptor instead. func (*ChangeSettingsRequest) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{20} + return file_server_v1_server_proto_rawDescGZIP(), []int{22} } func (x *ChangeSettingsRequest) GetEnableUpdates() bool { @@ -1480,7 +1598,7 @@ type ChangeSettingsResponse struct { func (x *ChangeSettingsResponse) Reset() { *x = ChangeSettingsResponse{} - mi := &file_server_v1_server_proto_msgTypes[21] + mi := &file_server_v1_server_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1492,7 +1610,7 @@ func (x *ChangeSettingsResponse) String() string { func (*ChangeSettingsResponse) ProtoMessage() {} func (x *ChangeSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_server_v1_server_proto_msgTypes[21] + mi := &file_server_v1_server_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1505,7 +1623,7 @@ func (x *ChangeSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeSettingsResponse.ProtoReflect.Descriptor instead. func (*ChangeSettingsResponse) Descriptor() ([]byte, []int) { - return file_server_v1_server_proto_rawDescGZIP(), []int{21} + return file_server_v1_server_proto_rawDescGZIP(), []int{23} } func (x *ChangeSettingsResponse) GetSettings() *Settings { @@ -1519,7 +1637,7 @@ var File_server_v1_server_proto protoreflect.FileDescriptor const file_server_v1_server_proto_rawDesc = "" + "\n" + - "\x16server/v1/server.proto\x12\tserver.v1\x1a\x13common/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\"\x84\x01\n" + + "\x16server/v1/server.proto\x12\tserver.v1\x1a\x13common/common.proto\x1a\x1aextensions/v1/redact.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\"\x84\x01\n" + "\vVersionInfo\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x12!\n" + "\ffull_version\x18\x02 \x01(\tR\vfullVersion\x128\n" + @@ -1555,7 +1673,17 @@ const file_server_v1_server_proto_rawDesc = "" + "\x16ListChangeLogsResponse\x126\n" + "\aupdates\x18\x01 \x03(\v2\x1c.server.v1.DockerVersionInfoR\aupdates\x129\n" + "\n" + - "last_check\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tlastCheck\"\x95\x01\n" + + "last_check\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tlastCheck\"Y\n" + + "\x13UpdateStatusRequest\x12#\n" + + "\n" + + "auth_token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\tauthToken\x12\x1d\n" + + "\n" + + "log_offset\x18\x02 \x01(\rR\tlogOffset\"f\n" + + "\x14UpdateStatusResponse\x12\x1b\n" + + "\tlog_lines\x18\x01 \x03(\tR\blogLines\x12\x1d\n" + + "\n" + + "log_offset\x18\x02 \x01(\rR\tlogOffset\x12\x12\n" + + "\x04done\x18\x03 \x01(\bR\x04done\"\x95\x01\n" + "\x12MetricsResolutions\x12)\n" + "\x02hr\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x02hr\x12)\n" + "\x02mr\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x02mr\x12)\n" + @@ -1636,13 +1764,14 @@ const file_server_v1_server_proto_rawDesc = "" + "\x17DISTRIBUTION_METHOD_OVF\x10\x02\x12\x1b\n" + "\x17DISTRIBUTION_METHOD_AMI\x10\x03\x12\x1d\n" + "\x19DISTRIBUTION_METHOD_AZURE\x10\x04\x12\x1a\n" + - "\x16DISTRIBUTION_METHOD_DO\x10\x052\x89\r\n" + + "\x16DISTRIBUTION_METHOD_DO\x10\x052\xc1\x0e\n" + "\rServerService\x12\x86\x01\n" + "\aVersion\x12\x19.server.v1.VersionRequest\x1a\x1a.server.v1.VersionResponse\"D\x92A'\x12\aVersion\x1a\x1cReturns PMM Server versions.\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/server/version\x12\xab\x02\n" + "\tReadiness\x12\x1b.server.v1.ReadinessRequest\x1a\x1c.server.v1.ReadinessResponse\"\xe2\x01\x92A\xc5\x01\x12\x16Check server readiness\x1a\xaa\x01Returns an error when Server components being restarted are not ready yet. Use this API for checking the health of Docker containers and for probing Kubernetes readiness.\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/server/readyz\x12\x81\x02\n" + "\x11LeaderHealthCheck\x12#.server.v1.LeaderHealthCheckRequest\x1a$.server.v1.LeaderHealthCheckResponse\"\xa0\x01\x92Ay\x12\x10Check Leadership\x1aeChecks if the instance is the leader in a cluster. Returns an error if the instance isn't the leader.\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/server/leaderHealthCheck\x12\xa7\x01\n" + "\fCheckUpdates\x12\x1e.server.v1.CheckUpdatesRequest\x1a\x1f.server.v1.CheckUpdatesResponse\"V\x92A9\x12\rCheck updates\x1a(Checks for available PMM Server updates.\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/server/updates\x12\xe9\x01\n" + - "\x0eListChangeLogs\x12 .server.v1.ListChangeLogsRequest\x1a!.server.v1.ListChangeLogsResponse\"\x91\x01\x92Ai\x12\x11Get the changelog\x1aTDisplay a changelog comparing the installed version to the latest available version.\x82\xd3\xe4\x93\x02\x1f\x12\x1d/v1/server/updates/changelogs\x12\xa0\x01\n" + + "\x0eListChangeLogs\x12 .server.v1.ListChangeLogsRequest\x1a!.server.v1.ListChangeLogsResponse\"\x91\x01\x92Ai\x12\x11Get the changelog\x1aTDisplay a changelog comparing the installed version to the latest available version.\x82\xd3\xe4\x93\x02\x1f\x12\x1d/v1/server/updates/changelogs\x12\xb5\x01\n" + + "\fUpdateStatus\x12\x1e.server.v1.UpdateStatusRequest\x1a\x1f.server.v1.UpdateStatusResponse\"d\x92A:\x12\rUpdate status\x1a)Returns PMM Server initialization status.\x82\xd3\xe4\x93\x02!:\x01*\"\x1c/v1/server/updates:getStatus\x12\xa0\x01\n" + "\vGetSettings\x12\x1d.server.v1.GetSettingsRequest\x1a\x1e.server.v1.GetSettingsResponse\"R\x92A4\x12\fGet settings\x1a$Returns current PMM Server settings.\x82\xd3\xe4\x93\x02\x15\x12\x13/v1/server/settings\x12\xd9\x01\n" + "\x13GetReadOnlySettings\x12%.server.v1.GetReadOnlySettingsRequest\x1a&.server.v1.GetReadOnlySettingsResponse\"s\x92AL\x12\x16Get read-only settings\x1a2Returns a stripped version of PMM Server settings.\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/server/settings/readonly\x12\xa7\x01\n" + "\x0eChangeSettings\x12 .server.v1.ChangeSettingsRequest\x1a!.server.v1.ChangeSettingsResponse\"P\x92A/\x12\x0fChange settings\x1a\x1cChanges PMM Server settings.\x82\xd3\xe4\x93\x02\x18:\x01*\x1a\x13/v1/server/settingsB\x90\x01\n" + @@ -1663,7 +1792,7 @@ func file_server_v1_server_proto_rawDescGZIP() []byte { var ( file_server_v1_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1) - file_server_v1_server_proto_msgTypes = make([]protoimpl.MessageInfo, 22) + file_server_v1_server_proto_msgTypes = make([]protoimpl.MessageInfo, 24) file_server_v1_server_proto_goTypes = []any{ DistributionMethod(0), // 0: server.v1.DistributionMethod (*VersionInfo)(nil), // 1: server.v1.VersionInfo @@ -1678,67 +1807,70 @@ var ( (*CheckUpdatesResponse)(nil), // 10: server.v1.CheckUpdatesResponse (*ListChangeLogsRequest)(nil), // 11: server.v1.ListChangeLogsRequest (*ListChangeLogsResponse)(nil), // 12: server.v1.ListChangeLogsResponse - (*MetricsResolutions)(nil), // 13: server.v1.MetricsResolutions - (*AdvisorRunIntervals)(nil), // 14: server.v1.AdvisorRunIntervals - (*Settings)(nil), // 15: server.v1.Settings - (*ReadOnlySettings)(nil), // 16: server.v1.ReadOnlySettings - (*GetSettingsRequest)(nil), // 17: server.v1.GetSettingsRequest - (*GetReadOnlySettingsRequest)(nil), // 18: server.v1.GetReadOnlySettingsRequest - (*GetSettingsResponse)(nil), // 19: server.v1.GetSettingsResponse - (*GetReadOnlySettingsResponse)(nil), // 20: server.v1.GetReadOnlySettingsResponse - (*ChangeSettingsRequest)(nil), // 21: server.v1.ChangeSettingsRequest - (*ChangeSettingsResponse)(nil), // 22: server.v1.ChangeSettingsResponse - (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 24: google.protobuf.Duration - (*common.StringArray)(nil), // 25: common.StringArray + (*UpdateStatusRequest)(nil), // 13: server.v1.UpdateStatusRequest + (*UpdateStatusResponse)(nil), // 14: server.v1.UpdateStatusResponse + (*MetricsResolutions)(nil), // 15: server.v1.MetricsResolutions + (*AdvisorRunIntervals)(nil), // 16: server.v1.AdvisorRunIntervals + (*Settings)(nil), // 17: server.v1.Settings + (*ReadOnlySettings)(nil), // 18: server.v1.ReadOnlySettings + (*GetSettingsRequest)(nil), // 19: server.v1.GetSettingsRequest + (*GetReadOnlySettingsRequest)(nil), // 20: server.v1.GetReadOnlySettingsRequest + (*GetSettingsResponse)(nil), // 21: server.v1.GetSettingsResponse + (*GetReadOnlySettingsResponse)(nil), // 22: server.v1.GetReadOnlySettingsResponse + (*ChangeSettingsRequest)(nil), // 23: server.v1.ChangeSettingsRequest + (*ChangeSettingsResponse)(nil), // 24: server.v1.ChangeSettingsResponse + (*timestamppb.Timestamp)(nil), // 25: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 26: google.protobuf.Duration + (*common.StringArray)(nil), // 27: common.StringArray } ) - var file_server_v1_server_proto_depIdxs = []int32{ - 23, // 0: server.v1.VersionInfo.timestamp:type_name -> google.protobuf.Timestamp + 25, // 0: server.v1.VersionInfo.timestamp:type_name -> google.protobuf.Timestamp 1, // 1: server.v1.VersionResponse.server:type_name -> server.v1.VersionInfo 1, // 2: server.v1.VersionResponse.managed:type_name -> server.v1.VersionInfo 0, // 3: server.v1.VersionResponse.distribution_method:type_name -> server.v1.DistributionMethod - 23, // 4: server.v1.DockerVersionInfo.timestamp:type_name -> google.protobuf.Timestamp + 25, // 4: server.v1.DockerVersionInfo.timestamp:type_name -> google.protobuf.Timestamp 1, // 5: server.v1.CheckUpdatesResponse.installed:type_name -> server.v1.VersionInfo 9, // 6: server.v1.CheckUpdatesResponse.latest:type_name -> server.v1.DockerVersionInfo - 23, // 7: server.v1.CheckUpdatesResponse.last_check:type_name -> google.protobuf.Timestamp + 25, // 7: server.v1.CheckUpdatesResponse.last_check:type_name -> google.protobuf.Timestamp 9, // 8: server.v1.ListChangeLogsResponse.updates:type_name -> server.v1.DockerVersionInfo - 23, // 9: server.v1.ListChangeLogsResponse.last_check:type_name -> google.protobuf.Timestamp - 24, // 10: server.v1.MetricsResolutions.hr:type_name -> google.protobuf.Duration - 24, // 11: server.v1.MetricsResolutions.mr:type_name -> google.protobuf.Duration - 24, // 12: server.v1.MetricsResolutions.lr:type_name -> google.protobuf.Duration - 24, // 13: server.v1.AdvisorRunIntervals.standard_interval:type_name -> google.protobuf.Duration - 24, // 14: server.v1.AdvisorRunIntervals.rare_interval:type_name -> google.protobuf.Duration - 24, // 15: server.v1.AdvisorRunIntervals.frequent_interval:type_name -> google.protobuf.Duration - 13, // 16: server.v1.Settings.metrics_resolutions:type_name -> server.v1.MetricsResolutions - 24, // 17: server.v1.Settings.data_retention:type_name -> google.protobuf.Duration - 14, // 18: server.v1.Settings.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals - 15, // 19: server.v1.GetSettingsResponse.settings:type_name -> server.v1.Settings - 16, // 20: server.v1.GetReadOnlySettingsResponse.settings:type_name -> server.v1.ReadOnlySettings - 13, // 21: server.v1.ChangeSettingsRequest.metrics_resolutions:type_name -> server.v1.MetricsResolutions - 24, // 22: server.v1.ChangeSettingsRequest.data_retention:type_name -> google.protobuf.Duration - 25, // 23: server.v1.ChangeSettingsRequest.aws_partitions:type_name -> common.StringArray - 14, // 24: server.v1.ChangeSettingsRequest.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals - 15, // 25: server.v1.ChangeSettingsResponse.settings:type_name -> server.v1.Settings + 25, // 9: server.v1.ListChangeLogsResponse.last_check:type_name -> google.protobuf.Timestamp + 26, // 10: server.v1.MetricsResolutions.hr:type_name -> google.protobuf.Duration + 26, // 11: server.v1.MetricsResolutions.mr:type_name -> google.protobuf.Duration + 26, // 12: server.v1.MetricsResolutions.lr:type_name -> google.protobuf.Duration + 26, // 13: server.v1.AdvisorRunIntervals.standard_interval:type_name -> google.protobuf.Duration + 26, // 14: server.v1.AdvisorRunIntervals.rare_interval:type_name -> google.protobuf.Duration + 26, // 15: server.v1.AdvisorRunIntervals.frequent_interval:type_name -> google.protobuf.Duration + 15, // 16: server.v1.Settings.metrics_resolutions:type_name -> server.v1.MetricsResolutions + 26, // 17: server.v1.Settings.data_retention:type_name -> google.protobuf.Duration + 16, // 18: server.v1.Settings.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals + 17, // 19: server.v1.GetSettingsResponse.settings:type_name -> server.v1.Settings + 18, // 20: server.v1.GetReadOnlySettingsResponse.settings:type_name -> server.v1.ReadOnlySettings + 15, // 21: server.v1.ChangeSettingsRequest.metrics_resolutions:type_name -> server.v1.MetricsResolutions + 26, // 22: server.v1.ChangeSettingsRequest.data_retention:type_name -> google.protobuf.Duration + 27, // 23: server.v1.ChangeSettingsRequest.aws_partitions:type_name -> common.StringArray + 16, // 24: server.v1.ChangeSettingsRequest.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals + 17, // 25: server.v1.ChangeSettingsResponse.settings:type_name -> server.v1.Settings 2, // 26: server.v1.ServerService.Version:input_type -> server.v1.VersionRequest 4, // 27: server.v1.ServerService.Readiness:input_type -> server.v1.ReadinessRequest 6, // 28: server.v1.ServerService.LeaderHealthCheck:input_type -> server.v1.LeaderHealthCheckRequest 8, // 29: server.v1.ServerService.CheckUpdates:input_type -> server.v1.CheckUpdatesRequest 11, // 30: server.v1.ServerService.ListChangeLogs:input_type -> server.v1.ListChangeLogsRequest - 17, // 31: server.v1.ServerService.GetSettings:input_type -> server.v1.GetSettingsRequest - 18, // 32: server.v1.ServerService.GetReadOnlySettings:input_type -> server.v1.GetReadOnlySettingsRequest - 21, // 33: server.v1.ServerService.ChangeSettings:input_type -> server.v1.ChangeSettingsRequest - 3, // 34: server.v1.ServerService.Version:output_type -> server.v1.VersionResponse - 5, // 35: server.v1.ServerService.Readiness:output_type -> server.v1.ReadinessResponse - 7, // 36: server.v1.ServerService.LeaderHealthCheck:output_type -> server.v1.LeaderHealthCheckResponse - 10, // 37: server.v1.ServerService.CheckUpdates:output_type -> server.v1.CheckUpdatesResponse - 12, // 38: server.v1.ServerService.ListChangeLogs:output_type -> server.v1.ListChangeLogsResponse - 19, // 39: server.v1.ServerService.GetSettings:output_type -> server.v1.GetSettingsResponse - 20, // 40: server.v1.ServerService.GetReadOnlySettings:output_type -> server.v1.GetReadOnlySettingsResponse - 22, // 41: server.v1.ServerService.ChangeSettings:output_type -> server.v1.ChangeSettingsResponse - 34, // [34:42] is the sub-list for method output_type - 26, // [26:34] is the sub-list for method input_type + 13, // 31: server.v1.ServerService.UpdateStatus:input_type -> server.v1.UpdateStatusRequest + 19, // 32: server.v1.ServerService.GetSettings:input_type -> server.v1.GetSettingsRequest + 20, // 33: server.v1.ServerService.GetReadOnlySettings:input_type -> server.v1.GetReadOnlySettingsRequest + 23, // 34: server.v1.ServerService.ChangeSettings:input_type -> server.v1.ChangeSettingsRequest + 3, // 35: server.v1.ServerService.Version:output_type -> server.v1.VersionResponse + 5, // 36: server.v1.ServerService.Readiness:output_type -> server.v1.ReadinessResponse + 7, // 37: server.v1.ServerService.LeaderHealthCheck:output_type -> server.v1.LeaderHealthCheckResponse + 10, // 38: server.v1.ServerService.CheckUpdates:output_type -> server.v1.CheckUpdatesResponse + 12, // 39: server.v1.ServerService.ListChangeLogs:output_type -> server.v1.ListChangeLogsResponse + 14, // 40: server.v1.ServerService.UpdateStatus:output_type -> server.v1.UpdateStatusResponse + 21, // 41: server.v1.ServerService.GetSettings:output_type -> server.v1.GetSettingsResponse + 22, // 42: server.v1.ServerService.GetReadOnlySettings:output_type -> server.v1.GetReadOnlySettingsResponse + 24, // 43: server.v1.ServerService.ChangeSettings:output_type -> server.v1.ChangeSettingsResponse + 35, // [35:44] is the sub-list for method output_type + 26, // [26:35] is the sub-list for method input_type 26, // [26:26] is the sub-list for extension type_name 26, // [26:26] is the sub-list for extension extendee 0, // [0:26] is the sub-list for field type_name @@ -1749,14 +1881,14 @@ func file_server_v1_server_proto_init() { if File_server_v1_server_proto != nil { return } - file_server_v1_server_proto_msgTypes[20].OneofWrappers = []any{} + file_server_v1_server_proto_msgTypes[22].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_server_v1_server_proto_rawDesc), len(file_server_v1_server_proto_rawDesc)), NumEnums: 1, - NumMessages: 22, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/api/server/v1/server.pb.gw.go b/api/server/v1/server.pb.gw.go index 464f987dd7d..01b1e98aa90 100644 --- a/api/server/v1/server.pb.gw.go +++ b/api/server/v1/server.pb.gw.go @@ -168,6 +168,33 @@ func local_request_ServerService_ListChangeLogs_0(ctx context.Context, marshaler return msg, metadata, err } +func request_ServerService_UpdateStatus_0(ctx context.Context, marshaler runtime.Marshaler, client ServerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateStatusRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.UpdateStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_ServerService_UpdateStatus_0(ctx context.Context, marshaler runtime.Marshaler, server ServerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateStatusRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.UpdateStatus(ctx, &protoReq) + return msg, metadata, err +} + func request_ServerService_GetSettings_0(ctx context.Context, marshaler runtime.Marshaler, client ServerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( protoReq GetSettingsRequest @@ -343,6 +370,26 @@ func RegisterServerServiceHandlerServer(ctx context.Context, mux *runtime.ServeM } forward_ServerService_ListChangeLogs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_ServerService_UpdateStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/server.v1.ServerService/UpdateStatus", runtime.WithHTTPPathPattern("/v1/server/updates:getStatus")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ServerService_UpdateStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ServerService_UpdateStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_ServerService_GetSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -528,6 +575,23 @@ func RegisterServerServiceHandlerClient(ctx context.Context, mux *runtime.ServeM } forward_ServerService_ListChangeLogs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_ServerService_UpdateStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/server.v1.ServerService/UpdateStatus", runtime.WithHTTPPathPattern("/v1/server/updates:getStatus")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ServerService_UpdateStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ServerService_UpdateStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_ServerService_GetSettings_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -588,6 +652,7 @@ var ( pattern_ServerService_LeaderHealthCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "server", "leaderHealthCheck"}, "")) pattern_ServerService_CheckUpdates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "server", "updates"}, "")) pattern_ServerService_ListChangeLogs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "server", "updates", "changelogs"}, "")) + pattern_ServerService_UpdateStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "server", "updates"}, "getStatus")) pattern_ServerService_GetSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "server", "settings"}, "")) pattern_ServerService_GetReadOnlySettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "server", "settings", "readonly"}, "")) pattern_ServerService_ChangeSettings_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "server", "settings"}, "")) @@ -599,6 +664,7 @@ var ( forward_ServerService_LeaderHealthCheck_0 = runtime.ForwardResponseMessage forward_ServerService_CheckUpdates_0 = runtime.ForwardResponseMessage forward_ServerService_ListChangeLogs_0 = runtime.ForwardResponseMessage + forward_ServerService_UpdateStatus_0 = runtime.ForwardResponseMessage forward_ServerService_GetSettings_0 = runtime.ForwardResponseMessage forward_ServerService_GetReadOnlySettings_0 = runtime.ForwardResponseMessage forward_ServerService_ChangeSettings_0 = runtime.ForwardResponseMessage diff --git a/api/server/v1/server.pb.validate.go b/api/server/v1/server.pb.validate.go index 7c9c52c587a..e6412a2bb8a 100644 --- a/api/server/v1/server.pb.validate.go +++ b/api/server/v1/server.pb.validate.go @@ -1554,6 +1554,220 @@ var _ interface { ErrorName() string } = ListChangeLogsResponseValidationError{} +// Validate checks the field values on UpdateStatusRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateStatusRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateStatusRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateStatusRequestMultiError, or nil if none found. +func (m *UpdateStatusRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateStatusRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for AuthToken + + // no validation rules for LogOffset + + if len(errors) > 0 { + return UpdateStatusRequestMultiError(errors) + } + + return nil +} + +// UpdateStatusRequestMultiError is an error wrapping multiple validation +// errors returned by UpdateStatusRequest.ValidateAll() if the designated +// constraints aren't met. +type UpdateStatusRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateStatusRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateStatusRequestMultiError) AllErrors() []error { return m } + +// UpdateStatusRequestValidationError is the validation error returned by +// UpdateStatusRequest.Validate if the designated constraints aren't met. +type UpdateStatusRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateStatusRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateStatusRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateStatusRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateStatusRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateStatusRequestValidationError) ErrorName() string { + return "UpdateStatusRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateStatusRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateStatusRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = UpdateStatusRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateStatusRequestValidationError{} + +// Validate checks the field values on UpdateStatusResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateStatusResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateStatusResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateStatusResponseMultiError, or nil if none found. +func (m *UpdateStatusResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateStatusResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for LogOffset + + // no validation rules for Done + + if len(errors) > 0 { + return UpdateStatusResponseMultiError(errors) + } + + return nil +} + +// UpdateStatusResponseMultiError is an error wrapping multiple validation +// errors returned by UpdateStatusResponse.ValidateAll() if the designated +// constraints aren't met. +type UpdateStatusResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateStatusResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateStatusResponseMultiError) AllErrors() []error { return m } + +// UpdateStatusResponseValidationError is the validation error returned by +// UpdateStatusResponse.Validate if the designated constraints aren't met. +type UpdateStatusResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateStatusResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateStatusResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateStatusResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateStatusResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateStatusResponseValidationError) ErrorName() string { + return "UpdateStatusResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateStatusResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateStatusResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = UpdateStatusResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateStatusResponseValidationError{} + // Validate checks the field values on MetricsResolutions with the rules // defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. diff --git a/api/server/v1/server.proto b/api/server/v1/server.proto index f23f82a3e8d..d52342e6844 100644 --- a/api/server/v1/server.proto +++ b/api/server/v1/server.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package server.v1; import "common/common.proto"; +import "extensions/v1/redact.proto"; import "google/api/annotations.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -100,6 +101,22 @@ message ListChangeLogsResponse { google.protobuf.Timestamp last_check = 2; } +message UpdateStatusRequest { + // Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified. + string auth_token = 1 [(extensions.v1.sensitive) = REDACT_TYPE_FULL]; + // Progress log offset. + uint32 log_offset = 2; +} + +message UpdateStatusResponse { + // Progress log lines. + repeated string log_lines = 1; + // Progress log offset for the next request. + uint32 log_offset = 2; + // True when update is done. + bool done = 3; +} + // MetricsResolutions represents Prometheus exporters metrics resolutions. message MetricsResolutions { // High resolution. Should have a suffix in JSON: 1s, 1m, 1h. @@ -266,6 +283,20 @@ service ServerService { description: "Display a changelog comparing the installed version to the latest available version." }; } + // UpdateStatus returns PMM Server initialization status. + // + // It exists for pre-3.9 clients, which poll it after triggering an update to learn when the + // freshly started PMM Server has finished initializing. + rpc UpdateStatus(UpdateStatusRequest) returns (UpdateStatusResponse) { + option (google.api.http) = { + post: "/v1/server/updates:getStatus" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Update status" + description: "Returns PMM Server initialization status." + }; + } // GetSettings returns current PMM Server settings. rpc GetSettings(GetSettingsRequest) returns (GetSettingsResponse) { option (google.api.http) = {get: "/v1/server/settings"}; diff --git a/api/server/v1/server_grpc.pb.go b/api/server/v1/server_grpc.pb.go index 3253c4d8b8f..9a1ea03984d 100644 --- a/api/server/v1/server_grpc.pb.go +++ b/api/server/v1/server_grpc.pb.go @@ -25,6 +25,7 @@ const ( ServerService_LeaderHealthCheck_FullMethodName = "/server.v1.ServerService/LeaderHealthCheck" ServerService_CheckUpdates_FullMethodName = "/server.v1.ServerService/CheckUpdates" ServerService_ListChangeLogs_FullMethodName = "/server.v1.ServerService/ListChangeLogs" + ServerService_UpdateStatus_FullMethodName = "/server.v1.ServerService/UpdateStatus" ServerService_GetSettings_FullMethodName = "/server.v1.ServerService/GetSettings" ServerService_GetReadOnlySettings_FullMethodName = "/server.v1.ServerService/GetReadOnlySettings" ServerService_ChangeSettings_FullMethodName = "/server.v1.ServerService/ChangeSettings" @@ -47,6 +48,11 @@ type ServerServiceClient interface { CheckUpdates(ctx context.Context, in *CheckUpdatesRequest, opts ...grpc.CallOption) (*CheckUpdatesResponse, error) // ListChangeLogs delivers the changelog. ListChangeLogs(ctx context.Context, in *ListChangeLogsRequest, opts ...grpc.CallOption) (*ListChangeLogsResponse, error) + // UpdateStatus returns PMM Server initialization status. + // + // It exists for pre-3.9 clients, which poll it after triggering an update to learn when the + // freshly started PMM Server has finished initializing. + UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*UpdateStatusResponse, error) // GetSettings returns current PMM Server settings. GetSettings(ctx context.Context, in *GetSettingsRequest, opts ...grpc.CallOption) (*GetSettingsResponse, error) // GetReadOnlySettings returns a limited number of PMM settings that is opened to authenticated users of all roles. @@ -113,6 +119,16 @@ func (c *serverServiceClient) ListChangeLogs(ctx context.Context, in *ListChange return out, nil } +func (c *serverServiceClient) UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*UpdateStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateStatusResponse) + err := c.cc.Invoke(ctx, ServerService_UpdateStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *serverServiceClient) GetSettings(ctx context.Context, in *GetSettingsRequest, opts ...grpc.CallOption) (*GetSettingsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSettingsResponse) @@ -160,6 +176,11 @@ type ServerServiceServer interface { CheckUpdates(context.Context, *CheckUpdatesRequest) (*CheckUpdatesResponse, error) // ListChangeLogs delivers the changelog. ListChangeLogs(context.Context, *ListChangeLogsRequest) (*ListChangeLogsResponse, error) + // UpdateStatus returns PMM Server initialization status. + // + // It exists for pre-3.9 clients, which poll it after triggering an update to learn when the + // freshly started PMM Server has finished initializing. + UpdateStatus(context.Context, *UpdateStatusRequest) (*UpdateStatusResponse, error) // GetSettings returns current PMM Server settings. GetSettings(context.Context, *GetSettingsRequest) (*GetSettingsResponse, error) // GetReadOnlySettings returns a limited number of PMM settings that is opened to authenticated users of all roles. @@ -196,6 +217,10 @@ func (UnimplementedServerServiceServer) ListChangeLogs(context.Context, *ListCha return nil, status.Error(codes.Unimplemented, "method ListChangeLogs not implemented") } +func (UnimplementedServerServiceServer) UpdateStatus(context.Context, *UpdateStatusRequest) (*UpdateStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateStatus not implemented") +} + func (UnimplementedServerServiceServer) GetSettings(context.Context, *GetSettingsRequest) (*GetSettingsResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetSettings not implemented") } @@ -318,6 +343,24 @@ func _ServerService_ListChangeLogs_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _ServerService_UpdateStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ServerServiceServer).UpdateStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ServerService_UpdateStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ServerServiceServer).UpdateStatus(ctx, req.(*UpdateStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ServerService_GetSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetSettingsRequest) if err := dec(in); err != nil { @@ -399,6 +442,10 @@ var ServerService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListChangeLogs", Handler: _ServerService_ListChangeLogs_Handler, }, + { + MethodName: "UpdateStatus", + Handler: _ServerService_UpdateStatus_Handler, + }, { MethodName: "GetSettings", Handler: _ServerService_GetSettings_Handler, diff --git a/api/swagger/swagger-dev.json b/api/swagger/swagger-dev.json index eec24ab6fda..5e5d661a09c 100644 --- a/api/swagger/swagger-dev.json +++ b/api/swagger/swagger-dev.json @@ -33121,6 +33121,101 @@ } } }, + "/v1/server/updates:getStatus": { + "post": { + "description": "Returns PMM Server initialization status.", + "tags": [ + "ServerService" + ], + "summary": "Update status", + "operationId": "UpdateStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "auth_token": { + "description": "Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified.", + "type": "string", + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset.", + "type": "integer", + "format": "int64", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "log_lines": { + "description": "Progress log lines.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset for the next request.", + "type": "integer", + "format": "int64", + "x-order": 1 + }, + "done": { + "description": "True when update is done.", + "type": "boolean", + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }", + "type": "object", + "properties": { + "@type": { + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics.", + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, "/v1/server/version": { "get": { "description": "Returns PMM Server versions.", diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 3bfd7698839..1de4e786785 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -32148,6 +32148,101 @@ } } }, + "/v1/server/updates:getStatus": { + "post": { + "description": "Returns PMM Server initialization status.", + "tags": [ + "ServerService" + ], + "summary": "Update status", + "operationId": "UpdateStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "auth_token": { + "description": "Authentication token. Kept for compatibility with pre-3.9 clients; it is no longer verified.", + "type": "string", + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset.", + "type": "integer", + "format": "int64", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "log_lines": { + "description": "Progress log lines.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "log_offset": { + "description": "Progress log offset for the next request.", + "type": "integer", + "format": "int64", + "x-order": 1 + }, + "done": { + "description": "True when update is done.", + "type": "boolean", + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }", + "type": "object", + "properties": { + "@type": { + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics.", + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, "/v1/server/version": { "get": { "description": "Returns PMM Server versions.", diff --git a/build/ansible/roles/initialization/tasks/main.yml b/build/ansible/roles/initialization/tasks/main.yml index 1d3c67ab939..9ef53c20e37 100644 --- a/build/ansible/roles/initialization/tasks/main.yml +++ b/build/ansible/roles/initialization/tasks/main.yml @@ -127,7 +127,8 @@ state: absent path: /usr/share/pmm-server/maintenance/maintenance.html -- name: Remove the file provisioned by 'getStatus' +# Written by 'updates:start' up to PMM 3.8; nothing uses it now, so clean it off upgraded volumes. +- name: Remove the update auth token file left by older PMM versions file: state: absent path: /srv/pmm-update.json diff --git a/build/ansible/roles/nginx/files/conf.d/pmm.conf b/build/ansible/roles/nginx/files/conf.d/pmm.conf index 6ae7efbe7bc..534912f346e 100644 --- a/build/ansible/roles/nginx/files/conf.d/pmm.conf +++ b/build/ansible/roles/nginx/files/conf.d/pmm.conf @@ -60,14 +60,14 @@ ssl_dhparam /srv/nginx/dhparam.pem; # this block checks for maintenance.html file and, if it exists, it redirects all requests to the maintenance page - # there are two exceptions for it /v1/updates/Status and /auth_request endpoints + # there are two exceptions for it /v1/server/updates:getStatus and /auth_request endpoints set $maintenance_mode 0; if (-f /usr/share/pmm-server/maintenance/maintenance.html) { set $maintenance_mode 1; } - if ($request_uri ~* "^/v1/updates/Status|^/auth_request") { + if ($request_uri ~* "^/v1/server/updates:getStatus|^/auth_request") { set $maintenance_mode 0; } diff --git a/managed/cmd/pmm-managed/main.go b/managed/cmd/pmm-managed/main.go index 2468e9778a0..e8c8a1a6e54 100644 --- a/managed/cmd/pmm-managed/main.go +++ b/managed/cmd/pmm-managed/main.go @@ -947,7 +947,7 @@ func main() { //nolint:gocognit,maintidx,cyclop connectionCheck := agents.NewConnectionChecker(agentsRegistry) serviceInfoBroker := agents.NewServiceInfoBroker(agentsRegistry) - updater := server.NewUpdater(db) + updater := server.NewUpdater(gRPCMessageMaxSize, db) logs := server.NewLogs(version.FullInfo(), updater, vmParams) diff --git a/managed/services/grafana/auth_server.go b/managed/services/grafana/auth_server.go index 7bb9927c306..21fd254d67c 100644 --- a/managed/services/grafana/auth_server.go +++ b/managed/services/grafana/auth_server.go @@ -60,6 +60,9 @@ var rules = map[string]role{ "/actions.": viewer, "/advisors.v1.": editor, "/server.v1.ServerService/CheckUpdates": viewer, + // Polled by pre-3.9 clients right after an update, when Grafana and PostgreSQL may still be + // migrating and cannot authenticate anyone. + "/server.v1.ServerService/UpdateStatus": none, "/server.v1.ServerService/AWSInstanceCheck": none, // special case - used before Grafana can be accessed "/server.": admin, // TODO: do we need it for older agents? "/server.v1.": admin, @@ -83,6 +86,7 @@ var rules = map[string]role{ "/v1/management/Jobs": viewer, "/v1/server/AWSInstance": none, // special case - used before Grafana can be accessed "/v1/server/updates": viewer, + "/v1/server/updates:getStatus": none, // see /server.v1.ServerService/UpdateStatus above "/v1/server/settings": admin, "/v1/server/settings/readonly": viewer, "/v1/platform:": admin, diff --git a/managed/services/server/deps.go b/managed/services/server/deps.go index 5d1c510130b..5265ed8b3aa 100644 --- a/managed/services/server/deps.go +++ b/managed/services/server/deps.go @@ -77,6 +77,7 @@ type vmAlertExternalRules interface { // We use it instead of real type for testing and to avoid dependency cycle. type supervisordService interface { UpdateConfiguration(settings *models.Settings) error + ProgramRunning(program string) bool } // telemetryService is a subset of methods of telemetry.Service used by this package. diff --git a/managed/services/server/mock_supervisord_service_test.go b/managed/services/server/mock_supervisord_service_test.go index ec30c3eb54c..ce8c5bb0935 100644 --- a/managed/services/server/mock_supervisord_service_test.go +++ b/managed/services/server/mock_supervisord_service_test.go @@ -13,6 +13,24 @@ type mockSupervisordService struct { mock.Mock } +// ProgramRunning provides a mock function with given fields: program +func (_m *mockSupervisordService) ProgramRunning(program string) bool { + ret := _m.Called(program) + + if len(ret) == 0 { + panic("no return value specified for ProgramRunning") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(program) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // UpdateConfiguration provides a mock function with given fields: settings func (_m *mockSupervisordService) UpdateConfiguration(settings *models.Settings) error { ret := _m.Called(settings) diff --git a/managed/services/server/server.go b/managed/services/server/server.go index 199ee2bfed9..a15820c45b9 100644 --- a/managed/services/server/server.go +++ b/managed/services/server/server.go @@ -45,6 +45,9 @@ import ( "github.com/percona/pmm/version" ) +// pmmInitProgram is the supervisord program running PMM Server initialization and upgrade tasks. +const pmmInitProgram = "pmm-init" + // Server represents service for checking PMM Server status and changing settings. type Server struct { serverv1.UnimplementedServerServiceServer @@ -332,6 +335,53 @@ func (s *Server) ListChangeLogs(ctx context.Context, _ *serverv1.ListChangeLogsR return res, nil } +// UpdateStatus returns PMM Server initialization status. +// +// It exists for pre-3.9 clients: after triggering an update they keep polling it to learn when the +// freshly started PMM Server has finished initializing. The request's authentication token is not +// verified - it was issued by the PMM Server instance this one has replaced, so it is unverifiable +// here by design. +func (s *Server) UpdateStatus(ctx context.Context, req *serverv1.UpdateStatusRequest) (*serverv1.UpdateStatusResponse, error) { + if !s.supervisord.ProgramRunning(pmmInitProgram) { + // Deliver whatever is left in the log along with the final response. + lines, newOffset, err := s.updater.InitLog(req.GetLogOffset()) + if err != nil { + s.l.Warn(err) + } + + return &serverv1.UpdateStatusResponse{ + LogLines: lines, + LogOffset: newOffset, + Done: true, + }, nil + } + + // wait up to 30 seconds for new log lines + var lines []string + var newOffset uint32 + var err error + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) //nolint:mnd + defer cancel() + for ctx.Err() == nil { + lines, newOffset, err = s.updater.InitLog(req.GetLogOffset()) + if err != nil { + s.l.Warn(err) + } + + if len(lines) != 0 { + break + } + + time.Sleep(200 * time.Millisecond) //nolint:mnd + } + + return &serverv1.UpdateStatusResponse{ + LogLines: lines, + LogOffset: newOffset, + Done: false, + }, nil +} + // convertSettings merges database settings and settings from environment variables into API response. func (s *Server) convertSettings(settings *models.Settings, disableInternalPgQan bool) *serverv1.Settings { res := &serverv1.Settings{ diff --git a/managed/services/server/server_test.go b/managed/services/server/server_test.go index 7a3ed57a2ff..afb46439ee3 100644 --- a/managed/services/server/server_test.go +++ b/managed/services/server/server_test.go @@ -19,9 +19,12 @@ import ( "context" "errors" "math" + "os" + "path/filepath" "testing" "time" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -322,3 +325,61 @@ func TestConvertDefaultRoleID(t *testing.T) { }) } } + +func TestUpdateStatus(t *testing.T) { + newServer := func(t *testing.T, initRunning bool, log string) *Server { + t.Helper() + + old := pmmInitLog + pmmInitLog = filepath.Join(t.TempDir(), "pmm-init.log") + t.Cleanup(func() { pmmInitLog = old }) + require.NoError(t, os.WriteFile(pmmInitLog, []byte(log), 0o600)) + + var sv mockSupervisordService + sv.Test(t) + sv.On("ProgramRunning", pmmInitProgram).Return(initRunning) + + return &Server{ + supervisord: &sv, + updater: NewUpdater(1024, nil), + l: logrus.WithField("component", "server-test"), + } + } + + t.Run("done once pmm-init is no longer running", func(t *testing.T) { + s := newServer(t, false, "finishing up\n") + + res, err := s.UpdateStatus(t.Context(), &serverv1.UpdateStatusRequest{}) + require.NoError(t, err) + assert.True(t, res.Done) + assert.Equal(t, []string{"finishing up"}, res.LogLines, "the tail of the log must be delivered with the final response") + assert.Equal(t, uint32(13), res.LogOffset) + }) + + t.Run("not done while pmm-init is running", func(t *testing.T) { + s := newServer(t, true, "still working\n") + + res, err := s.UpdateStatus(t.Context(), &serverv1.UpdateStatusRequest{}) + require.NoError(t, err) + assert.False(t, res.Done) + assert.Equal(t, []string{"still working"}, res.LogLines) + }) + + t.Run("done even without a log to read", func(t *testing.T) { + s := newServer(t, false, "") + require.NoError(t, os.Remove(pmmInitLog)) + + res, err := s.UpdateStatus(t.Context(), &serverv1.UpdateStatusRequest{}) + require.NoError(t, err) + assert.True(t, res.Done) + assert.Empty(t, res.LogLines) + }) + + t.Run("an unverifiable auth token is accepted", func(t *testing.T) { + s := newServer(t, false, "") + + res, err := s.UpdateStatus(t.Context(), &serverv1.UpdateStatusRequest{AuthToken: "issued-by-the-previous-instance"}) + require.NoError(t, err) + assert.True(t, res.Done) + }) +} diff --git a/managed/services/server/updater.go b/managed/services/server/updater.go index 7db0301300d..b4738a270fb 100644 --- a/managed/services/server/updater.go +++ b/managed/services/server/updater.go @@ -16,10 +16,12 @@ package server import ( + "bufio" "context" "encoding/json" "errors" "fmt" + "io" "net/http" "os" "sort" @@ -43,12 +45,16 @@ const ( updateCheckResultFresh = updateCheckInterval + 10*time.Minute ) -var fileName = "/etc/pmm-server-update-version.json" +var ( + fileName = "/etc/pmm-server-update-version.json" + pmmInitLog = "/srv/logs/pmm-init.log" +) // Updater is a service to check for available PMM Server updates. type Updater struct { - l *logrus.Entry - db *reform.DB + l *logrus.Entry + db *reform.DB + gRPCMessageMaxSize uint32 checkRW sync.RWMutex lastCheckResult *version.DockerVersionInfo @@ -57,14 +63,17 @@ type Updater struct { // releaseNotes holds a map of PMM server versions to their release notes. releaseNotes map[string]string releaseNotesRW sync.RWMutex + + initLogM sync.Mutex } // NewUpdater creates a new Updater service. -func NewUpdater(db *reform.DB) *Updater { +func NewUpdater(gRPCMessageMaxSize uint32, db *reform.DB) *Updater { u := &Updater{ - l: logrus.WithField("service", "updater"), - db: db, - releaseNotes: make(map[string]string), + l: logrus.WithField("service", "updater"), + db: db, + gRPCMessageMaxSize: gRPCMessageMaxSize, + releaseNotes: make(map[string]string), } return u } @@ -319,6 +328,44 @@ func (up *Updater) InstalledPMMVersion() version.PackageInfo { } } +// InitLog returns some lines and a new offset from the pmm-init log starting from the given offset. +// It may return zero lines and the same offset. Caller is expected to handle this. +func (up *Updater) InitLog(offset uint32) ([]string, uint32, error) { + up.initLogM.Lock() + defer up.initLogM.Unlock() + + f, err := os.Open(pmmInitLog) + if err != nil { + return nil, 0, fmt.Errorf("failed to open log file %s: %w", pmmInitLog, err) + } + defer f.Close() //nolint:errcheck,gosec,nolintlint + + _, err = f.Seek(int64(offset), io.SeekStart) + if err != nil { + return nil, 0, fmt.Errorf("failed to seek to %d in log file %s: %w", offset, pmmInitLog, err) + } + + lines := make([]string, 0, 10) //nolint:mnd + reader := bufio.NewReader(f) + newOffset := offset + var line string + for { + line, err = reader.ReadString('\n') + if err != nil { + if errors.Is(err, io.EOF) { + return lines, newOffset, nil + } + // return already read lines even with error + return lines, newOffset, fmt.Errorf("failed to read line from log file %s: %w", pmmInitLog, err) + } + newOffset += uint32(len(line)) //nolint:gosec + if newOffset-offset > up.gRPCMessageMaxSize { + return lines, newOffset - uint32(len(line)), nil //nolint:gosec + } + lines = append(lines, strings.TrimSuffix(line, "\n")) + } +} + // checkResult returns the result of the last update check. // It may force re-check if last result is empty or too old. func (up *Updater) checkResult(ctx context.Context) (*version.DockerVersionInfo, time.Time) { diff --git a/managed/services/server/updater_test.go b/managed/services/server/updater_test.go index a36898a730c..348f5df815f 100644 --- a/managed/services/server/updater_test.go +++ b/managed/services/server/updater_test.go @@ -42,6 +42,7 @@ import ( ) func TestUpdater(t *testing.T) { + gRPCMessageMaxSize := uint32(100 * 1024 * 1024) const tmpDistributionFile = "/tmp/distribution" sqlDB := testdb.Open(t, models.SkipFixtures, nil) @@ -246,7 +247,7 @@ func TestUpdater(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - u := NewUpdater(db) + u := NewUpdater(gRPCMessageMaxSize, db) parsed, err := version.Parse(tt.args.currentVersion) require.NoError(t, err) _, next := u.next(t.Context(), *parsed, tt.args.results) @@ -278,7 +279,7 @@ func TestUpdater(t *testing.T) { t.Run("TestLatest", func(t *testing.T) { version.Version = "2.41.0" - u := NewUpdater(db) + u := NewUpdater(gRPCMessageMaxSize, db) t.Run("LatestFromProduction", func(t *testing.T) { _, latest, err := u.latest(t.Context()) @@ -311,7 +312,7 @@ func TestUpdater(t *testing.T) { err := os.WriteFile(fileName, []byte(fileBody), 0o600) require.NoError(t, err) - u := NewUpdater(db) + u := NewUpdater(gRPCMessageMaxSize, db) _, latest, err := u.latest(t.Context()) require.NoError(t, err) assert.Equal(t, "2.41.1", latest.Version.String()) @@ -328,7 +329,7 @@ func TestUpdater(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() - u := NewUpdater(db) + u := NewUpdater(gRPCMessageMaxSize, db) err = u.check(ctx) require.Error(t, err) tests.AssertGRPCError(t, grpcstatus.New(codes.FailedPrecondition, "PMM updates are disabled"), err) @@ -368,9 +369,63 @@ func TestGetReleaseNotesClosesResponseBody(t *testing.T) { http.DefaultClient.Transport = stubRoundTripper{statusCode: http.StatusNotFound, body: body} t.Cleanup(func() { http.DefaultClient.Transport = origTransport }) - u := NewUpdater(nil) + u := NewUpdater(0, nil) text, err := u.getReleaseNotesText(t.Context(), *version.MustParse("3.0.0")) require.NoError(t, err) assert.Empty(t, text) assert.True(t, body.closed, "response body must be closed on non-200 status") } + +func TestInitLog(t *testing.T) { + setLog := func(t *testing.T, content string) { + t.Helper() + old := pmmInitLog + pmmInitLog = filepath.Join(t.TempDir(), "pmm-init.log") + t.Cleanup(func() { pmmInitLog = old }) + require.NoError(t, os.WriteFile(pmmInitLog, []byte(content), 0o600)) + } + + t.Run("reads from the given offset", func(t *testing.T) { + setLog(t, "first\nsecond\n") + u := NewUpdater(1024, nil) + + lines, offset, err := u.InitLog(0) + require.NoError(t, err) + assert.Equal(t, []string{"first", "second"}, lines) + assert.Equal(t, uint32(13), offset) + + lines, offset, err = u.InitLog(offset) + require.NoError(t, err) + assert.Empty(t, lines) + assert.Equal(t, uint32(13), offset) + }) + + t.Run("skips an incomplete trailing line", func(t *testing.T) { + setLog(t, "done\nin progres") + u := NewUpdater(1024, nil) + + lines, offset, err := u.InitLog(0) + require.NoError(t, err) + assert.Equal(t, []string{"done"}, lines) + assert.Equal(t, uint32(5), offset) + }) + + t.Run("stops before exceeding the max gRPC message size", func(t *testing.T) { + setLog(t, "aaaa\nbbbb\ncccc\n") + u := NewUpdater(6, nil) + + lines, offset, err := u.InitLog(0) + require.NoError(t, err) + assert.Equal(t, []string{"aaaa"}, lines) + assert.Equal(t, uint32(5), offset) + }) + + t.Run("returns an error when the log is missing", func(t *testing.T) { + old := pmmInitLog + pmmInitLog = filepath.Join(t.TempDir(), "absent.log") + t.Cleanup(func() { pmmInitLog = old }) + + _, _, err := NewUpdater(1024, nil).InitLog(0) + require.Error(t, err) + }) +} diff --git a/managed/services/supervisord/devcontainer_test.go b/managed/services/supervisord/devcontainer_test.go index 40e91e8e0da..27d63f771ae 100644 --- a/managed/services/supervisord/devcontainer_test.go +++ b/managed/services/supervisord/devcontainer_test.go @@ -57,7 +57,7 @@ func TestDevContainer(t *testing.T) { require.NoError(t, err) } // force update supervisor config - err = s.supervisorctl("update") + _, err = s.supervisorctl("update") require.NoError(t, err) }() @@ -78,3 +78,16 @@ func TestDevContainer(t *testing.T) { require.NoError(t, err) }) } + +func TestProgramRunning(t *testing.T) { + vmParams, err := models.NewVictoriaMetricsParams(models.BasePrometheusConfigPath, models.VMBaseURL) + require.NoError(t, err) + + s := New("/etc/supervisord.d", &models.Params{VMParams: vmParams, PGParams: &models.PGParams{}, HAParams: &models.HAParams{}}) + if s.supervisorctlPath == "" { + t.Skip("supervisorctl not found") + } + + assert.True(t, s.ProgramRunning("nginx")) + assert.False(t, s.ProgramRunning("no-such-program")) +} diff --git a/managed/services/supervisord/supervisord.go b/managed/services/supervisord/supervisord.go index 48833ed0e97..191db7bde33 100644 --- a/managed/services/supervisord/supervisord.go +++ b/managed/services/supervisord/supervisord.go @@ -234,12 +234,51 @@ func (s *Service) UpdateConfiguration(settings *models.Settings) error { // StartSupervisedService starts given service. func (s *Service) StartSupervisedService(serviceName string) error { - return s.supervisorctl("start", serviceName) + _, err := s.supervisorctl("start", serviceName) + return err } // StopSupervisedService stops given service. func (s *Service) StopSupervisedService(serviceName string) error { - return s.supervisorctl("stop", serviceName) + _, err := s.supervisorctl("stop", serviceName) + return err +} + +// ProgramRunning returns true if the given supervisord program is running or is going to be +// restarted, false if it is not running, has exited as expected, or has failed for good. +func (s *Service) ProgramRunning(program string) bool { + // First check with the status command in case we missed that event during maintail + // or a pmm-managed restart. See http://supervisord.org/subprocess.html#process-states + b, err := s.supervisorctl("status", program) + if err != nil { + // supervisorctl exits with a non-zero code when the program is not running, + // so the output is still worth parsing. + s.l.Debugf("Status command for '%s' failed: %s", program, err) + } + if status := parseStatus(string(b)); status != nil { + return *status + } + + s.eventsM.Lock() + lastEvent := s.lastEvents[program] + s.eventsM.Unlock() + + s.l.Debugf("Status result for '%s' not parsed, inspecting last event '%s'.", program, lastEvent) + switch lastEvent { + case stopping, starting, running: + return true + case exitedUnexpected: // will be restarted + return true + case exitedExpected, fatal: // will not be restarted + return false + case stopped: // we don't know + fallthrough + default: + // A run-once program that exited before this pmm-managed started reports EXITED with no + // event recorded, so this is an expected state rather than something worth warning about. + s.l.Debugf("Unhandled status result for '%s' (last event '%s'), assuming it is not running.", program, lastEvent) + return false + } } var templates = template.Must(template.New("").Option("missingkey=error").Parse(` @@ -408,20 +447,20 @@ redirect_stderr = true {{end}} `)) -func (s *Service) supervisorctl(args ...string) error { +func (s *Service) supervisorctl(args ...string) ([]byte, error) { if s.supervisorctlPath == "" { - return errors.New("supervisorctl not found") + return nil, errors.New("supervisorctl not found") } cmd := exec.Command(s.supervisorctlPath, args...) //nolint:gosec,noctx cmdLine := strings.Join(cmd.Args, " ") s.l.Debugf("Running %q...", cmdLine) pdeathsig.Set(cmd, unix.SIGKILL) - _, err := cmd.Output() + b, err := cmd.Output() if err != nil { - return fmt.Errorf("%s failed: %w", cmdLine, err) + return b, fmt.Errorf("%s failed: %w", cmdLine, err) } - return nil + return b, nil } // parseStatus parses `supervisorctl status ` output, returns true if is running, @@ -444,7 +483,7 @@ func parseStatus(status string) *bool { // reload asks supervisord to reload configuration. func (s *Service) reload(name string) error { - err := s.supervisorctl("reread") + _, err := s.supervisorctl("reread") if err != nil { s.l.Warn(err) } @@ -456,7 +495,8 @@ func (s *Service) reload(name string) error { return nil } - return s.supervisorctl("update", name) + _, err = s.supervisorctl("update", name) + return err } // marshalConfig marshals supervisord program configuration. From b65705b9393e78e32c2ca95420ac31ecd2767ec1 Mon Sep 17 00:00:00 2001 From: Alex Demidoff Date: Wed, 5 Aug 2026 13:53:29 +0300 Subject: [PATCH 2/4] PMM-15266 Format the code --- api/server/v1/server.pb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/server/v1/server.pb.go b/api/server/v1/server.pb.go index c282413037f..302ddaa32c3 100644 --- a/api/server/v1/server.pb.go +++ b/api/server/v1/server.pb.go @@ -1824,6 +1824,7 @@ var ( (*common.StringArray)(nil), // 27: common.StringArray } ) + var file_server_v1_server_proto_depIdxs = []int32{ 25, // 0: server.v1.VersionInfo.timestamp:type_name -> google.protobuf.Timestamp 1, // 1: server.v1.VersionResponse.server:type_name -> server.v1.VersionInfo From 6cd786e75a2b0258d28bc9d5a5970b0a951887a8 Mon Sep 17 00:00:00 2001 From: Alex Demidoff Date: Wed, 5 Aug 2026 18:05:08 +0300 Subject: [PATCH 3/4] PMM-15266 Narrow update status to the done field Only "done" matters to pre-3.9 clients, so stop serving the pmm-init progress log: log_lines and log_offset always marshal as their default values, and auth_token is accepted without being verified. All three fields are marked deprecated in the proto. Dropping the log removes Updater.InitLog along with the gRPCMessageMaxSize it needed to cap a single response, which restores updater.go and main.go to their pre-restoration state. The offset a client carries over refers to the log of the instance it polled before the update, so replaying from it could only return the previous boot's output. Also drop two documentation rows left stale by PMM-14969: the removed /v1/server/updates:start endpoint and the "Start updates" permission, which no longer has a UI trigger. Signed-off-by: Alex Demidoff --- api-tests/server/updates_test.go | 28 +++++--- api/descriptor.bin | Bin 821684 -> 821999 bytes .../server_service/update_status_responses.go | 10 +-- api/server/v1/json/v1.json | 10 +-- api/server/v1/server.pb.go | 36 ++++++---- api/server/v1/server.proto | 23 ++++--- api/server/v1/server_grpc.pb.go | 4 +- api/swagger/swagger-dev.json | 10 +-- api/swagger/swagger.json | 10 +-- documentation/docs/admin/roles/index.md | 2 - managed/cmd/pmm-managed/main.go | 2 +- managed/services/grafana/auth_server.go | 2 +- managed/services/server/server.go | 46 ++----------- managed/services/server/server_test.go | 43 +++--------- managed/services/server/updater.go | 61 ++-------------- managed/services/server/updater_test.go | 65 ++---------------- 16 files changed, 110 insertions(+), 242 deletions(-) diff --git a/api-tests/server/updates_test.go b/api-tests/server/updates_test.go index dbddf242f66..0749adf0c78 100644 --- a/api-tests/server/updates_test.go +++ b/api-tests/server/updates_test.go @@ -192,12 +192,24 @@ func TestUpdateStatus(t *testing.T) { baseURL.User = nil noAuthClient := serverClient.New(pmmapitests.Transport(baseURL, true), nil) - res, err := noAuthClient.ServerService.UpdateStatus(&server.UpdateStatusParams{ - Body: server.UpdateStatusBody{ - AuthToken: "issued-by-the-previous-instance", - }, - Context: pmmapitests.Context, - }) - require.NoError(t, err) - assert.True(t, res.Payload.Done) + for _, tc := range []struct { + name string + body server.UpdateStatusBody + }{ + {"with a token issued by the previous instance", server.UpdateStatusBody{AuthToken: "unverifiable", LogOffset: 1024}}, + {"without a token", server.UpdateStatusBody{}}, + } { + t.Run(tc.name, func(t *testing.T) { + res, err := noAuthClient.ServerService.UpdateStatus(&server.UpdateStatusParams{ + Body: tc.body, + Context: pmmapitests.Context, + }) + require.NoError(t, err) + assert.True(t, res.Payload.Done) + // Pre-3.9 clients join log_lines unconditionally, so it must marshal as an empty array. + assert.NotNil(t, res.Payload.LogLines) + assert.Empty(t, res.Payload.LogLines) + assert.Zero(t, res.Payload.LogOffset) + }) + } } diff --git a/api/descriptor.bin b/api/descriptor.bin index bdb3305f10a84f4549d656a0b06cb2a9f11ccbf1..e6481fc092b60586292ec392f67f11b932d55847 100644 GIT binary patch delta 6150 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 TF%IQ!hAjyG`k=YO+!^~f@fuu2 delta 5854 zcmZu#du&v77M^qF_Rh?m+je@}4z-2$(uzF#jqJy!UEE1?NZy?P8A8u zC^iLGg1oj@WQ{=u6cn(cIjfN|!K_a_7bnxkCjin~kDhOLup6w0EbhGDDIUwzamWy03+HcvNg|6p`B{ z@c%>)+&#%m6&dH}8xbof6#9Uj<0qpP2QJ=bmYLMne~&q|cB!FhCWowWE6*x>upe5iF|Bg8enVbIR<3J=K~x-f_9l{ON) zFo$ZV{$6uT#xqL+rvFX$X|i{IVvAgE1a79&EJh%ke4N7L||btmMkl zY4+_Mk2**0Gk-H&Y_H668QhiN&WG)MOJ7Byr<}a`=KbZXC|YLZV!89hvDQnFlobL5 za#qhb$JF*xS&g3)mYn(q55lz8CzM)6At2}6e6zl`kIF{*IiU1WwXXo*+8UvZRTKtt zrYuT3g zBOmYKZM{&$ZUQT$t#=a5=I9Lm7C?~_KNH0DR5sLuVtNB{5%x-mFb!1e ze*|>{6}$7rM^HET^G$FV)QvJP9NdjP|kmT;nTi@Vkj{=3TC)P_dV* zfxC%f{&HxbZlb~>?`sZ{HxsU+ON9{72>+vkxOKAe3C?MN)Mm15A46(0RbA^r9ohY| zYSn7;m`}gc)@+W%s_7v12g|L4*dHu+1VksWA5q9XIth+|>QHQmH%J6jhf3YEh_6wB zL+gz^Lx;miRDB-iuUiz#Tk?X zhI=-0!QC$0>57XQz1=B)z`XhP?Exhhl?fN&hI6@*1b#yO3hx1R~dbT@>F2y zZ&2u6qS9sd+|=TxJKEbGb2cn6i=6Q(v#I+0i)Sz2ZbVc77$48Y;D8AU=%4- z2ZK?Wp{0FD zp%LmsAwGOa(MlW#)`~3sBMN;?q&^kl(?@6Jtx}oAO7>G?OPxnRO9OJ}420ZIsn9KHfKdHZ)?Lgd zE&Vu!zM+ikHi^{Z6m@S&QeJP`3EwTCLhhJASjrO=&sP=zK|Mim6F!`IVD{G_zbDf{asJ8?DI*#^#Us7Hb@{)Px`GF5Y&@?>rJZG z`-QaLRfH1*Y-xaXrD@yg_>*~qGvj`y%`Y1^L}qmvVoy5$k5lRp;6Q?IFlYSM>Spu%o|X+KnZ_n>FyV3q`&=f zC|neU(Mkc`{i5^SLuPg3#Xv!N{6$LmZl%Xxq-y`fnozhT3L}&Pdi*7a+Rc%TmjVUp z^p_~%-xulhm;A1pQgb;_$c4gXN?hk1A8GuTX>_$}e@QIZ zSI+5nb7|%)zx^du`}-xtJyTE$VG=9#wcq}d(*C}d_9q2ph^2o^p?`U0Y6yn?mZI(j z7^2G5cj8pupn#SJIV#u-%0hGZLl7)^zSM3fpek5oMnAa(PCqgq3Iz= z=vRn;a;A2g6EjyxSIt}EuzQ8_)Z0iPw64&|NiuzbSg{{Sn`RUkF$G^cHdra4_*BC( zjBE%ZKaf2IKcYRB{%_KLBptknO4=B;5Q_guIYz>0h~1nnwSH+rt6hW|+*@mjVEDhVz8uF!mO5zFq?PPaKLvktw(vlRw$ z9T%z#A!%T(si~BH62!_*K>SRU|M3J zZD76JGc6GezmfGDX5*41w~iG$eB#p*dN5UhTlIL9+By~kl~ zmbcQHXWHTTOmCJVidXLu_Omi{bjTeGG$W%^l;Z&juz$Vy8Lv@NVxdZs0U;h$r@ z)i~>(>Kseo#{3TsmwxQ@^q3#xpD^xq%Rv>`7A$=Zl(q#+FORxyvh?mPL)+6|WNioh z6sp^qE)>Dy~OcdYI-s| z3Bl9OU`r$dTQ9Tz8{;OmCAy{WW`4t&{HQrSvzvAGQyE+x#NF(6CV^n>=K7nwnM>#0 z!&;-aCAzmIdpJJLGwp^TvWM-d-V%VN?`7@vtY=+$-pldVm1k9GBnH~6tPk_HL?Rge zHP)x72M+8+EPa6akTY_b`9x-bb@kSeIG6)ms(#rC1Z{wCyw7tjJ$+wrWh0*JeZiG= zLlD^)Tv@R6{j9y2bsbTz_jCMB up.gRPCMessageMaxSize { - return lines, newOffset - uint32(len(line)), nil //nolint:gosec - } - lines = append(lines, strings.TrimSuffix(line, "\n")) - } -} - // checkResult returns the result of the last update check. // It may force re-check if last result is empty or too old. func (up *Updater) checkResult(ctx context.Context) (*version.DockerVersionInfo, time.Time) { diff --git a/managed/services/server/updater_test.go b/managed/services/server/updater_test.go index 348f5df815f..a36898a730c 100644 --- a/managed/services/server/updater_test.go +++ b/managed/services/server/updater_test.go @@ -42,7 +42,6 @@ import ( ) func TestUpdater(t *testing.T) { - gRPCMessageMaxSize := uint32(100 * 1024 * 1024) const tmpDistributionFile = "/tmp/distribution" sqlDB := testdb.Open(t, models.SkipFixtures, nil) @@ -247,7 +246,7 @@ func TestUpdater(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - u := NewUpdater(gRPCMessageMaxSize, db) + u := NewUpdater(db) parsed, err := version.Parse(tt.args.currentVersion) require.NoError(t, err) _, next := u.next(t.Context(), *parsed, tt.args.results) @@ -279,7 +278,7 @@ func TestUpdater(t *testing.T) { t.Run("TestLatest", func(t *testing.T) { version.Version = "2.41.0" - u := NewUpdater(gRPCMessageMaxSize, db) + u := NewUpdater(db) t.Run("LatestFromProduction", func(t *testing.T) { _, latest, err := u.latest(t.Context()) @@ -312,7 +311,7 @@ func TestUpdater(t *testing.T) { err := os.WriteFile(fileName, []byte(fileBody), 0o600) require.NoError(t, err) - u := NewUpdater(gRPCMessageMaxSize, db) + u := NewUpdater(db) _, latest, err := u.latest(t.Context()) require.NoError(t, err) assert.Equal(t, "2.41.1", latest.Version.String()) @@ -329,7 +328,7 @@ func TestUpdater(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() - u := NewUpdater(gRPCMessageMaxSize, db) + u := NewUpdater(db) err = u.check(ctx) require.Error(t, err) tests.AssertGRPCError(t, grpcstatus.New(codes.FailedPrecondition, "PMM updates are disabled"), err) @@ -369,63 +368,9 @@ func TestGetReleaseNotesClosesResponseBody(t *testing.T) { http.DefaultClient.Transport = stubRoundTripper{statusCode: http.StatusNotFound, body: body} t.Cleanup(func() { http.DefaultClient.Transport = origTransport }) - u := NewUpdater(0, nil) + u := NewUpdater(nil) text, err := u.getReleaseNotesText(t.Context(), *version.MustParse("3.0.0")) require.NoError(t, err) assert.Empty(t, text) assert.True(t, body.closed, "response body must be closed on non-200 status") } - -func TestInitLog(t *testing.T) { - setLog := func(t *testing.T, content string) { - t.Helper() - old := pmmInitLog - pmmInitLog = filepath.Join(t.TempDir(), "pmm-init.log") - t.Cleanup(func() { pmmInitLog = old }) - require.NoError(t, os.WriteFile(pmmInitLog, []byte(content), 0o600)) - } - - t.Run("reads from the given offset", func(t *testing.T) { - setLog(t, "first\nsecond\n") - u := NewUpdater(1024, nil) - - lines, offset, err := u.InitLog(0) - require.NoError(t, err) - assert.Equal(t, []string{"first", "second"}, lines) - assert.Equal(t, uint32(13), offset) - - lines, offset, err = u.InitLog(offset) - require.NoError(t, err) - assert.Empty(t, lines) - assert.Equal(t, uint32(13), offset) - }) - - t.Run("skips an incomplete trailing line", func(t *testing.T) { - setLog(t, "done\nin progres") - u := NewUpdater(1024, nil) - - lines, offset, err := u.InitLog(0) - require.NoError(t, err) - assert.Equal(t, []string{"done"}, lines) - assert.Equal(t, uint32(5), offset) - }) - - t.Run("stops before exceeding the max gRPC message size", func(t *testing.T) { - setLog(t, "aaaa\nbbbb\ncccc\n") - u := NewUpdater(6, nil) - - lines, offset, err := u.InitLog(0) - require.NoError(t, err) - assert.Equal(t, []string{"aaaa"}, lines) - assert.Equal(t, uint32(5), offset) - }) - - t.Run("returns an error when the log is missing", func(t *testing.T) { - old := pmmInitLog - pmmInitLog = filepath.Join(t.TempDir(), "absent.log") - t.Cleanup(func() { pmmInitLog = old }) - - _, _, err := NewUpdater(1024, nil).InitLog(0) - require.Error(t, err) - }) -} From 2f916bf85f3d9b2869e7db1745e0a92af5a64db9 Mon Sep 17 00:00:00 2001 From: Alex Demidoff Date: Wed, 5 Aug 2026 18:37:50 +0300 Subject: [PATCH 4/4] PMM-15266 Update the comment --- managed/services/server/server.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/managed/services/server/server.go b/managed/services/server/server.go index 0dc3dda0ef5..022afac7432 100644 --- a/managed/services/server/server.go +++ b/managed/services/server/server.go @@ -339,9 +339,7 @@ func (s *Server) ListChangeLogs(ctx context.Context, _ *serverv1.ListChangeLogsR // // It exists for pre-3.9 clients: after triggering an update they keep polling it to learn when the // freshly started PMM Server has finished initializing. Only the "done" field is meaningful. The -// authentication token is not verified - it was issued by the PMM Server instance this one has -// replaced, so it is unverifiable here by design. The progress log fields are left empty, as the -// offset a client carries over refers to the log of the instance it polled before the update. +// progress log fields are left empty. func (s *Server) UpdateStatus(_ context.Context, _ *serverv1.UpdateStatusRequest) (*serverv1.UpdateStatusResponse, error) { return &serverv1.UpdateStatusResponse{ Done: !s.supervisord.ProgramRunning(pmmInitProgram),