diff --git a/.zuul.d/config.yaml b/.zuul.d/config.yaml new file mode 100644 index 000000000..4490f6de2 --- /dev/null +++ b/.zuul.d/config.yaml @@ -0,0 +1,52 @@ +--- +- project: + name: SovereignCloudStack/standards + default-branch: main + merge-mode: "squash-merge" + periodic-daily: + jobs: + - scs-check-pco-prod1 + - scs-check-pco-prod2 + - scs-check-pco-prod3 + - scs-check-regio-a + - scs-check-wavestack + periodic-hourly: + jobs: + - scs-check-gx-scs + post: + jobs: + - scs-check-gx-scs +- job: + name: scs-check-gx-scs + parent: base + secrets: + - name: clouds_conf + secret: SECRET_STANDARDS + vars: + cloud: gx-scs + run: playbooks/compliance_check.yaml +- job: + name: scs-check-pco-prod3 + parent: scs-check-gx-scs + vars: + cloud: pco-prod3 +- job: + name: scs-check-pco-prod2 + parent: scs-check-gx-scs + vars: + cloud: pco-prod2 +- job: + name: scs-check-pco-prod1 + parent: scs-check-gx-scs + vars: + cloud: pco-prod1 +- job: + name: scs-check-regio-a + parent: scs-check-gx-scs + vars: + cloud: regio-a +- job: + name: scs-check-wavestack + parent: scs-check-gx-scs + vars: + cloud: wavestack diff --git a/.zuul.d/secure.yaml b/.zuul.d/secure.yaml new file mode 100644 index 000000000..aa40fc8ad --- /dev/null +++ b/.zuul.d/secure.yaml @@ -0,0 +1,136 @@ +--- +- secret: + name: SECRET_STANDARDS + data: + gx_scs_ac_id: !encrypted/pkcs1-oaep + - b/SnLk91ZRXkFj22EjOZk+vqqJIDySsGL9WzaR8nZntJOYfhkqVbp5AV9KDuqP/bj9MrR + yDc2hdkBesm1d62ynjQ94CjP8p7Lhs45FFyOcxGWQwMairD4YtnFsvKfvYtp3mz13n0gF + HOFGycm0CZwO1cETUJFB2O9ekbI9T5iO6PmQiwWbWbT/5EJu8bjAUaLV7ZyGsCZ22FQ8E + 4dbs+2ShRsckitp7iBDWAzsPzX/aq8xuzoZ9Zf0DlHXuQrqENkx721QygsNLxb4dVC8e1 + vT8R6Oy0MBGfn667Ob1yfquileryCu+eXmFPzKZwxn2IWpl3IdSYEs0ZSFkd9ZEuA4UIE + bolgg0hXSCzxoI9M+b0+FTvNmYQw7u4tFJ6YPLhs9QJHhJVj7oywwrZRumrD0XgPzHQgW + RhrL6OS8ChvZ5yjZdRK9L+pDM2MW4kKtKzmXZ5RpoMzNoh8Mkns5YlPrikrkQsYaiq0bx + 540qoAZl+zL0SiV2Z7t8VdGwEroBDpEIrPfIboBPo9eyRbGUKRk9DqiJ+wqMhkEQ0Lu+T + whlE9WShj0BNs4mghjUg5WY9EMmH4IVFTxvbcr2UhfcnGxJJ0I+MfaAP4G8UZ0rRohYgi + i8YmbOQ7NCpyRqz0IniDODo2Cz76/NJ5e53nbTuGNBuoFoigwyGqMvI4vkN2Oc= + gx_scs_ac_secret: !encrypted/pkcs1-oaep + - ZJiujVLXDgWrPSOU6759BE23gzBGtT33c4ziUDNNFpZzW1Q8PwrrtMCBHbbAey1b8qF88 + Yp8wk4EMLMNxB9SD+E1wVBAtHRubzFwOj4/4R1kza430BydqHr+8RE3kTVukj8Wv1B2ZC + SI1gaKJHhvLIVdmiDncI6MKf4AXC0pERIhg618xYNxmGjQT5vxSSZjvwflfJiFutXa0wa + 5jFNo29T3TPQH7mA5SMzM2a0AENt/Ccp6/t+UlNLG3v3M1g/P+xednVOI1tdy+CappvHY + 10u8Eln1L9ZfKKT6ebLLdoYws++numKGgzGkfyS0Ob7gOGaHC3EYyha5y//BqTkGsVmfE + 8ed+zV9fO2BpaUB1h1rlO76SgXlygw/4r+JmNIbqG4b/KEseQeGL4NwR1wOJfG1LEbdsF + xE+zhSf3a3pzjt1q4qL+F4VEzIIJWS7Vn3BZaaKLovRz0iGc8PRnrQyl6arCgtInzbemu + mLv1l8bxsHvMEdb3l6wqqNvVPjo8pgZTH+1MK/2H2Zpg62NI6b+AGkqOPYIr6U3xllT23 + ZLPVpoq1cuvY9Eh3i5OjE7AU7hGcy7cDy7lHSBNYPKv+ZERIneEciApgq6QYEEuf7v7rl + wxiW8mQgmQSsly/+7LrcBpwyv0ouHrg1VOx84dhYHEV++I0FDJGJsa/HcZ/8ZQ= + pco_prod1_ac_id: !encrypted/pkcs1-oaep + - jGScb1B/BfnuDdDnfsJoHnVRaeiTAX1fCB3eYBuUx6grQTQ2SorKWeUeVWqznfJJF0Pug + uE09n6oCwZE3hxzI2VxFA+o4wDBA3azasAs8N3vV+QyFYF5dl+5K1M0xwdkhqAyefw5n8 + EhJvrqmzFk81TA4lDIOprK++16on4BsLnc1eUErYbeUv0AuKbBcq4RYU0AFLG47DA8BTH + 9kMc+RkgAhB4+EtwTrGTITOrEVB/gfEnmbKanuopCFgmcdoa4GliHilHZBc24Ht64TfdN + HHuv8598r43SQX9B1l9ZzThWSs4VlfJEet4hAaSO9uJj2CSbRvC81Ep3UQBJBkSlE3r6g + XI+FIY5x/vtoPj7SFm89gyuwZ50rO3Gp8etJhqddyQjJUrGtZHNpS0LJ514EZ+ckDYuYG + iUd96mxVPVHukSSIfkFPhDrZLG0PhW94EuJjRi/qi9NgqHVKs0JtjwA/YI7tCVa5rq6cs + VfoZ0K9lG3r7Fh6Mzob+mZKMfaLMh73e0ZR8LwvBn/vFHmVTzaApaLJWnOepAWBLOWJyB + Hv5+vWbmohdwxaK6KuaiSENR9ssqerWjGFTeyG0dUSkW3f0N6nS2T9emIkelNLtANAwvw + 7CA6nSrj8k1DNG+nH4RfeN5r+k8cCLCvVj4p/bIJuKSe5dLW6/+ooGyv+dfUDU= + pco_prod1_ac_secret: !encrypted/pkcs1-oaep + - htWKeIuB3Lracz52Zprq+WUe1y7OLEfR8GmrDKUYbUy5CN9gp+kxx4cmSjm/31Xz3wa9I + tssK7NJR5rAIgpzyXwCJaScxek/5d9Zfa/DFw6KJsnATq+VNEtQS/wDnptb2YJ5P5ff7A + zfBnAMIQf0tpRwzcupZPng3zLkYsqfS0ZnSo5RJBL1lH0gwHKbhq7XD4Vn6uCxClPelTT + iL90bk3zJUWB8qmtjVfcmWqepiaBb45Ve7kU5U6/iqoDo6itDRCaHMMmP7NI61Wlpxp3A + La6/X8a3/wMH4KLoWFaDxbirZEsMRHmTmHyYnQtko4WyYYz4s9q/vUEbCTecIiXBRBp8a + 8GkoHO1XkrZuBsd2yFsA8fc2/d0D2gr9iMDdipsPmJbwFEtHM8KH5DmtIiXy2HRl2rO9f + HMCyUdEtH7UT7zPQDuY+jk4ExwL3SCuihVimLFQyWTqAM6r8L3MQAGNVWikxVlWokeQaX + VG+WGHBasBsBieTos/OujXFVqMDgiiyB5G6crr17AKMRldxAcgvPSTG279hdstNCNClQ9 + xE2Yf0v8vXJzM/AWOMJgFanBZ35QDPIYMH6+aMh+BXj/Q0zYpWZ+y1vg358y8OYX4Q+NM + 3AGCgXvUVG3wblRSlUWUac7enMNYxlvHXTeKNLYXwut+A1qPuYFWwE+EvjReEM= + pco_prod2_ac_id: !encrypted/pkcs1-oaep + - cjuaL7RFh1f4LPVP5nKv4eEOOIPKFVsK8vXtv390lX/MRDLxJGA/lb8xxRo77tKFOPlUA + 65jEHYyeDMDn1RP+8Fqq6lEQGo9X3986zNj9+NlaINv/pRHfcNCMltEoLVsOTuBeg+DJC + XvcoAz+D2A9FFTIOaShE6iElUOmHnroSO1v6TrmOVcBdNmRYtuhL8ao7gHgHTnKdkZWPm + QjZfcME+rjftezOdRQWN6W7sLKP44miKXoglTpqxPFpko09MctP0F3YVNecoJzBtlOkMJ + dZlNhkaJeof2RCLN3xKRlwwGwhZA/YlIphE4YdzI2LXXpfHi+kD0DOx3jEixSdMJRdNdn + iHzdMyezpXzfZSGPhFVeCMb/lpVHsVGKn8sRfaH65WQX80t/vDP88k8biRbdg8eYbVHJw + Wtj4xjJbJyGG/Cc1VjBVUxP3YiVRHUGbcjOMytnDr94giMjz05fdOK//N0mmy3qgrRJIX + SYX40oU1gEMv/b1PyX3dsf7a7p4x8LVyvzUG+Kkr4+duVsqqJFJw9ejXSjhyf64oy2uhc + +GsHE6bmIraY+YTVmAI1l4T0g/bxL4ssuyZfyskJEnQrF9JEj1SBbW7JDZrqwYC6BqnSm + BDPhlbgxEfsNhxHk526f/rgG9IQIw4f0ntB+JTbQnAorNoN16IJHV3H7xVFTbc= + pco_prod2_ac_secret: !encrypted/pkcs1-oaep + - f5zoAfFxqVILvjPZWpOgAtpPFPexwPQg6wZMBAArsSY9fCaRSgh4/KyHhtojVf2kgXI2l + 3We0O3HrwHrqItEWXsxGxYt5URRmQQIYbrfSQC5qMz5UdVPKZTK7jnk/ydsLlweeOZdCY + kEVrnmBUqJrvpYRMC2D1CCsXvoBaMLdEU1skPOesa1MlWT2T6bvl8tX+YW6lqEd823idX + m41SwDL/k7eRwqjdcUBs5T9C93j04vqgFB5vLCe7hoJfvdW0B1oHFeWGyz1NpDXgZGM2P + LToMIMCQ2KsRpmk8/gam1GocIce8gbUtOurppvBZFTH0ySnCgzqjf0MHVuxVxfZWjbmzl + 5cqAcEslommZfDMwv6nASvPSH8EJL6wVuCj9QHqcgbILWO/z8VRB7g/yej1ZOjjBdw7Em + TxTcc1Kx2GtQtXLl3GDC6ib8gvJSsEys6nxVTlr1mr3+lq1g8rNl63PXlsk255ZI2Xm+x + FokGXgiJvYxZ+FroMlIVzJKrl3KN5luPM8FAj2CGwjoX2j6V5RlAl99lkKce8KSp76HVM + ItQVFGP94wfzMxeV1MFN5lYYJvOyApDgFLh9B35/Y2n2XcBu8KytvUR0zPojH3Q8YsGXR + y8MeCclG92oucn4huTRMTFdfuUuuAVRXapUMOP7M8HP8tqeYQkQjK1ipH2RyFE= + pco_prod3_ac_id: !encrypted/pkcs1-oaep + - Sxo7/4v13s46G/j45jkBrvI7puOww3VeKwc2wIaTGw7QIABH/wpcECcUqklXePd6iOWIU + apTY8h8uRd+3HyKn5z9b60L0QuSrkE7pawwN544CpFNMq2uImvfv4b/VeLk1VrJn/zO5j + keaIf/bTtg8eAPhakcnZLhDMbjprKsM2P6UpqQf473/n3jeNNoPBGf8lzkAqa2BTsQVRL + FYN1RIVviKUuEkHTY4U4ziw5i2SzHJq061qs+fYK3G4c9UWDEwKxCQilYHU3/6DgzQwzz + 4n+nea2qUGuCc8B4q5qUbnMXVy1d9+pJZwSk3I3GkrROcsJ30C+MH1bsyHIxzZffVfjvT + NtjzX7KvnRRGhdL13HwODZOt6BYx5irN5TsEs4O4RgxaTv2tSQca3SLWW8uR6JsPXuOhl + lKK/dZPzVHWVXp+0bgRlhl/0E85eN2ivt+eIgje/S77tPSxAsxdri0gL/b2L5cT8spDqk + ZhV4VaoxEcgUhh605I09SToM+/V0fdWjIb1IySikj+jyOKTKsi11KUEA8yQKAEvnnI/Xo + vCu7SIuqy8yhPXqe7o+fzmbEsMA2ToQ779CBcxgwfHZk6iC/IFGFfXhHjgj0RzDlk+0yy + hiPzEAtU32Df/gzmo/hvVFiD2FrrI2RCou4kZjq+6xD42q1iupn8Wuzjgdne1M= + pco_prod3_ac_secret: !encrypted/pkcs1-oaep + - iYft4Sbh/hGulIyIur/cn0fJ9eAl97ah0d70/8NZecQMLoJBS9Lcfc9r4l0p/JItieGxE + tPd1qk4Zfx2a1kwIU6l49s+iveNAxxxfRrIckClk8TwwesjXhRjcjecTfDGZsUJ/ZeXa8 + jnA/xe1Aj0oscME9oNpgu1z1ad02uzxuE2YgcONcx2P5boKoWGNovtBzGgBrUleaNQBsa + TLCgooK2es19l1Xh3EEtnI56Ps3vOKOuzbCVF1LzIedeAK7o7XnbTokMz3jydOPj9ebK4 + 8BhSJBJT9PIIVSyf0F0yDoFoWXQu/cItlRWjKnB19ZkyxKMAf+dHUwOqB9JKud7pXPL6x + TuslXjxiB3EXF29Ft+4q43HKV3CGKYi4xu09lwzlxx3QdRknY4GhSy2dB9LBoBhkIikO3 + NACmZxg5z5eFZP+HQSky+uNRrnLKRBvHnc6i0QUYtVuxbprQlUgssvDTByQxQ9a8CVp+Q + riPv16TIXHG/VYteDxT4f/onB6xBBiZ7Bm7drM3nAsM+ZM3WwxUnu4luWZsZYAADV+S31 + ODoxs6vEGmQgoOCCej1a71jkLMx+xdOeRN606H6Jrqfm2BfsYa1ZxUhX1Dk1dgpcVXZOK + gJLXG3zz2PJNa/Zl0/3aqrWJS6+A9lD5XuDHDdPxKfyhwo+R2+zhzScd/bbn4E= + regio_a_ac_id: !encrypted/pkcs1-oaep + - lBlWjvJ6RA3uniS1M4etvbdUxKB9KRNWm53gL3VlPyRkA7Ic2yFcAkZEGodHWH1iqNWfN + p/3B2iFYwuZRktllbc/Ro80pkg52vHOkNkBdXLQd7ZFKG8zNJOxRt2nhDQxQPS7PzcUgo + dLklwSAbAPJohxA1atqH5qw7pfk/EKsCAW8mu3eWbZWrYAcN9faNiDkm58XOKow4t5x9w + c+rG0zmKB4nsqZh+6Bt1iesSzJcUBZIX6eFYQRhj6ev70kCHU5NNYO4y2elNxPOLwSTrx + O2b9lYff/iY95ZiKHgyninMXOuoyQHntN7nKtReoBOuwf/1fFxrxEah5BAH5KDIpZ8NoX + 5iArRHDoaL6L5oLLGs4Z6mojeypQfIHZmcaxkBvK6ZHel+cf4gCC05k1uAVS9005Qb/LH + pWJQoTF5ebCVSwU9JcBptaCss60NRkJP1bVLUuyvRTSbkbcK57BsXYnpXtCxsH3qR56QQ + nlxnrKT0IawjI0qra2QwUHjVJwmwY/U+Im00p2B2mXwJ2HCQoXD1pCcgBECeHkN3ENQeZ + SnE9w4NYo8UR1xjfMGb8xcCg5Lfj/19yxFtPTi/O6MthNmz8GYQMqcZ3x2knoEkt96SY8 + 9IjJxckOkduqBIQrL72CKk7Mk9+2Bb/9STPaMGkIFTGzVCIqtWF0uXs6EEUzWk= + regio_a_ac_secret: !encrypted/pkcs1-oaep + - r0hxDzPwGm9/Lvvp0KhXJt4sr+gISnEji91RLBt1bx+KRlsOZdZS50hO5Yp0bkWP1sWGx + ZphF6STyZmboeQn3+aeeLqhli0KfgAp9RLkQugaSIKNUm4ZF1MbBgBZ+kUrVrlI3JIFAG + su3mwmQnAeo6LvK6KdKbQsZm1/cLp9xQ0R6D24h5Y/OXAnfR7xV/Jb76/ZqSlgh65aqXE + xpYc+evJ7VcQZnqWPsNf/hC4y8tdjhdBR4T8lIi8+kBF8x7P8uFG7NKjnBFuXhXqauiih + HiFVt9oqs5diK8Ujvo+pL9K3kzwuh9v+1k3JyWw4KHeWyH6GcXvDnR0u/DpEnT8t7yACw + DRpfO/J5HLKPNKpRFD2oRx9gMQraslcyPXoA/pZzLsqbpsNzwy350cURl3fonUdA1hyyc + rnOfDsBXiS7omq79zM0/g8Fyarc7Nx0V+C39kn140LlbYfOpnjciJjmKAA5w5D0kP3SCf + VCsXjf0qBBMrzz6HP9z95Bk44fiJ3L/LkA3Iij961dYrQXbZKDrKOiX/QPwrcSrVmjmew + UbPexJFHgvTCqjadoLejSt9cUd9lVzhuzLJ8CS+CcCMbZOno6qathrd2B88riQaPNIGNu + gfkNT9R63ZzKB1qIA2n5RZi7SH9DPIUd0AwLMn2bhp3uok5pNAPP/4/1RkQiCA= + wavestack_ac_id: !encrypted/pkcs1-oaep + - NgtWt9AeOFCvfDaDtYdWAFO1oh+LVLMNi2gyK2N0IHkf5SK68DRkR8asKm10iOIaXVkN4 + riQQqirjYHzIzWS2s/dKoLIH5DTpRHZUl4n8i7sdN5lhdoxjga5+Ep+FWTG8oSWN6ZJFP + jCEhvlPc9znUjZ1xNpLdNyLoRutuSXNmLajTFxvR7SNciAGOZdxHBcCwfL6fXO6UqCBb7 + 9iHVHdSLq8EMa3I3GaZ0M4VgoHfJ8XHC1D82fIQbzgZ1I4UJX6dIV+rkFQ4nhY+xUu4Sx + opWNT264qYejkqFz7a8VIc1X+bqBO/VDyyp7d25ZR8pRjZRJtpkdBcX+Rb0CUzQ8Gb3wo + FLqGxy17EUzB2m4l9+paDOPXnp1zOrSnCnFYKfHhWAkWQAJslubgjFn8tF9Afo1lZ4R83 + veqqznWfPAtzy6Gn0WAgAjNV9VdK0lIpZ5JLLkhzrmjmhpN2dJIcJabfPabi9NwEtqAO/ + 4HDPZqmNbBYFaRGqhNd+ZwuTVEV38N4ZFv7sKDe0dM7IOjtL9zWeqeFI/raPXjyjE5W/P + +HG7rspYyh26JeiYOpNVxXbCn7IzZ48DB7keUZsiTyy6PFHEdzHRrYHxiT1LUZRrpCuLs + vPknbPwhA3KtKi7wKMvI2umXV+0zmzOk6Oq5OkIBv7aQp0eHAbpdL9TTr9vKPc= + wavestack_ac_secret: !encrypted/pkcs1-oaep + - Wd9fYe9nn6Dw4bY/WnQ6BBej8Mw59YWqBVH+zFyVmFIj6WpFpux7vLVVjujYsXIvNAcRA + t3+oky/N/AEINHP7XUczPt15P0/GJ8rAWd4L/NYN/p90hjsRE0QTHzp9GDpy+ZgyUuso6 + HGUfDiykoWrSe/9Vl0BwqZJLzrOjBeK1HOccO8GvVkyRYk5WI2OksVtiQxKbcrffS/BJK + 9vU+VlphqiRVPGe8cyAjtoLwm1HH6FQ5VqjXYi4R2uhgTJM66/ueRky2GFCuFThxUGLQl + YBA0+Kt/2W7ejtlII/7KA5Gy7Zqw5/lM6quVJzJE+jarMj/YbZnNbpUUQws9z88ZfMA0Q + Siix+3irhIwTWDgeZyO2G20pPLGoahj4LWDzi4xFeOY1w0xXB0vkNDpGY59BRBn/oJ5zW + Pap+BdSbstyqHpJDkwteplJxbh5hN510opGFw4D6PbtoYKlsXfD1GPprHB0tnTPot9IgL + wzWYpJQQ6gehh3OTs5i9fLetoaQ59HF01SVDQL44VKT3xi4P5Wr9u+hLCbGDeu+OMPNGM + UYLHSWwKv893lov4K6/e4jz00EikV17kd+tVW1qMKMRFxdrRgwKP+wr50do2zVxhBMu4H + Co5I7CeK4GeN3KURGCKfcm/IRm+8A9j1+ocJelQf4T5DZtUlLrkGBe2SGw/V2I= diff --git a/Tests/iaas/flavor-naming/flavor-names-openstack.py b/Tests/iaas/flavor-naming/flavor-names-openstack.py index 8b512eafc..8e7fda00a 100755 --- a/Tests/iaas/flavor-naming/flavor-names-openstack.py +++ b/Tests/iaas/flavor-naming/flavor-names-openstack.py @@ -58,7 +58,7 @@ def main(argv): ("os-cloud=", "mand=", "verbose", "help", "quiet", "v2plus", "v3", "v1prefer", "accept-old-mandatory")) except getopt.GetoptError as exc: - print(f"{exc}", file=sys.stderr) + print(f"CRITICAL: {exc!r}", file=sys.stderr) usage(1) for opt in opts: if opt[0] == "-h" or opt[0] == "--help": @@ -85,13 +85,14 @@ def main(argv): else: usage(2) if len(args) > 0: - print(f"Extra arguments {str(args)}", file=sys.stderr) + print(f"CRITICAL: Extra arguments {str(args)}", file=sys.stderr) usage(1) scsMandatory, scsRecommended = fnmck.readflavors(scsMandFile, v3mode, fnmck.prefer_old) if not cloud: - print("ERROR: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr) + print("CRITICAL: You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr) + sys.exit(1) conn = openstack.connect(cloud=cloud, timeout=32) flavors = conn.compute.flavors() @@ -187,7 +188,7 @@ def main(argv): except NameError as exc: errors += 1 wrongFlv.append(flv.name) - print(f"Wrong flavor \"{flv.name}\": {exc}", file=sys.stderr) + print(f"ERROR: Wrong flavor \"{flv.name}\": {exc}", file=sys.stderr) # This makes the output more readable MSCSFlv.sort() RSCSFlv.sort() @@ -196,8 +197,9 @@ def main(argv): wrongFlv.sort() warnFlv.sort() # We have counted errors on the fly, add missing flavors to the final result - if scsMandatory: - errors += len(scsMandatory) + for fn in scsMandatory: + errors += 1 + print(f"ERROR: Missing mandatory flavor: {fn}", file=sys.stderr) # Produce dicts for YAML reporting flvSCSList = { "MandatoryFlavorsPresent": MSCSFlv, @@ -250,4 +252,10 @@ def main(argv): if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) + try: + sys.exit(main(sys.argv[1:])) + except SystemExit: + raise + except BaseException as exc: + print(f"CRITICAL: {exc!r}", file=sys.stderr) + sys.exit(1) diff --git a/Tests/iaas/image-metadata/image-md-check.py b/Tests/iaas/image-metadata/image-md-check.py index f88056c04..253d3989a 100755 --- a/Tests/iaas/image-metadata/image-md-check.py +++ b/Tests/iaas/image-metadata/image-md-check.py @@ -216,6 +216,7 @@ def main(argv): opts, args = getopt.gnu_getopt(argv[1:], "phvc:s", ("private", "help", "os-cloud=", "verbose", "skip-completeness")) except getopt.GetoptError: # as exc: + print("CRITICAL: Command-line syntax error", file=sys.stderr) usage(1) for opt in opts: if opt[0] == "-h" or opt[0] == "--help": @@ -230,18 +231,21 @@ def main(argv): cloud = opt[1] images = args if not cloud: - print("ERROR: Need to specify --os-cloud or set OS_CLOUD environment.", file=sys.stderr) + print("CRITICAL: Need to specify --os-cloud or set OS_CLOUD environment.", file=sys.stderr) usage(1) - conn = openstack.connect(cloud=cloud, timeout=24) - # Do work - if not images: - images = get_imagelist(private) - err = 0 - # Analyse image metadata - for image in images: - err += validate_imageMD(image) - if not skip: - err += report_stdimage_coverage(images) + try: + conn = openstack.connect(cloud=cloud, timeout=24) + # Do work + if not images: + images = get_imagelist(private) + err = 0 + # Analyse image metadata + for image in images: + err += validate_imageMD(image) + if not skip: + err += report_stdimage_coverage(images) + except BaseException as e: + print(f"CRITICAL: {e!r}") return err diff --git a/Tests/scs-compatible-iaas.yaml b/Tests/scs-compatible-iaas.yaml index 39ce368f0..d0287df61 100644 --- a/Tests/scs-compatible-iaas.yaml +++ b/Tests/scs-compatible-iaas.yaml @@ -1,24 +1,48 @@ name: SCS Compatible IaaS url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Tests/scs-compatible-iaas.yaml versions: - - version: v1 - stabilized_at: 2021-01-01 - obsoleted_at: 2023-10-31 + - version: v4 standards: + - name: Standard flavors + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0103-v1-standard-flavors.md + check_tools: + - executable: ./iaas/standard-flavors/flavors-openstack.py + args: "./iaas/scs-0103-v1-flavors.yaml" + - name: Standard images + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0104-v1-standard-images.md + check_tools: + - executable: ./iaas/standard-images/images-openstack.py + args: "./iaas/scs-0104-v1-images.yaml" - name: Flavor naming - url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v1-flavor-naming.md + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v3-flavor-naming.md check_tools: - executable: ./iaas/flavor-naming/flavor-names-openstack.py - args: "--v1prefer" + args: "--mand=./iaas/scs-0100-v3-flavors.yaml" - name: Image metadata url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0102-v1-image-metadata.md check_tools: - executable: ./iaas/image-metadata/image-md-check.py - args: -v - - name: OpenStack Powered Compute v2020.11 - url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2020.11.json - condition: mandatory + args: -s -v + - name: OpenStack Powered Compute v2022.11 + url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json # Unfortunately, no wrapper to run refstack yet, needs to be added + - version: v3 + stabilized_at: 2023-06-15 + obsoleted_at: 2024-04-30 + standards: + - name: Flavor naming + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v3-flavor-naming.md + check_tools: + - executable: ./iaas/flavor-naming/flavor-names-openstack.py + args: "--v3" + # Note: "--v3 --v2plus" would outlaw the v1 flavor names. Don't do this yet. + - name: Image metadata + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0102-v1-image-metadata.md + check_tools: + - executable: ./iaas/image-metadata/image-md-check.py + args: -v + - name: OpenStack Powered Compute v2022.11 + url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json - version: v2 stabilized_at: 2023-03-23 obsoleted_at: 2023-11-30 @@ -34,24 +58,19 @@ versions: args: -v - name: OpenStack Powered Compute v2022.11 url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json - condition: mandatory - # Unfortunately, no wrapper to run refstack yet, needs to be added - - version: v3 - stabilized_at: 2023-06-15 - obsoleted_at: 2024-04-30 + - version: v1 + stabilized_at: 2021-01-01 + obsoleted_at: 2023-10-31 standards: - name: Flavor naming - url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v3-flavor-naming.md + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v1-flavor-naming.md check_tools: - executable: ./iaas/flavor-naming/flavor-names-openstack.py - args: "--v3" - # Note: "--v3 --v2plus" would outlaw the v1 flavor names. Don't do this yet. + args: "--v1prefer" - name: Image metadata url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0102-v1-image-metadata.md check_tools: - executable: ./iaas/image-metadata/image-md-check.py args: -v - - name: OpenStack Powered Compute v2022.11 - url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json - condition: mandatory - # Unfortunately, no wrapper to run refstack yet, needs to be added + - name: OpenStack Powered Compute v2020.11 + url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2020.11.json diff --git a/Tests/scs-compliance-check.py b/Tests/scs-compliance-check.py index 9d1d6fc46..c80675467 100755 --- a/Tests/scs-compliance-check.py +++ b/Tests/scs-compliance-check.py @@ -23,251 +23,282 @@ """ import os +import os.path import sys +import shlex import getopt # import time import datetime import subprocess import copy +from functools import partial +from itertools import chain import yaml -def usage(): - "Output usage information" - print("Usage: scs-compliance-check.py [options] compliance-spec.yaml layer [layer [layer]]") - print("Options: -v/--verbose: More verbose output") - print(" -q/--quiet: Don't output anything but errors") - print(" -s/--single-layer: Don't perform required checks for dependant layers") - print(" -d/--date YYYY-MM-DD: Check standards valid on specified date instead of today") - print(" -V/--version VERS: Force version VERS of the standard (instead of deriving from date)") - print(" -c/--os-cloud CLOUD: Use specified cloud env (instead of OS_CLOUD env var)") - print(" -o/--output path: Generate yaml report of compliance check under given path") +# valid keywords for various parts of the spec, to be checked using `check_keywords` +KEYWORDS = { + 'spec': ('name', 'url', 'versions', 'prerequisite'), + 'version': ('version', 'standards', 'stabilized_at', 'obsoleted_at'), + 'standard': ('check_tools', 'url', 'name', 'condition'), + 'checktool': ('executable', 'args', 'condition', 'classification'), +} -def is_valid_standard(now, stable, obsolete): - "Check if now is after stable and not after obsolete" - if not stable: - return False - if now < stable: - return False - if obsolete and now > obsolete: - return False - return True +def usage(file=sys.stdout): + """Output usage information""" + print("""Usage: scs-compliance-check.py [options] compliance-spec.yaml +Options: -v/--verbose: More verbose output + -q/--quiet: Don't output anything but errors + -s/--single-scope: Don't perform required checks for prerequisite scopes + -d/--date YYYY-MM-DD: Check standards valid on specified date instead of today + -V/--version VERS: Force version VERS of the standard (instead of deriving from date) + -c/--os-cloud CLOUD: Use specified cloud env (instead of OS_CLOUD env var) + -o/--output REPORT_PATH: Generate yaml report of compliance check under given path + -C/--critical-only: Only return critical errors in return code +With -C, the return code will be nonzero precisely when the tests couldn't be run to completion. +""".strip(), file=file) -MYPATH = "." - -def add_search_path(arg0): - """Store path of scs-compliance-check.py to search path, as check tools - referenced in compliance.spec might be relative to it. - """ - global MYPATH - arg0_pidx = arg0.rfind('/') - assert arg0_pidx != -1 - MYPATH = arg0[:arg0_pidx] - # os.environ['PATH'] += ":" + MYPATH - - -def run_check_tool(executable, args, verbose=False, quiet=False): - "Run executable and return exit code" +def run_check_tool(executable, args, env=None, cwd=None): + """Run executable and return `CompletedProcess` instance""" if executable.startswith("http://") or executable.startswith("https://"): - print(f"ERROR: remote check_tool {executable} not yet supported", file=sys.stderr) # TODO: When we start supporting this, consider security concerns # Running downloaded code is always risky # - Certificate pinning for https downloads # - Refuse http # - Check sha256/512 or gpg signature - return 999999 + raise NotImplementedError(f"remote check_tool {executable} not yet supported") if executable.startswith("file://"): executable = executable[7:] - if executable[0] == "/": - exe = [executable, ] - else: - exe = [MYPATH + "/" + executable, ] - if args: - exe.extend(args.split(" ")) - # print(f"{exe}") - # compl = subprocess.run(exe, capture_output=True, text=True, check=False) - compl = subprocess.run(exe, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - encoding='UTF-8', check=False) - if verbose: - print(compl.stdout) - if not quiet: - print(compl.stderr, file=sys.stderr) - return compl.returncode + exe = [os.path.abspath(os.path.join(cwd or ".", executable)), *shlex.split(args)] + return subprocess.run( + exe, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + encoding='UTF-8', check=False, env=env, cwd=cwd, + ) def errcode_to_text(err): "translate error code to text" - if err == 0: - return "PASSED" - return f"{err} ERRORS" - - -def dictval(dct, key): - "Helper: Return dct[key] if it exists, None otherwise" - if key in dct: - return dct[key] - return None - + return f"{err} ERRORS" if err else "PASSED" -def search_version(layerdict, checkdate, forceversion=None): - "Return dict with latest matching version, None if not found" - bestdays = datetime.timedelta(999999999) # Infinity - bestversion = None - for versdict in layerdict: - # print(f'Version {versdict["version"]}') - if forceversion and forceversion == versdict["version"]: - if "stabilized_at" not in versdict: - print(f"WARNING: Forced version {forceversion} not stable", - file=sys.stderr) - return versdict - stabilized = dictval(versdict, "stabilized_at") - if is_valid_standard(checkdate, stabilized, dictval(versdict, "obsoleted_at")): - diffdays = checkdate - stabilized - if diffdays < bestdays: - bestdays = diffdays - bestversion = versdict - # print(f"Identified best version {bestversion}") - if forceversion and bestversion and not bestversion["version"] == forceversion: - print(f"Wanted version {forceversion} which was not found") - sys.exit(3) - return bestversion +class Config: + def __init__(self): + self.arg0 = None + self.verbose = False + self.quiet = False + self.os_cloud = os.environ.get("OS_CLOUD") + self.checkdate = datetime.date.today() + self.version = None + self.output = None + self.classes = ["light", "medium", "heavy"] + self.critical_only = False -def optparse(argv): - "Parse options. Return (args, verbose, quiet, checkdate, version, output)." - verbose = False - quiet = False - checkdate = datetime.date.today() - version = None - output = None - try: - opts, args = getopt.gnu_getopt(argv, "hvqd:V:sc:o:", - ("help", "verbose", "quiet", "date=", "version=", - "os-cloud=", "output=")) - except getopt.GetoptError as exc: - print(f"Option error: {exc}", file=sys.stderr) - usage() - sys.exit(1) - for opt in opts: - if opt[0] == "-h" or opt[0] == "--help": + def apply_argv(self, argv): + """Parse options. May exit the program.""" + try: + opts, args = getopt.gnu_getopt(argv, "hvqd:V:sc:o:r:C", ( + "help", "verbose", "quiet", "date=", "version=", + "os-cloud=", "output=", "resource-usage=", "critical-only" + )) + except getopt.GetoptError as exc: + print(f"Option error: {exc}", file=sys.stderr) usage() - sys.exit(0) - elif opt[0] == "-v" or opt[0] == "--verbose": - verbose = True - elif opt[0] == "-q" or opt[0] == "--quiet": - quiet = True - elif opt[0] == "-d" or opt[0] == "--date": - checkdate = datetime.date.fromisoformat(opt[1]) - elif opt[0] == "-V" or opt[0] == "--version": - version = opt[1] - elif opt[0] == "-c" or opt[0] == "--os-cloud": - os.environ["OS_CLOUD"] = opt[1] - elif opt[0] == "-o" or opt[0] == "--output": - output = opt[1] - else: - print(f"Error: Unknown argument {opt[0]}", file=sys.stderr) - if len(args) < 1: - usage() - sys.exit(1) - return (args, verbose, quiet, checkdate, version, output) + sys.exit(1) + for opt in opts: + if opt[0] == "-h" or opt[0] == "--help": + usage() + sys.exit(0) + elif opt[0] == "-v" or opt[0] == "--verbose": + self.verbose = True + elif opt[0] == "-q" or opt[0] == "--quiet": + self.quiet = True + elif opt[0] == "-d" or opt[0] == "--date": + self.checkdate = datetime.date.fromisoformat(opt[1]) + elif opt[0] == "-V" or opt[0] == "--version": + self.version = opt[1] + elif opt[0] == "-c" or opt[0] == "--os-cloud": + self.os_cloud = opt[1] + elif opt[0] == "-o" or opt[0] == "--output": + self.output = opt[1] + elif opt[0] == "-r" or opt[0] == "--resource-usage": + self.classes = [x.strip() for x in opt[1].split(",")] + elif opt[0] == "-C" or opt[0] == "--critical-only": + self.critical_only = True + else: + print(f"Error: Unknown argument {opt[0]}", file=sys.stderr) + if len(args) < 1: + usage(file=sys.stderr) + sys.exit(1) + self.arg0 = args[0] def condition_optional(cond, default=False): - """check whether condition is in dict cond + """ + check whether condition is in dict cond - If set to mandatory, return False - If set to optional, return True - If set to something else, error out - If unset, return default """ - if "condition" not in cond: - return default - if cond["condition"] == "optional": - return True - if cond["condition"] == "mandatory": - return False - print(f"ERROR in spec parsing condition: {cond['condition']}", file=sys.stderr) - return default + value = cond.get("condition") + value = {None: default, "optional": True, "mandatory": False}.get(value) + if value is None: + print(f"ERROR in spec parsing condition: {cond['condition']}", file=sys.stderr) + value = default + return value + + +def check_keywords(ctx, d): + valid = KEYWORDS[ctx] + invalid = [k for k in d if k not in valid] + if invalid: + print(f"ERROR in spec: {ctx} uses unknown keywords: {','.join(invalid)}", file=sys.stderr) + return len(invalid) -def optstr(optional): - "return 'optional ' if True, otherwise ''" - if optional: - return 'optional ' - return '' +def suppress(*args, **kwargs): + return + + +def invoke_check_tool(check, check_env, check_cwd): + """run check tool and return invokation dict to use in the report""" + try: + compl = run_check_tool(check["executable"], check.get("args", ''), env=check_env, cwd=check_cwd) + except Exception as e: + invokation = { + "rc": 127, + "stdout": [], + "stderr": [f"CRITICAL: {e!s}"], + } + else: + invokation = { + "rc": compl.returncode, + "stdout": compl.stdout.splitlines(), + "stderr": compl.stderr.splitlines(), + } + for signal in ('info', 'warning', 'error', 'critical'): + invokation[signal] = len([ + line + for line in chain(invokation["stderr"], invokation["stdout"]) + if line.lower().startswith(signal) + ]) + return invokation def main(argv): """Entry point for the checker""" - args, verbose, quiet, checkdate, version, output = optparse(argv) - with open(args[0], "r", encoding="UTF-8") as specfile: - specdict = yaml.load(specfile, Loader=yaml.SafeLoader) + config = Config() + config.apply_argv(argv) + if not config.os_cloud: + print("You need to have OS_CLOUD set or pass --os-cloud=CLOUD.", file=sys.stderr) + return 1 + printv = suppress if not config.verbose else partial(print, file=sys.stderr) + printnq = suppress if config.quiet else partial(print, file=sys.stderr) + with open(config.arg0, "r", encoding="UTF-8") as specfile: + spec = yaml.load(specfile, Loader=yaml.SafeLoader) + check_env = {'OS_CLOUD': config.os_cloud, **os.environ} + check_cwd = os.path.dirname(config.arg0) or os.getcwd() + allaborts = 0 allerrors = 0 - report = {} - if output: - for key in "name", "url": - report[key] = dictval(specdict, key) - report["os_cloud"] = os.environ["OS_CLOUD"] - # TODO: Add kubeconfig context as well - report["checked_at"] = checkdate - if "prerequisite" in specdict: + report = { + "spec": copy.deepcopy(spec), + "run": { + "argv": argv, + "os_cloud": config.os_cloud, + # TODO: Add kubeconfig context as well + "checked_at": config.checkdate, + "classes": config.classes, + "forced_version": config.version or None, + "aborts": 0, + "errors": 0, + "versions": {}, + "invokations": {}, + }, + } + check_keywords('spec', spec) + if config.version: + spec["versions"] = [vd for vd in spec["versions"] if vd["version"] == config.version] + if "prerequisite" in spec: print("WARNING: prerequisite not yet implemented!", file=sys.stderr) - bestversion = search_version(specdict["versions"], checkdate, version) - if not bestversion: - print(f"No valid standard found for {checkdate}", file=sys.stderr) - return 2 - errors = 0 - if not quiet: - print(f"Testing {specdict['name']} version {bestversion['version']}") - if "standards" not in bestversion: - print(f"WARNING: No standards defined yet for {specdict['name']} version {bestversion['version']}", - file=sys.stderr) - if output: - report[specdict['name']] = [copy.deepcopy(bestversion)] - for standard in bestversion["standards"]: - optional = condition_optional(standard) - if not quiet: - print("*******************************************************") - print(f"Testing {optstr(optional)}standard {standard['name']} ...") - print(f"Reference: {standard['url']} ...") - if "check_tools" not in standard: - print(f"WARNING: No compliance check tool implemented yet for {standard['name']}") - error = 0 - else: - chkidx = 0 - for check in standard["check_tools"]: - args = dictval(check, 'args') - error = run_check_tool(check["executable"], args, verbose, quiet) - if output: - version_index = 0 # report[layer].index(bestversion) - standard_index = bestversion["standards"].index(standard) - report[specdict['name']][version_index]["standards"][standard_index]["check_tools"][chkidx]["errors"] = error + vrs = report["run"]["versions"] + memo = report["run"]["invokations"] # memoize check tool results + matches = 0 + for vd in spec["versions"]: + check_keywords('version', vd) + stb_date = vd.get("stabilized_at") + obs_date = vd.get("obsoleted_at") + futuristic = not stb_date or config.checkdate < stb_date + outdated = obs_date and obs_date < config.checkdate + vr = vrs[vd["version"]] = { + "status": outdated and "outdated" or futuristic and "preview" or "valid", + "passed": False, + "aborts": 0, + "errors": 0, + "invokations": [], + } + if outdated and not config.version: + continue + matches += 1 + if config.version and outdated: + print(f"WARNING: Forced version {config.version} outdated", file=sys.stderr) + if config.version and futuristic: + print(f"INFO: Forced version {config.version} not (yet) stable", file=sys.stderr) + printnq(f"Testing {spec['name']} version {vd['version']}") + if "standards" not in vd: + print(f"WARNING: No standards defined yet for {spec['name']} version {vd['version']}", + file=sys.stderr) + errors = 0 + aborts = 0 + invokations = vr["invokations"] + for standard in vd.get("standards", ()): + check_keywords('standard', standard) + optional = condition_optional(standard) + printnq("*******************************************************") + printnq(f"Testing {'optional ' * optional}standard {standard['name']} ...") + printnq(f"Reference: {standard['url']} ...") + if "check_tools" not in standard: + printnq(f"WARNING: No check tool specified for {standard['name']}", file=sys.stderr) + for check in standard.get("check_tools", ()): + check_keywords('checktool', check) + if check.get("classification", "light") not in config.classes: + print(f"skipping check tool '{check['executable']}' because of resource classification") + continue + args = check.get('args', '') + memo_key = f"{check['executable']} {args}".strip() + invokation = memo.get(memo_key) + if invokation is None: + invokation = invoke_check_tool(check, check_env, check_cwd) + printv("\n".join(invokation["stdout"])) + printnq("\n".join(invokation["stderr"])) + memo[memo_key] = invokation + invokations.append(memo_key) + abort = invokation["critical"] + error = invokation["error"] + printnq(f"... returned {error} errors, {abort} aborts") if not condition_optional(check, optional): + aborts += abort errors += error - if not quiet: - print(f"... returned {error} errors") - chkidx += 1 - for kwd in check: - if kwd not in ('executable', 'args', 'condition', 'classification'): - print(f"ERROR in spec: check_tools.{kwd} is an unknown keyword", - file=sys.stderr) - for kwd in standard: - if kwd not in ('check_tools', 'url', 'name', 'condition'): - print(f"ERROR in spec: standard.{kwd} is an unknown keyword", file=sys.stderr) - if output: - report[specdict['name']][version_index]["errors"] = errors - with open(output, 'w', encoding='UTF-8') as file: - output = yaml.safe_dump(report, file, default_flow_style=False, sort_keys=False) - if not quiet: - print("*******************************************************") - print(f"Verdict for os_cloud {os.environ['OS_CLOUD']}, {specdict['name']}, " - f"version {bestversion['version']}: {errcode_to_text(errors)}") - allerrors += errors - return allerrors + vr["aborts"] = aborts + vr["errors"] = errors + vr["passed"] = not (aborts + errors) + printnq("*******************************************************") + printnq(f"Verdict for os_cloud {config.os_cloud}, {spec['name']}, " + f"version {vd['version']}: {errcode_to_text(aborts + errors)}") + allaborts += aborts + allerrors += errors + report["run"]["aborts"] = allaborts + report["run"]["errors"] = allerrors + if not matches: + print(f"CRITICAL: No valid scope found for {config.checkdate}", file=sys.stderr) + allaborts += 1 # note: this is after we put the number into the report, so only for return code + if config.output: + with open(config.output, 'w', encoding='UTF-8') as file: + yaml.safe_dump(report, file, default_flow_style=False, sort_keys=False) + return min(127, allaborts + (0 if config.critical_only else allerrors)) if __name__ == "__main__": - add_search_path(sys.argv[0]) sys.exit(main(sys.argv[1:])) diff --git a/playbooks/clouds.yaml.j2 b/playbooks/clouds.yaml.j2 new file mode 100644 index 000000000..78b329d19 --- /dev/null +++ b/playbooks/clouds.yaml.j2 @@ -0,0 +1,55 @@ +--- +clouds: + gx-scs: + auth: + auth_url: https://api.gx-scs.sovereignit.cloud:5000 + application_credential_id: "{{ clouds_conf.gx_scs_ac_id }}" + application_credential_secret: "{{ clouds_conf.gx_scs_ac_secret }}" + region_name: "RegionOne" + interface: "public" + identity_api_version: 3 + auth_type: "v3applicationcredential" + pco-prod1: + region_name: "prod1" + interface: "public" + identity_api_version: 3 + auth: + auth_url: https://prod1.api.pco.get-cloud.io:5000 + application_credential_id: "{{ clouds_conf.pco_prod1_ac_id }}" + application_credential_secret: "{{ clouds_conf.pco_prod1_ac_secret }}" + auth_type: "v3applicationcredential" + pco-prod2: + region_name: "prod2" + interface: "public" + identity_api_version: 3 + auth: + auth_url: https://prod2.api.pco.get-cloud.io:5000 + application_credential_id: "{{ clouds_conf.pco_prod2_ac_id }}" + application_credential_secret: "{{ clouds_conf.pco_prod2_ac_secret }}" + auth_type: "v3applicationcredential" + pco-prod3: + region_name: "prod3" + interface: "public" + identity_api_version: 3 + auth: + auth_url: https://prod3.api.pco.get-cloud.io:5000 + application_credential_id: "{{ clouds_conf.pco_prod3_ac_id }}" + application_credential_secret: "{{ clouds_conf.pco_prod3_ac_secret }}" + auth_type: "v3applicationcredential" + regio-a: + region_name: "RegionA" + interface: "public" + identity_api_version: 3 + auth: + auth_url: https://keystone.services.a.regiocloud.tech/v3/ + application_credential_id: "{{ clouds_conf.regio_a_ac_id }}" + application_credential_secret: "{{ clouds_conf.regio_a_ac_secret }}" + auth_type: "v3applicationcredential" + wavestack: + interface: "public" + identity_api_version: 3 + auth: + auth_url: https://api.wavestack.de:5000 + application_credential_id: "{{ clouds_conf.wavestack_ac_id }}" + application_credential_secret: "{{ clouds_conf.wavestack_ac_secret }}" + auth_type: "v3applicationcredential" diff --git a/playbooks/compliance_check.yaml b/playbooks/compliance_check.yaml new file mode 100644 index 000000000..eafce04af --- /dev/null +++ b/playbooks/compliance_check.yaml @@ -0,0 +1,57 @@ +--- +- name: Run compliance check tool + hosts: all + roles: + - role: ensure-pip # https://zuul-ci.org/docs/zuul-jobs/latest/python-roles.html#role-ensure-pip + tasks: + - name: Create cloud config dir + ansible.builtin.file: + path: "~/.config/openstack" + state: directory + recurse: true + mode: "0700" + + - name: Create cloud config file + ansible.builtin.template: + src: "clouds.yaml.j2" + dest: "~/.config/openstack/clouds.yaml" + mode: "0600" + no_log: true + + - name: Copy Tests on the node + ansible.builtin.copy: + src: "../Tests" + dest: "~/" + mode: 0500 + no_log: false + + - name: Install dependencies + ansible.builtin.pip: + requirements: /home/ubuntu/Tests/requirements.txt + + - name: Run compliance script + ansible.builtin.command: + cmd: python3 /home/ubuntu/Tests/scs-compliance-check.py /home/ubuntu/Tests/scs-compatible-iaas.yaml -c {{ cloud }} -o {{ cloud }}-iaas.yaml -C + register: result + changed_when: true + failed_when: result.rc != 0 + + - ansible.builtin.debug: + msg: "{{ result.stdout }} {{ result.stderr }}" + + - name: Copy result YAML + ansible.builtin.synchronize: + dest: "{{ zuul.executor.log_root }}/{{ cloud }}-iaas.yaml" + mode: pull + src: "{{ cloud }}-iaas.yaml" + verify_host: true + owner: no + group: no + + - name: Return artifact URL + zuul_return: + data: + zuul: + artifacts: + - name: "{{ cloud }}-iaas.yaml" + url: "{{ cloud }}-iaas.yaml"