From 811f549164618e3607721eecbfd76e292555b339 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 19 Oct 2021 09:09:54 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../projects/environments_controller.rb | 4 +- .../projects/usage_quotas_controller.rb | 7 -- app/models/deployment.rb | 2 + .../deployments/_deployment.html.haml | 2 +- .../development/project_storage_ui.yml | 8 --- doc/administration/geo/setup/database.md | 26 ++++---- .../contributing/merge_request_workflow.md | 1 + doc/install/aws/eks_clusters_aws.md | 3 +- doc/install/aws/gitlab_hybrid_on_aws.md | 3 +- doc/install/aws/gitlab_sre_for_aws.md | 3 +- .../integrations/img/zentao_product_id.png | Bin 0 -> 40486 bytes doc/user/project/integrations/overview.md | 1 + doc/user/project/integrations/zentao.md | 40 ++++++++++++ doc/user/usage_quotas.md | 5 +- lib/gitlab/gitaly_client.rb | 22 +------ lib/gitlab/spamcheck/client.rb | 16 +++-- lib/gitlab/x509/certificate.rb | 20 ++++++ lib/sidebars/projects/menus/settings_menu.rb | 4 -- locale/gitlab.pot | 60 +++++++----------- .../projects/environments/environment_spec.rb | 36 +++++++++-- spec/lib/gitlab/gitaly_client_spec.rb | 23 ------- spec/lib/gitlab/x509/certificate_spec.rb | 45 +++++++++++++ .../projects/menus/settings_menu_spec.rb | 20 +----- spec/models/deployment_spec.rb | 11 ++++ spec/requests/projects/usage_quotas_spec.rb | 48 +++++--------- .../support/database/cross-join-allowlist.yml | 1 - .../navbar_structure_context.rb | 2 +- .../nav/sidebar/_project.html.haml_spec.rb | 24 +------ 28 files changed, 230 insertions(+), 207 deletions(-) delete mode 100644 config/feature_flags/development/project_storage_ui.yml create mode 100644 doc/user/project/integrations/img/zentao_product_id.png create mode 100644 doc/user/project/integrations/zentao.md diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 23dabd885c8..84ebdcd9364 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -70,11 +70,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def show - @deployments = environment.deployments.order(id: :desc).page(params[:page]) + @deployments = environment.deployments.ordered.page(params[:page]) end - # rubocop: enable CodeReuse/ActiveRecord def new @environment = project.environments.new diff --git a/app/controllers/projects/usage_quotas_controller.rb b/app/controllers/projects/usage_quotas_controller.rb index 103e1cc596a..b319e427eaa 100644 --- a/app/controllers/projects/usage_quotas_controller.rb +++ b/app/controllers/projects/usage_quotas_controller.rb @@ -2,7 +2,6 @@ class Projects::UsageQuotasController < Projects::ApplicationController before_action :authorize_admin_project! - before_action :verify_usage_quotas_enabled! layout "project_settings" @@ -20,10 +19,4 @@ class Projects::UsageQuotasController < Projects::ApplicationController wiki_help_page_path: help_page_path('administration/wikis/index.md', anchor: 'reduce-wiki-repository-size') } end - - private - - def verify_usage_quotas_enabled! - render_404 unless Feature.enabled?(:project_storage_ui, project&.group, default_enabled: :yaml) - end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f91700f764b..140d4fefd90 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -54,6 +54,8 @@ class Deployment < ApplicationRecord scope :finished_after, ->(date) { where('finished_at >= ?', date) } scope :finished_before, ->(date) { where('finished_at < ?', date) } + scope :ordered, -> { order(finished_at: :desc) } + FINISHED_STATUSES = %i[success failed canceled].freeze state_machine :status, initial: :created do diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 57dfcb8cf4a..f18574c3ad5 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -6,7 +6,7 @@ .table-section.section-10{ role: 'gridcell' } .table-mobile-header{ role: 'rowheader' }= _("ID") - %strong.table-mobile-content ##{deployment.iid} + %strong.table-mobile-content{ data: { testid: 'deployment-id' } } ##{deployment.iid} .table-section.section-10{ role: 'gridcell' } .table-mobile-header{ role: 'rowheader' }= _("Triggerer") diff --git a/config/feature_flags/development/project_storage_ui.yml b/config/feature_flags/development/project_storage_ui.yml deleted file mode 100644 index 23a5b5c3d29..00000000000 --- a/config/feature_flags/development/project_storage_ui.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: project_storage_ui -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68289 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334889 -milestone: '14.2' -type: development -group: group::utilization -default_enabled: false diff --git a/doc/administration/geo/setup/database.md b/doc/administration/geo/setup/database.md index d72bb0737ae..7372cc4ce15 100644 --- a/doc/administration/geo/setup/database.md +++ b/doc/administration/geo/setup/database.md @@ -7,23 +7,21 @@ type: howto # Geo database replication **(PREMIUM SELF)** -NOTE: -If your GitLab installation uses external (not managed by Omnibus) PostgreSQL -instances, the Omnibus roles are unable to perform all necessary -configuration steps. In this case, -[follow the Geo with external PostgreSQL instances document instead](external_database.md). +This document describes the minimal required steps to replicate your primary +GitLab database to a secondary node's database. You may have to change some +values, based on attributes including your database's setup and size. NOTE: +If your GitLab installation uses external (not managed by Omnibus GitLab) +PostgreSQL instances, the Omnibus roles cannot perform all necessary +configuration steps. In this case, use the [Geo with external PostgreSQL instances](external_database.md) +process instead. + The stages of the setup process must be completed in the documented order. -Before attempting the steps in this stage, [complete all prior stages](../setup/index.md#using-omnibus-gitlab). +Before you attempt the steps in this stage, [complete all prior stages](../setup/index.md#using-omnibus-gitlab). -This document describes the minimal steps you have to take to replicate your -**primary** GitLab database to a **secondary** node's database. You may have to -change some values, based on attributes including your database's setup and -size. - -You are encouraged to first read through all the steps before executing them -in your testing/production environment. +Be sure to read and review all of these steps before you execute them in your +testing or production environments. ## Single instance database replication @@ -214,7 +212,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o ## - Prevents automatic upgrade of Postgres since it requires downtime of ## streaming replication to Geo secondary sites ## - Enables standard single-node GitLab services like NGINX, Puma, Redis, - ## Sidekiq, etc. If you are segregating services, then you will need to + ## or Sidekiq. If you are segregating services, then you will need to ## explicitly disable unwanted services. ## roles(['geo_primary_role']) diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index a521d89db2b..91643cd3ee0 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -240,6 +240,7 @@ requirements. 1. Working and clean code that is commented where needed. 1. [Unit, integration, and system tests](../testing_guide/index.md) that all pass on the CI server. +1. Peer member testing is optional but recommended when the risk of a change is high. This includes when the changes are [far-reaching](https://about.gitlab.com/handbook/engineering/development/#reducing-the-impact-of-far-reaching-work) or are for [components critical for security](../code_review.md#security). 1. Regressions and bugs are covered with tests that reduce the risk of the issue happening again. 1. [Performance guidelines](../merge_request_performance_guidelines.md) have been followed. diff --git a/doc/install/aws/eks_clusters_aws.md b/doc/install/aws/eks_clusters_aws.md index 3c19a83f128..86318467a91 100644 --- a/doc/install/aws/eks_clusters_aws.md +++ b/doc/install/aws/eks_clusters_aws.md @@ -1,7 +1,6 @@ --- -type: reference, concepts stage: Enablement -group: Alliances +group: Distribution info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/install/aws/gitlab_hybrid_on_aws.md b/doc/install/aws/gitlab_hybrid_on_aws.md index 4d22a29ad0a..1fe183629b3 100644 --- a/doc/install/aws/gitlab_hybrid_on_aws.md +++ b/doc/install/aws/gitlab_hybrid_on_aws.md @@ -1,7 +1,6 @@ --- -type: reference, concepts stage: Enablement -group: Alliances +group: Distribution info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/install/aws/gitlab_sre_for_aws.md b/doc/install/aws/gitlab_sre_for_aws.md index 06e3bf784bd..3365888a8ea 100644 --- a/doc/install/aws/gitlab_sre_for_aws.md +++ b/doc/install/aws/gitlab_sre_for_aws.md @@ -1,10 +1,9 @@ --- stage: Enablement -group: Alliances +group: Distribution info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false description: Doing SRE for GitLab instances and runners on AWS. -type: index --- # GitLab Site Reliability Engineering for AWS **(FREE SELF)** diff --git a/doc/user/project/integrations/img/zentao_product_id.png b/doc/user/project/integrations/img/zentao_product_id.png new file mode 100644 index 0000000000000000000000000000000000000000..a91b4c3f82d1c43528829d96017b693de89a8fd5 GIT binary patch literal 40486 zcmeFX`6E>S7eB5&rHx9K$Tm}CyVl7TNnwn#W)Ig$WSJ4!qau;*X1NRsDYC>^vRBq? zE)vPU8#`sq+IM<=zMt<;??2)DgUdY4^FGh>aUSP9&-0uUdW&%5*b&YnOiWD2G;gZw zF)8drppHYlJc9!*pJQ6&kI`qTwGlI`}gm{ z!UFg07GC3aN$**#UB9@;)Wv&)Le@PJp3|3I#w5LFgdK;)A5QV;)o|)GW57O9(!|`{ z9FO6*^ZKnAn_jsmOP3smc<+3_VA^s1b~WHLCwRA8`tjm?vo-9 zMlYCjh`Nq*-9mYczH#Z&E)oZ%{1*VzxEmR(BM^f0!)GJ(G(9?kF=wDIWI zNIaUB4p@|bx_s&W5GR2q>NYOwHo<{MW`1o;`Oa&k&dP)uvp;)Bx3;!~9Y#crl)8?^$jr7mND7Ne^nM)v;=_C?ZS}{`KG5;e&D*wh zKh{@P*M>*Ok={Q8^exJp4|ndmjMJxM^5@n!ce{HBBcRvw3%`DhZr%u>PfpK%ZeQsc z+xT3IEG@6tyZGkQ{yYnd^Guz0PM9Ue%*Ge}Uc7HOGB#P)(4y)+x!Kt=JGV$M1Rn=S z|6biqNPdsC>(hNYMX6p>^%#y#%8+|FaVucNCTY2>s@D1?y}bR;?9$HKhg50T;akzm zRbLvGp(ig())p6k4-C`iw1JB1I>52dFK=~-zV4d2y1Tk^3G7mM@~f z=Lw6)?N^A6K#w&A-w84`|JR#_|> z&F^KiE3t>M{|Kw+c@{lmqnllR=laDkmJA~|%MTwR{`>VJH~ETd@meRNT$sRB5y{F; zLp@Mp`NQYzU_J1{L!TqqWgdx#O~H=@kB(laI5BZOVX@oOW6ZK3e)35^Q|cUB{$pbo zcroa)@1g2H#lJN)OxAbs0C zKfmlBXEs@!dZZ@d$YPfqNZG4D`_A!&KGItS9!V%?cd({VSVfxx9|@*2*5#Hs#1_-e zd;p9;1v6b>9=%n0wj$)FT4iXhx(Hj;jkMxbH@43phd>fj2K#GJw9qrg@^xv!dc@(I z0v(Sq=h(zY$HN2Bmzwm`w#H{mZH>vsF=ao)A+yoqM+a)^mU*5x`3Q=qR5r2N3ZA%H z&e63Z#R!DnnEli($e7(tbsoDZ9G3gxGwt9fnzuP zZXABwwl8zfACs$`M9w1zHICrlu)Rhlq9zDn2Dz-G0Ri?++gubl9zS|rOjpnX`y!8m zco6nv9SR>$F!%HGb2e4H8G2aLq1-<#LG(eaG=E23+@n)X*sEY%U?t=mqdtCq-#X0P zbWC7raUnrmIeXSxOfcJc?fK8(Jgw)MCI=+KSS()P;wfR7USctNox&2u_&V#cZH)JX zap)9O8)N<3H0QX;g%2^K*_s9KyV+3obwM8%qUCMuZs!*(s(#mjly+IU$HxwUfv4mS zUp>fC?st&?%7pS-vmgJk``A;Q|nW_dtBp&=oIF0D08rLeBd1BbVca?tDaL=A1r^A`Tgi{@>0|z zr`0N*yJY?70fVcBF9vhe475VYo-9Qz3hmg%*ZPAp!Gj#G7;hmrSy`g0TqD}D$K4ps zRbN3wkl3kL#@!-Thf><*hs^$%ry!F;+Fl1h&GxE{$L{+YETq24<}+jpCV1)W@0aBF zo{$XUu$hWs5oCVugZz2e{A=rOY3qrzGakR1@nx4yE{R+a3IF8rc|LCU#!K?&8ndNP z<&6_>yoEASC7oFx9$)409!zPxQ&NyI{gbQ*HHNIL^_#sTv+02#S@=FPSsUJU1m4v(Qcl>>$vkC7b zp&(QH&@cETCryboW7Y(d;03l{(`>f=3P;n8Nu?__T=$aXSbvDBgt14jkE+}SV%we? zTnbYPD`^x=a_~a4=Ov42D{cuTw9Z`UxF1oydipi3Q&I^7p0V}9&r5}s&{#sJB_|&a z^}oKn|Cx8@gyc)c7kJo~g)eTMOgGb@D>SSmIQ+D|9}l-C@aS*hMNYI_aY<84mg=;< z;z#bsP64)$@B<2dy>>SeXm#g!$OEeh=Sv#R%~D4nx9tzhBCEU|o0V*NP0R4$7-k5Q zfxoNqu-i7`eq^Xv$MJIfuY`pCnKHst0B?bdb4jn5)NDDbGY{l~^Zj;<-iroQmbHXl zJ3T7PuY_KE620}Cbz<3fSpWAbW0W+seWuZNW{{ajYH?CBwx!6NSUL^lntRSlKdb+h zpffD7PcgF)&ZH?4j`w(FLE+CkaQ|&v` z(Z+Ala$LXJ2Jg4PG^{4vh^Xn@U@k~{J1_Ad-XuX%iTGtLVHo&gfAEiXKFz$uf*3yw zJQRCK5No$iM6%BDbQC0H(hS}han#xhCtWJ6hkvNXJ9yy>8Kab93nKa`abogf{Dv`% zM)Hb3K1zaD{J3}CT1)7}Be!{|Gv0WwNv4M=`FJ_{$~__L)em6?!0zx6>KrPjkHe3# zL|)RtShceZ@Oa~U#MUXVX%uWcZqb)FKa2!-%F8KQN0z--q6bYg#4U6AgrAA!g`>utD3nmeAO#+%XmBhR+<6Y;v>=&cp_m zI#=NP7^-LHf%b(Q*AJp%Tu3!29}OiH#3E79LH z*+$iNijf&>{+pYnMC@v_UE}0oXZDCTD&F8BqbmtZ&Vgnm*tj(2 z=ao~F4sn6;KLe!{Gd*Aihe%49Yp~H!WyDfKx13~fqoRirJ!)POu}Kt@davK`8+Ohw za<*?I!KeFd<8&g+(v;|03AOH<#DqrfbKiJ6recF>bDkFT3=g=$(EXd8QA9>#PE?~J zVB^I=)QHMb6%;Woc{Hz)(mwxv>>q&&oxJc;Q^1W^v`WEvq~*WC3+1O7~!j$7977wg9OT>F!6VqSSsuJ+a6vLlmiUaF-a{dcv0^)XWFkpzW&hOiM0@mmz3H61twElUdMM zBnM|32Y*5x=Yhzv2SCyaMhZzn$NxG)8{hju;Yj8`&VXoq8S9kfOC6$5Cx)u@9rO>a zKyu`h(CT3wqEHt`|Dn#VN1iPF?3WJlOE1{korK!i)KL*`=04D)Hwo2VNTrxbm;ouFx2$gibnI&wFGZSlc$4ohn8l$EvBs9t*vly9s*=wuTm!U)Nh8dH_ zBwt$4bE(L@QSgF1!j+51XbUD+|CXW*KP3Kpyb8WGQ01gn>NaRV;2^l04#r z?*-%d<=~DZ6Ov~dFk~7-$-yqb&K0{(%yRM|IJk|KgW1jSJwyq$zA4u0fY9fV_fdK@YhZ&?77vANirTso(4;vYn@nrS>*DA{VCA1uqrH$0m9^<&mkk+{Ki>xzs0y&v!&&gidW%cPtV54>rS?K+)A$-K zHETe0k*s@=+t+Aj57Ny%bc(sS@9c2tAiV67HZh#r}~T^ZS3*^h75P|pp9b% z(1x~bZBCV-0{oSm9N*|%rqp(NDbpR>QR(R{2k3Tr0yUE#&HVM|Y&ofDvi}uWjqdaf z*al@;=vScev=dhN|^ja1u|G3o?vnKWhBTQRXS z%H5G-mza!pboqPrU4egurP|82BijYtNRfEkvY8oBNPY`qmxUN2#r!~gV_fXY-GS7c zL0o=IY_2DeI=N0u&7TLo=QpGi?YjB}7NfhxRfrFP$QQ|IdTTO%(vp6F8sFomk!o!j zH484BQGk7vuE0KA4}k!Xu?>L|U{(R9b9XF&yLr~wTGZf1Zpg z0m-OwnGq&lbRFjjoRIebP8cY_KWNRdHTOG_oHFumk+BH18tTD%0A(GYpbeIQ$!(`} z<;-rxmoNujq4W-rD&nVOMYvi3$A12J0-ff8h5E!@%&^N@4aulg5f#d60|IR3;>bNQ zv!H)RG1N2h4)Ri)15;ePMFYN+MP5QB=S?zi@OE5@NX9p6WVSbtZza$#LyLCDS+-GK zEaV9GO5qzVF++%=m);bZveJR{U-AGv(;#Ln8v#SX%fqg~2>2Cw`u`97e|Wq+zZ@9* zQmaf6uUoc#*Vc~xf5McDgDtow#=1CUm7BRN{wIpE^I)C<>TW_dG#dV27&|p1<^>@M z{gNc2*Ma}3jUfU(>&2t^O6SC$1OH25nJQSS{G3E>PG*bs+U@_T#EwX46E8oV#q#-oQQ^`g$Rk#Pp(TFsJ@-Ff!ohe+k7I;`C(v`li-|y_ z66m+yNbF~bOgAC};_dqwe-W1n#OFNnaIQQ&w(@V8GKXaJ1=V!i*Kin=odDwi>)4Yr zluKX!{p&3Suk;^eJw*_rhM}NF|97BFGZ{V9sH9sQomC2igz$bwRu;jttQp>{2mX<{ zGz!WJ`H$8~9)J>tG4l2bOl(A~_Mf>$BXzs2~6RnCBgW|P|g z$m4tl+mZg?7c-wiCVh-Gr+*^uQ~zO5hNGad2mSEvh;$ocGB1J1SC5I`Zreek4E8Ye zfr^fTr2n&Sk}FW9z}WPT$A4zsuTCJAsTR7I2h&`E2?hAtDOPi~WVHA~=yL+mNTyBA z+oTzx82;G%kK##R5b1w_jexSVVDG>8wcy~F|K2tN`r`<6tub8Y6j=U}@v$5SYd?K; zKY-*F8YT1w%Ic*}3igP!BPg5Ry6e* z&9vB_J=o>>nw{nO+;I@2S-oo?KdGtb3K*tobca%l;AmH1HEP;#RQ*8@LNWUB_3q!Y zSDf!m$VP_tEk^XQu%u7JWE*zk3;{d?$Ci$*evbq=nT`?Rx! zxE6Y0srHw+tGmJ>jwFLucW>S1FIZE6k&j;AA-OxBD$gF*EuI4DGRuN&rEBF1uvDV# z4-8hyp~}{Gb1B&yOIGW!IZp<#(p$7+8?L~lv}mU?cDdvO#PQ=PTdXV4(fiN=OI?iE z_&SXgRLr{qP*)9l@7@hF3L2H!@^AMWhZW#={f!BybducNfq}DVUnkidFma2&f*wNQAKMxU#6CV74&!gHN2P4vCA3P=Mn_lTJ57{YO!Y<IFPaJpq}mi3Pv@<5;rr$pptRXrn%$QKjO*}S*HU_-p-{nglk3N}GocOYF*l{#_%#pUI# z32D%jKzz|~;{#MiL?wQzW68N!5cghjD>f|b%&-**c9{e3t0ylm+Vhb=21Gr>(-4pk zB5I!naxF1O8T?k51$Q3@<3)AbiZt|z65~BcWs@4F)QoVAxSWjg2!NaJKxzHGN1^5~ z{1js|PS_|lMzjx|b7X*Jv^RblxpAkLo2o|CCSZ=i(3}jL+nSu`JXq*?e;CBP`uGaU z;;`E9r_Cw1ufP-wJwqdKur!#tx?Y`vn#1Z zE>|1}mZiaiX{`i|(!_Bb%$q$vlwY%p1(x1)P*cDC)!luqvU$jFr9of>6pNPc6VZ*D zT2PbLpL)v&=}3>^B}WzD>|(PNL2%<1CTXymLU`+4y54&KQW}xXv0*<|uIB1rr%#|e zKBG6 z%M7nJ_Yu;(MyyM)Xc+x+_fbO!GLC_r;<9uBhTAr2S{Xvni_LbnrHW}6e@@$<1^Va= zEF2VjHHNU?s)mlhqVfy~8^V=|&l~b87)tiM9OpRv53`AX-&=^EyvJH&-uKU+ohJbf zP25sGaQsw!#l=~$Flyr$1ueLGSF0BRA-n??3PztxmQgC$58tadO2&(xl%=3HL78Yp zt^!EkwMT#tqH6319U%P-`ytGHomG!ba{$)xdWnD zecLd#4nY}OwGg+dcH+?aPP-B_%R3SQ@LRs+Zysv=o8Jnr-c0dN)P>u`a-8LA^6?co z_=85X(=b@5Nq;SmNGrfjF99=iLG+T=Qi3Fs(<FqH^@Pd7aw+-fun<1oRx8qeiyfty6gx{8^x-a--M% zf@@Er?b-U@cDK8b48D$jvAi)vKP^_&Zm;xiZTv`;^}sKmCH-KWR5AobKW>mTsvp)K^~`eC}d&%4$G$RbDl? zZx0(Yjb-jz>_R`TcHlDHr~L&X#vO(Ci;0(5LLUZ-)ThEe#QqRFyV7Z~u<94kh6-lM zU_{R-HtpY1Yy+3j)XbK~Mc;rS|Fw8gu5ISUo!QED9i0x}pJYHgNQVR7c8PCm`CVhd zqom+ncS`Fe4wlg+x}T~!)s-G0@yx^M{KLR_B=)kU{PiE>tfr23p>5;<-P2KsAIuG; zN8YS37slP|OxM;#VSJwSD!BQohaXEluS*w~^8plau!|Iyng?pE(SnLWMVs|PQr$5Kv?wB73NkScM$o@e)tP=NJcoubZ{P&Ro zwV$~hOE#Gg6jX6AmHB3vMWh4L;Gk2q|mpoN(oaQC zdEYN3n8Wx(ViEPv-5=(*X*q^v5z|>m#Gd3?%yUeKRygD6FVZM09u#Q$8nF!ya`c-T z(ee7la{f#lSZ^$;RIVawzaaAnFoyED9AU`B7&x(RB*C{>KTR)lG}w$?*Z&WW9mK_+MdnaaU%Bw==fd!%pmp4I6H{@mL32KDp;J^rD$} znj5$#wER9FBpy)pR}aEiy8k|-OLrsz`$U%+^wm*>YrO0K!SRoNGT07dpq7F+j-dUC@Mjf8=r1~d^C5@6V{^d^R5 z;aQfS-((?P@=120I~NW%M^ zVm?d3TOs2P8eW*hE#1_-Hq&*`Th%X$qU=@p7v@Z{-t!7RvPqL<1!lQQqM>O92ML%w zztykLzVtd|Hienw#R?f7Jai}(UX^NKsncuxI;R8$) zm8~d&^ON9tM) z0|pcmexX&0PGE3wJa)HY76%8^2;c{@Pq^qXds^o5)?8QF|2K`*eP(;BYG~+_TawYx zr{9g9vRF20kH6oiqC@UGBv&TP3dQP4sXhy9Apy_o+b>=q$K9v3$9-ZaC+}|!E4h&4 z>Vi48xLDQ{i&yk*q8xh>m?kV#%=^I7f(PVyW=iKG0i`3zc72i}%;301GvZ_b2x!Ng z9;%46HoRo4C+Hx=iUf%qE!%q+Kw|Nlg+^Kzbdp8j*G)nK^t9wdc9<`(i2Ov`2j!04 zP{KV=jr&snw-?|H$t0Y<2d2oaz7=~N5~FjEo8My?5qy_=Pw0iJGI3CFM!My|5|MEy zy<-FRD!OUe*HJuF!Dc=^K&hdq>z{|Fhk8A3QDcI=dhLYYSsQNNv5Jehkkk|u7uVbL z&yE5@$Pv1Gf!|s^CKp~&!%rKgHntu5F#&a}>j(jN;#E}K_j2_nT94q-okZlQ_b7wE z`*blphD?47Surn8%JnXMRDk{0-;?dqN4VWyemNEMY5tC)rg|1TiL?`cBt#0PPv4V1YZMO&zc`xwraX;7Pg2pi(wQn58#JDy zT&`qobZj55iV6K}&V-%J9H{JFSy{Qg{dte?!&K8hcVLngDR0|h{)+Muy|&-#N5)bC zbFO5c-$fAAgCI^ZP*2eU_QORHw)ptdZ#z+FW6XIH^k2{V^b0Z;BYYItFnX*WZQww5#iujXpcFuOqg`~HK?T>#Ig}dRUp@?Ox z;pJHvxt0PsxEKO~L0Y*DzC{dX{@eiMCc)|%gpw`lQv072u~5$x5nPCF__ zlNB^(zpIPgv&;b>SD;q(?pjUWIEAOmA$m9ycdtB2cw(q+WdR-5IDXEH<2d*?A@yt^l zk;mFoFd?7sarQ{M#df~oo72rr;k^>6e;VtjOJw-)$M`34L7Gvf6Rr4{+X>Q+@4?ZJ ziU2s;Z-d>k$b5ywhZY~*;W?Kmh<8eud1uJ80XWn$jN|~nmZ{Ckj!tF3n-{jvA$Ewc zJToj6jT^7^#Yp+Nemrt(<^4iPC&3mxX?)X9R_z6bnpHp38GGs@F-7?C5q$W1R1CtB zEiU&s#JD60+Q^P!)(}a>-K^8dLq!VTr{btbR+gGxGV@&ilY#;X-?m-3Qt)|pHgeB~ zTA5ZC_K5urO}6v)k?ahWDG>Q>IZHvyV{qS&1sjA)>yW^P>ypZ{!DDhrTxi+5#-?JE zHx+ay#6b_ZUWT60v1wg6@zI_Zf-X4UEz-j1aOV~7Q1E)dGZYG@pp#!FTV5@%q))01 z&=A z=8#H-VxjZPZ9|9kJ>_yYLVNIeq~MRk?VlL|c={R{;3`H-7|(b)Iw%$= zD;oCCd(w~`5%|EbiNQ|~^eu^kI-?VoH)4z~Vx{sNe%?gfAK7^s*?8#zZ_nk64!yhS zw1@lB8HS@)wf_F~BT=DRSKsDYo^_aWtmngHSR5%xILyf&2Zth-MGy*1n z7rgo9QZK$kQ~L-UmT8w$aYoqSZ=hewq@OOyhE3-_du0=|3{GW}PU+yR-l-m%T)v~3 zEy{bSg%R*X_0y!;ACLgA1>-4jsk6uS#t4oN2Wz(4zk)apRB5hG$WBu2Ra0A71*Gz3 zb(0L^Ymy`Om4~ zD2U^fqP)8#MK-HiQSgr-4$jOGl{=8?5D#5i&18kAK#hVD?zR{Ao72@)+*vyvDcQ86 z-b?Dti;wtx|G7NOXAxQa70JM3CV*uL*=E9dDL7Li zhhl3?^_B4NuI|z&o7mAjes8V@G&XaRtJC+ z8tNuxYVWwjAl!ZW1bUTO_)cYdTU$#iN5w>3WPtUY?9tC*A-~XFts=D1Lgnka|0fEb zBA)+wIrRP_>5;*`l1fP|n+Ph&sJ~>q#ZgeK%MoQ{tJ>t@A9$kfoiRH^G_eKkV8P80T8^~&;)NzJ08i?~ zg&j=~O2Je1qpUxR=3c*biyQ~V0uRQnPGrFnhfBF}u-)nSv+T{4l(2Zf6tQY2<>hxXZ?dkzCU}dPJwf5`y75F0d zr>vELqSQw(+u1Vvr@qrLyJ ziE){*Z7JV*U%uo}d*?ois7IH1QqV@@k@c1K*7?Z0%Rd^6S5>4uk_+-H79&1Nx37yQPMSJ>^pQFo?iSa+5qS>6H z@&XFg?|A_+PpZOr1lfOEZlj);wSUSv6hfX9h2N-VvYvJY*n>0;Jq41{10;ZLoY5bG zgm*+0I5BVSeBKy$jpGKpf^YW9B>B+9qigA$crfDZ})5|r(-(VU%IMDaP z-Z$%$KG&-yoQ$fs?^>Za_NeRl1iM)?_&wg1ex zECtPJ22JalSdOHqX%LSjqmjW{u3HC^pSlA_V<2Xq`Smd{sOj=e0`Zh%Q9;U0m8aD4 z4~*iAsSU%cj_j#r-*UIkTGi~Nd@AQ6&_D7XYre1nmjs-HXZ+oPu{wqVncF^m$#~Ps zFd{2gtEYnY$xWAEmu>J+Pbhi>FRVIH(*iaZeZWb>-2dhAJ?Im z?c2XODK<*WL8JoI}~gpW$|RJlXITo$3>&D`t(W0sQ2(KX3gd3c2>TGN7-9-ga@ zSw0;o>2StSC|Eue3N0iJWl5ab9lkJbZJIEYLj<_UfQ2%har0iDo+fEi@W<5OLkPwG z@<=ndn`l5ZX^HiehkaU+l@)av(FwSIFNsH{RuE81L1PF&$J}oNh27UsD2%t@1IUSS zT&GP9Bmvrck+9biWgjtd+}PzKTG|WG(b@{e7VV#Wd8hRtC73Thn}Rw;oDahLQmGs8<^bC*gTFz~Rdb3A79PyqQ0@0gd{q5rHsL zp~;VWg@Al~M!?KfUiR5NZHVb$)ZK3K2{1}j+SeX!J^RS& zCWg5L!7;1KUSh0^jmZ8pk2Y=<@4a}$UT8(UX8-ls1&auNi1d;{o5+=1=G&XiCmpkg zP8_a9l=;55_{7(|gr?UUC4;AV8$wAnmvXF0z*y((jVtg0gM@?q7aI{lS)%p3rpq=m z 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 Application**. +1. Select **Add Application**. +1. Under **Name** and **Code**, enter a name and a code for the new secret key. +1. Under **Account**, select an existing account name. +1. Select **Save**. +1. Copy the generated key to use in GitLab. + +## Configure GitLab + +Complete these steps in GitLab: + +1. Go to your project and select **Settings > Integrations**. +1. Select **ZenTao**. +1. Turn on the **Active** toggle under **Enable Integration**. +1. Provide the ZenTao configuration information: + - **ZenTao Web URL**: The base URL of the ZenTao instance web interface you're linking to this GitLab project (for example, `example.zentao.net`). + - **ZenTao API URL** (optional): The base URL to the ZenTao instance API. Defaults to the Web URL value if not set. + - **ZenTao API token**: Use the key you generated when you [configured ZenTao](#configure-zentao). + - **ZenTao Product ID**: To display issues from a single ZenTao product in a given GitLab project. The Product ID can be found in the ZenTao product page under **Settings > Overview**. + + ![ZenTao settings page](img/zentao_product_id.png) + +1. To verify the ZenTao connection is working, select **Test settings**. +1. Select **Save changes**. diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md index 5a48353c9d4..16b6424b232 100644 --- a/doc/user/usage_quotas.md +++ b/doc/user/usage_quotas.md @@ -36,9 +36,8 @@ namespace to recalculate the storage. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68898) project-level graph in GitLab 14.4 [with a flag](../administration/feature_flags.md) named `project_storage_ui`. Disabled by default. > - Enabled on GitLab.com in GitLab 14.4. - -FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `project_storage_ui`. On GitLab.com, this feature is available. +> - Enabled on self-managed in GitLab 14.5. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71270) in GitLab 14.5. The following storage usage statistics are available to an owner: diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 2c26da037da..a824f97e197 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -24,7 +24,6 @@ module Gitlab end end - PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m.freeze SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' MAXIMUM_GITALY_CALLS = 30 CLIENT_NAME = (Gitlab::Runtime.sidekiq? ? 'gitlab-sidekiq' : 'gitlab-web').freeze @@ -62,28 +61,9 @@ module Gitlab end private_class_method :channel_args - def self.stub_cert_paths - cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"] - cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE - cert_paths - end - - def self.stub_certs - return @certs if @certs - - @certs = stub_cert_paths.flat_map do |cert_file| - File.read(cert_file).scan(PEM_REGEX).map do |cert| - OpenSSL::X509::Certificate.new(cert).to_pem - rescue OpenSSL::OpenSSLError => e - Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, cert_file: cert_file) - nil - end.compact - end.uniq.join("\n") - end - def self.stub_creds(storage) if URI(address(storage)).scheme == 'tls' - GRPC::Core::ChannelCredentials.new stub_certs + GRPC::Core::ChannelCredentials.new ::Gitlab::X509::Certificate.ca_certs_bundle else :this_channel_is_insecure end diff --git a/lib/gitlab/spamcheck/client.rb b/lib/gitlab/spamcheck/client.rb index df6d3eb7d0a..b8c07c0c316 100644 --- a/lib/gitlab/spamcheck/client.rb +++ b/lib/gitlab/spamcheck/client.rb @@ -5,6 +5,7 @@ module Gitlab module Spamcheck class Client include ::Spam::SpamConstants + DEFAULT_TIMEOUT_SECS = 2 VERDICT_MAPPING = { @@ -27,12 +28,7 @@ module Gitlab # connect with Spamcheck @endpoint_url = @endpoint_url.gsub(%r(^grpc:\/\/), '') - @creds = - if Rails.env.development? || Rails.env.test? - :this_channel_is_insecure - else - GRPC::Core::ChannelCredentials.new - end + @creds = stub_creds end def issue_spam?(spam_issue:, user:, context: {}) @@ -98,6 +94,14 @@ module Gitlab nanos: ar_timestamp.to_time.nsec) end + def stub_creds + if Rails.env.development? || Rails.env.test? + :this_channel_is_insecure + else + GRPC::Core::ChannelCredentials.new ::Gitlab::X509::Certificate.ca_certs_bundle + end + end + def grpc_client @grpc_client ||= ::Spamcheck::SpamcheckService::Stub.new(@endpoint_url, @creds, interceptors: interceptors, diff --git a/lib/gitlab/x509/certificate.rb b/lib/gitlab/x509/certificate.rb index c7289a51b49..81a50433be2 100644 --- a/lib/gitlab/x509/certificate.rb +++ b/lib/gitlab/x509/certificate.rb @@ -33,6 +33,26 @@ module Gitlab from_strings(File.read(key_path), File.read(cert_path), ca_certs_string) end + # Returns all top-level, readable files in the default CA cert directory + def self.ca_certs_paths + cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"].select do |path| + !File.directory?(path) && File.readable?(path) + end + cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE + cert_paths + end + + # Returns a concatenated array of Strings, each being a PEM-coded CA certificate. + def self.ca_certs_bundle + return @certs if @certs + + @certs = ca_certs_paths.flat_map do |cert_file| + load_ca_certs_bundle(File.read(cert_file)) + rescue OpenSSL::OpenSSLError => e + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, cert_file: cert_file) + end.uniq.join("\n") + end + # Returns an array of OpenSSL::X509::Certificate objects, empty array if none found # # Ruby OpenSSL::X509::Certificate.new will only load the first diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb index 6439c97d0bc..2411ca8263a 100644 --- a/lib/sidebars/projects/menus/settings_menu.rb +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -144,10 +144,6 @@ module Sidebars end def usage_quotas_menu_item - unless Feature.enabled?(:project_storage_ui, context.project&.group, default_enabled: :yaml) - return ::Sidebars::NilMenuItem.new(item_id: :usage_quotas) - end - ::Sidebars::MenuItem.new( title: s_('UsageQuota|Usage Quotas'), link: project_usage_quotas_path(context.project), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index af96e7b092e..8a8fec10953 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1843,6 +1843,9 @@ msgstr "" msgid "Activate Service Desk" msgstr "" +msgid "Activated on" +msgstr "" + msgid "Active" msgstr "" @@ -13860,6 +13863,9 @@ msgstr "" msgid "Expires in %{expires_at}" msgstr "" +msgid "Expires on" +msgstr "" + msgid "Expires:" msgstr "" @@ -19976,6 +19982,9 @@ msgstr "" msgid "Last Seen" msgstr "" +msgid "Last Sync" +msgstr "" + msgid "Last Used" msgstr "" @@ -25484,6 +25493,9 @@ msgstr "" msgid "Plain-text response to send to clients that hit a rate limit" msgstr "" +msgid "Plan" +msgstr "" + msgid "Plan:" msgstr "" @@ -28593,6 +28605,9 @@ msgstr "" msgid "Renew subscription" msgstr "" +msgid "Renews" +msgstr "" + msgid "Reopen" msgstr "" @@ -30044,6 +30059,9 @@ msgstr[1] "" msgid "Searching by both author and message is currently not supported." msgstr "" +msgid "Seats" +msgstr "" + msgid "Seats usage data as of %{last_enqueue_time} (Updated daily)" msgstr "" @@ -33026,18 +33044,12 @@ msgstr "" msgid "Sunday" msgstr "" -msgid "SuperSonics|Activate" -msgstr "" - msgid "SuperSonics|Activate cloud license" msgstr "" msgid "SuperSonics|Activate subscription" msgstr "" -msgid "SuperSonics|Activated on" -msgstr "" - msgid "SuperSonics|Activation code" msgstr "" @@ -33059,9 +33071,6 @@ msgstr "" msgid "SuperSonics|Cloud licensing is now available. It's an easier way to activate instances and manage subscriptions. Read more about it in our %{blogPostLinkStart}blog post%{blogPostLinkEnd}. Activation codes are available in the %{portalLinkStart}Customers Portal%{portalLinkEnd}." msgstr "" -msgid "SuperSonics|Expires on" -msgstr "" - msgid "SuperSonics|Export license usage file" msgstr "" @@ -33074,12 +33083,6 @@ msgstr "" msgid "SuperSonics|I agree that my use of the GitLab Software is subject to the Subscription Agreement located at the %{linkStart}Terms of Service%{linkEnd}, unless otherwise agreed to in writing with GitLab." msgstr "" -msgid "SuperSonics|ID" -msgstr "" - -msgid "SuperSonics|Last Sync" -msgstr "" - msgid "SuperSonics|Learn how to %{linkStart}activate your subscription%{linkEnd}." msgstr "" @@ -33095,30 +33098,15 @@ msgstr "" msgid "SuperSonics|Paste your activation code" msgstr "" -msgid "SuperSonics|Plan" -msgstr "" - msgid "SuperSonics|Please agree to the Subscription Agreement" msgstr "" msgid "SuperSonics|Ready to get started? A GitLab plan is ideal for scaling organizations and for multi team usage." msgstr "" -msgid "SuperSonics|Renews" -msgstr "" - -msgid "SuperSonics|Seats" -msgstr "" - msgid "SuperSonics|Start free trial" msgstr "" -msgid "SuperSonics|Started" -msgstr "" - -msgid "SuperSonics|Subscription" -msgstr "" - msgid "SuperSonics|Subscription details" msgstr "" @@ -33146,9 +33134,6 @@ msgstr "" msgid "SuperSonics|To activate your subscription, connect to GitLab servers through the %{linkStart}Cloud Licensing%{linkEnd} service, a hassle-free way to manage your subscription." msgstr "" -msgid "SuperSonics|Type" -msgstr "" - msgid "SuperSonics|Upload a license file" msgstr "" @@ -33161,9 +33146,6 @@ msgstr "" msgid "SuperSonics|Users with a Guest role or those who don't belong to a Project or Group will not use a seat from your license." msgstr "" -msgid "SuperSonics|Valid From" -msgstr "" - msgid "SuperSonics|You can learn more about %{activationLinkStart}activating your subscription%{activationLinkEnd}. If you need further assistance, please %{supportLinkStart}contact GitLab Support%{supportLinkEnd}." msgstr "" @@ -37487,6 +37469,9 @@ msgstr "" msgid "Using the %{codeStart}needs%{codeEnd} keyword makes jobs run before their stage is reached. Jobs run as soon as their %{codeStart}needs%{codeEnd} relationships are met, which speeds up your pipelines." msgstr "" +msgid "Valid From" +msgstr "" + msgid "Validate" msgstr "" @@ -39681,6 +39666,9 @@ msgstr "" msgid "Zentao issues" msgstr "" +msgid "Zentao user" +msgstr "" + msgid "ZentaoIntegration|An error occurred while requesting data from the ZenTao service." msgstr "" diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 5320f68b525..bcbf2f46f79 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -23,10 +23,6 @@ RSpec.describe 'Environment' do let!(:action) { } let!(:cluster) { } - before do - visit_environment(environment) - end - context 'with auto-stop' do let!(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) } @@ -52,12 +48,20 @@ RSpec.describe 'Environment' do end context 'without deployments' do + before do + visit_environment(environment) + end + it 'does not show deployments' do expect(page).to have_content('You don\'t have any deployments right now.') end end context 'with deployments' do + before do + visit_environment(environment) + end + context 'when there is no related deployable' do let(:deployment) do create(:deployment, :success, environment: environment, deployable: nil) @@ -108,6 +112,26 @@ RSpec.describe 'Environment' do end end + context 'with many deployments' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + + let!(:second) { create(:deployment, environment: environment, deployable: build, status: :success, finished_at: Time.current) } + let!(:first) { create(:deployment, environment: environment, deployable: build, status: :running) } + let!(:last) { create(:deployment, environment: environment, deployable: build, status: :success, finished_at: 2.days.ago) } + let!(:third) { create(:deployment, environment: environment, deployable: build, status: :canceled, finished_at: 1.day.ago) } + + before do + visit_environment(environment) + end + + it 'shows all of them in ordered way' do + ids = find_all('[data-testid="deployment-id"]').map { |e| e.text } + expected_ordered_ids = [first, second, third, last].map { |d| "##{d.iid}" } + expect(ids).to eq(expected_ordered_ids) + end + end + context 'with related deployable present' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } @@ -116,6 +140,10 @@ RSpec.describe 'Environment' do create(:deployment, :success, environment: environment, deployable: build) end + before do + visit_environment(environment) + end + it 'does show build name' do expect(page).to have_link("#{build.name} (##{build.id})") end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 16f75691288..ba4ea1069d8 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -5,14 +5,6 @@ require 'spec_helper' # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want # those stubs while testing the GitalyClient itself. RSpec.describe Gitlab::GitalyClient do - let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s } - - before do - allow(described_class) - .to receive(:stub_cert_paths) - .and_return([sample_cert]) - end - def stub_repos_storages(address) allow(Gitlab.config.repositories).to receive(:storages).and_return({ 'default' => { 'gitaly_address' => address } @@ -142,21 +134,6 @@ RSpec.describe Gitlab::GitalyClient do end end - describe '.stub_certs' do - it 'skips certificates if OpenSSLError is raised and report it' do - expect(Gitlab::ErrorTracking) - .to receive(:track_and_raise_for_dev_exception) - .with( - a_kind_of(OpenSSL::X509::CertificateError), - cert_file: a_kind_of(String)).at_least(:once) - - expect(OpenSSL::X509::Certificate) - .to receive(:new) - .and_raise(OpenSSL::X509::CertificateError).at_least(:once) - - expect(described_class.stub_certs).to be_a(String) - end - end describe '.stub_creds' do it 'returns :this_channel_is_insecure if unix' do address = 'unix:/tmp/gitaly.sock' diff --git a/spec/lib/gitlab/x509/certificate_spec.rb b/spec/lib/gitlab/x509/certificate_spec.rb index a5b192dd051..4e1a269eecf 100644 --- a/spec/lib/gitlab/x509/certificate_spec.rb +++ b/spec/lib/gitlab/x509/certificate_spec.rb @@ -5,6 +5,9 @@ require 'spec_helper' RSpec.describe Gitlab::X509::Certificate do include SmimeHelper + let(:sample_ca_certs_path) { Rails.root.join('spec/fixtures/clusters').to_s } + let(:sample_cert) { Rails.root.join('spec/fixtures/x509_certificate.crt').to_s } + # cert generation is an expensive operation and they are used read-only, # so we share them as instance variables in all tests before :context do @@ -13,6 +16,11 @@ RSpec.describe Gitlab::X509::Certificate do @cert = generate_cert(signer_ca: @intermediate_ca) end + before do + stub_const("OpenSSL::X509::DEFAULT_CERT_DIR", sample_ca_certs_path) + stub_const("OpenSSL::X509::DEFAULT_CERT_FILE", sample_cert) + end + describe 'testing environment setup' do describe 'generate_root' do subject { @root_ca } @@ -103,6 +111,43 @@ RSpec.describe Gitlab::X509::Certificate do end end + describe '.ca_certs_paths' do + it 'returns all files specified by OpenSSL defaults' do + cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"] + + expect(described_class.ca_certs_paths).to match_array(cert_paths + [sample_cert]) + end + end + + describe '.ca_certs_bundle' do + it 'skips certificates if OpenSSLError is raised and report it' do + expect(Gitlab::ErrorTracking) + .to receive(:track_and_raise_for_dev_exception) + .with( + a_kind_of(OpenSSL::X509::CertificateError), + cert_file: a_kind_of(String)).at_least(:once) + + expect(OpenSSL::X509::Certificate) + .to receive(:new) + .and_raise(OpenSSL::X509::CertificateError).at_least(:once) + + expect(described_class.ca_certs_bundle).to be_a(String) + end + + it 'returns a list certificates as strings' do + expect(described_class.ca_certs_bundle).to be_a(String) + end + end + + describe '.load_ca_certs_bundle' do + it 'loads a PEM-encoded certificate bundle into an OpenSSL::X509::Certificate array' do + ca_certs_string = described_class.ca_certs_bundle + ca_certs = described_class.load_ca_certs_bundle(ca_certs_string) + + expect(ca_certs).to all(be_an(OpenSSL::X509::Certificate)) + end + end + def common_cert_tests(parsed_cert, cert, signer_ca, with_ca_certs: nil) expect(parsed_cert.cert).to be_a(OpenSSL::X509::Certificate) expect(parsed_cert.cert.subject).to eq(cert[:cert].subject) diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb index 3079c781d73..1e5d41dfec4 100644 --- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb @@ -162,24 +162,10 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu do describe 'Usage Quotas' do let(:item_id) { :usage_quotas } - describe 'with project_storage_ui feature flag enabled' do - before do - stub_feature_flags(project_storage_ui: true) - end + specify { is_expected.not_to be_nil } - specify { is_expected.not_to be_nil } - - describe 'when the user does not have access' do - let(:user) { nil } - - specify { is_expected.to be_nil } - end - end - - describe 'with project_storage_ui feature flag disabled' do - before do - stub_feature_flags(project_storage_ui: false) - end + describe 'when the user does not have access' do + let(:user) { nil } specify { is_expected.to be_nil } end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index f9a05fbb06f..d3161db3f02 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -456,6 +456,17 @@ RSpec.describe Deployment do end end + describe '.ordered' do + let!(:deployment1) { create(:deployment, status: :running) } + let!(:deployment2) { create(:deployment, status: :success, finished_at: Time.current) } + let!(:deployment3) { create(:deployment, status: :canceled, finished_at: 1.day.ago) } + let!(:deployment4) { create(:deployment, status: :success, finished_at: 2.days.ago) } + + it 'sorts by finished at' do + expect(described_class.ordered).to eq([deployment1, deployment2, deployment3, deployment4]) + end + end + describe 'visible' do subject { described_class.visible } diff --git a/spec/requests/projects/usage_quotas_spec.rb b/spec/requests/projects/usage_quotas_spec.rb index 04e01da61ef..114e9bd9f1e 100644 --- a/spec/requests/projects/usage_quotas_spec.rb +++ b/spec/requests/projects/usage_quotas_spec.rb @@ -22,40 +22,26 @@ RSpec.describe 'Project Usage Quotas' do end describe 'GET /:namespace/:project/usage_quotas' do - context 'with project_storage_ui feature flag enabled' do - before do - stub_feature_flags(project_storage_ui: true) - end + it 'renders usage quotas path' do + mock_storage_app_data = { + project_path: project.full_path, + usage_quotas_help_page_path: help_page_path('user/usage_quotas'), + build_artifacts_help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'when-job-artifacts-are-deleted'), + packages_help_page_path: help_page_path('user/packages/package_registry/index.md', anchor: 'delete-a-package'), + repository_help_page_path: help_page_path('user/project/repository/reducing_the_repo_size_using_git'), + snippets_help_page_path: help_page_path('user/snippets', anchor: 'reduce-snippets-repository-size'), + wiki_help_page_path: help_page_path('administration/wikis/index.md', anchor: 'reduce-wiki-repository-size') + } + get project_usage_quotas_path(project) - it 'renders usage quotas path' do - mock_storage_app_data = { - project_path: project.full_path, - usage_quotas_help_page_path: help_page_path('user/usage_quotas'), - build_artifacts_help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'when-job-artifacts-are-deleted'), - packages_help_page_path: help_page_path('user/packages/package_registry/index.md', anchor: 'delete-a-package'), - repository_help_page_path: help_page_path('user/project/repository/reducing_the_repo_size_using_git'), - snippets_help_page_path: help_page_path('user/snippets', anchor: 'reduce-snippets-repository-size'), - wiki_help_page_path: help_page_path('administration/wikis/index.md', anchor: 'reduce-wiki-repository-size') - } - get project_usage_quotas_path(project) - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to include(project_usage_quotas_path(project)) - expect(assigns[:storage_app_data]).to eq(mock_storage_app_data) - expect(response.body).to include("Usage of project resources across the #{project.name} project") - end - - context 'renders :not_found for user without permission' do - let(:role) { :developer } - - it_behaves_like 'response with 404 status' - end + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to include(project_usage_quotas_path(project)) + expect(assigns[:storage_app_data]).to eq(mock_storage_app_data) + expect(response.body).to include("Usage of project resources across the #{project.name} project") end - context 'with project_storage_ui feature flag disabled' do - before do - stub_feature_flags(project_storage_ui: false) - end + context 'renders :not_found for user without permission' do + let(:role) { :developer } it_behaves_like 'response with 404 status' end diff --git a/spec/support/database/cross-join-allowlist.yml b/spec/support/database/cross-join-allowlist.yml index 54375e43833..86b2ba9333c 100644 --- a/spec/support/database/cross-join-allowlist.yml +++ b/spec/support/database/cross-join-allowlist.yml @@ -1,4 +1,3 @@ -- "./ee/spec/features/ci/ci_minutes_spec.rb" - "./ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb" - "./ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb" - "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb" diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index 2abc52fce85..bcc6abdc308 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -119,7 +119,7 @@ RSpec.shared_context 'project navbar structure' do _('Repository'), _('CI/CD'), _('Monitor'), - (s_('UsageQuota|Usage Quotas') if Feature.enabled?(:project_storage_ui, default_enabled: :yaml)) + s_('UsageQuota|Usage Quotas') ] } ].compact diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 20c5d9992be..f7da288b9f3 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -987,28 +987,10 @@ RSpec.describe 'layouts/nav/sidebar/_project' do end describe 'Usage Quotas' do - context 'with project_storage_ui feature flag enabled' do - before do - stub_feature_flags(project_storage_ui: true) - end + it 'has a link to Usage Quotas' do + render - it 'has a link to Usage Quotas' do - render - - expect(rendered).to have_link('Usage Quotas', href: project_usage_quotas_path(project)) - end - end - - context 'with project_storage_ui feature flag disabled' do - before do - stub_feature_flags(project_storage_ui: false) - end - - it 'does not have a link to Usage Quotas' do - render - - expect(rendered).not_to have_link('Usage Quotas', href: project_usage_quotas_path(project)) - end + expect(rendered).to have_link('Usage Quotas', href: project_usage_quotas_path(project)) end end end