Merge branch 'feature/kubeconfig-encrypted'
Some checks are pending
CI / checks (push) Waiting to run
Some checks are pending
CI / checks (push) Waiting to run
This fixes the new developer issue with kubeconfig not being se automatically.
This commit is contained in:
commit
8048fc6138
8 changed files with 535 additions and 42 deletions
|
|
@ -36,6 +36,12 @@ JOKER_DYNDNS_USERNAME=
|
|||
JOKER_DYNDNS_PASSWORD=
|
||||
REGISTRY_USERNAME=
|
||||
REGISTRY_PASSWORD=
|
||||
REDMINE_URL=
|
||||
REDMINE_API_KEY=
|
||||
REDMINE_PROJECT_ID=
|
||||
REDMINE_TRACKER_BUG_ID=
|
||||
REDMINE_TRACKER_SECURITY_ID=
|
||||
REDMINE_ASSIGNEE_ID=
|
||||
|
||||
# Admin bootstrap (used by seed/reset scripts)
|
||||
ADMIN_EMAIL=
|
||||
|
|
|
|||
|
|
@ -1,44 +1,50 @@
|
|||
# Encrypted with sops (age). To edit: sops creds/secrets.enc.env
|
||||
AUTH_SECRET=ENC[AES256_GCM,data:DQl4MEDrsUe76/NYdI03BZb31/NTTA0nBTUALhsULvT1EvMRRiR1,iv:rXjuUm3Z5OFMa0Jc/h8BSF1DWxddrtH2DyvPwX8KZ3c=,tag:eL7NZj0WE44y9AxVoCXrFA==,type:str]
|
||||
DATABASE_URL=ENC[AES256_GCM,data:87ysYMPY0lI8LrL46dHPjcZOYo4IlP/aQ4h+psSXS4EKzUiBJuExtF8f+kAZNJBc4Xj+B82Q34CfbbsBC8NdQoeebqvXF6/0IluvmpCBhpN2MKWqwlx/mQZTENZA3J0QiAP+BapeZXuXLc//,iv:n4W+hyWf1oH8IahJJs1XQ3d3i5hJjLXaNE+b4Z66Zbw=,tag:JYsBd5d0APkikYYg8Ea/Fg==,type:str]
|
||||
DB_HOST=ENC[AES256_GCM,data:qlmMVJEkDwfiRYGFUdP/,iv:syUEM2Cx64jE7IW8zRSqfe9uAL+JEMl9Tu5nw12kdWI=,tag:VoAJgXgGy8OXksUvn/SpUQ==,type:str]
|
||||
DB_PORT=ENC[AES256_GCM,data:vLjEIHpK,iv:ym9R0E/S5WKLKF+0thz9tmwI4pYa7skrY4sDpY42y0E=,tag:SK8gDGrnx8/YAPi8qB7KeQ==,type:str]
|
||||
DB_USER=ENC[AES256_GCM,data:KIlxtjxjx0EIDxXxHKY=,iv:dlhX4pIe6AE8fzT+TVB3hg9gJ46sq85Qp8f4pkxO9n8=,tag:WCMaOPpnI8SA8WMRDA392w==,type:str]
|
||||
DB_PASS=ENC[AES256_GCM,data:CJEh2+yQrPptlTnthe2UWsc/baNXBIAC0lpxtm8DI47gTg==,iv:UPrDDlMbWaiAiz5QcjRqXRIiVohMRF7wtmi5ioONcLE=,tag:jLSfXcefy8+cll8bVclkSQ==,type:str]
|
||||
APP_URL=ENC[AES256_GCM,data:1vZsUQ/d4MTbBM+juPJeVwseAVz4O2M=,iv:KrsOr3w1i3j3R19KAowFxFbdSm1B9IikqRoqU9tmQus=,tag:BwR3v/2R7bAFxHnB5waFIA==,type:str]
|
||||
SMTP_HOST=ENC[AES256_GCM,data:H+47VDofg1EEBo6q1UEleQ==,iv:KlAxFj/bKOMikubGbqPYkKPZt18oIdXxrLjEVnbxhN8=,tag:LpC930G7lsjt9YuSEFxW8A==,type:str]
|
||||
SMTP_PORT=ENC[AES256_GCM,data:F6fiIz4=,iv:Pelfn6ciIGvqF4nMPX5WrsWqkLtAxPBmBOSBvn4RUmo=,tag:UWBGXnDAYf5zCQJagiSE9Q==,type:str]
|
||||
SMTP_USER=ENC[AES256_GCM,data:CnbiHSRC0w/TOwwUwto=,iv:s8tJxs3X3BaFf5LrR+hHVvRIbDEfNI6ZszdTi+lg3PQ=,tag:DAbkk3szBzmhUrNTPZdA6A==,type:str]
|
||||
SMTP_PASS=ENC[AES256_GCM,data:GmmDQ2mROFz9zpGpXgtwlUvU2dmWg9KHsw==,iv:pl7O1QCjkaaqZ0sGS6kIBUKJ5glE8FTyA1vCYz0BfoU=,tag:/eE1ZWrLlKsa043VY3p3DQ==,type:str]
|
||||
SMTP_FROM=ENC[AES256_GCM,data:XY9BFPRmpO5KblsSMB2680DNx/bvUndfXA==,iv:yCUsaOMu1MrHtfR9AwfuIscyiEqsl0OYvO8UiKO6IYk=,tag:MpEIu2UJXEjE+9n1MVaHRQ==,type:str]
|
||||
SMTP_TLS=ENC[AES256_GCM,data:sBPcDhWs,iv:lZRjMBtrtAinC1blRFE0RH5jzyEcdLf+UOgEHuKitEk=,tag:R3Q4+ri3m5LB+xzohsYJHA==,type:str]
|
||||
SMTP_SSL=ENC[AES256_GCM,data:dYAo31bHTw==,iv:OG0c0HYzewLwlsgPlyn0nz+vwdeuSCjJ5ifdE9bfQvI=,tag:EJ5CX6+weXcrcjdrr1uicg==,type:str]
|
||||
SMTP_REJECT_UNAUTHORIZED=ENC[AES256_GCM,data:HhBgopyX,iv:7W9TYVN1aEH5QWK1NXGE9mw9gCd7yagg/LFVNHSdMV4=,tag:rcWGdoXH2wXtqWaImLEfHA==,type:str]
|
||||
DKIM_SELECTOR=ENC[AES256_GCM,data:taqEDa6Ah1G6yQ==,iv:n4KfE3lFBIyndBFkgvoBvzAlqP2HV4RIyL8c7lLoQSU=,tag:nos80rdqlj7YQshANBoTvw==,type:str]
|
||||
DKIM_DOMAIN=ENC[AES256_GCM,data:ZQkoRT8bQ7/Kdt0O/M4kRB8=,iv:wSbk2ALpbcxRT8neFrcQBEj8ja54ZtWeKTfllm8Pr5s=,tag:o5ZvS1enT31pV4+EM8i5LA==,type:str]
|
||||
DKIM_PRIVATE_KEY_PATH=ENC[AES256_GCM,data:8hbyhw/+I1VWGzoDNIKMY3avRvSmt17EmHiwv7WpeJZzalEXqTmDB+bpkVEQ,iv:/7bUU6boXo1HPccvNBCDnLyql0hGaMXmGIeQEqpFnwM=,tag:nTE+dR0JB/Y1ri5h/FaC3A==,type:str]
|
||||
AUTO_APPROVE_LISTINGS=ENC[AES256_GCM,data:bAHiVYhjXw==,iv:/jKFQ5L+meSNF6tn54Dc8dpK1I0+zG/o/F0QqMInmwU=,tag:9f7mb2QN17AoualTjkLeSQ==,type:str]
|
||||
OPENAI_API_KEY=ENC[AES256_GCM,data:+Avly5xD/jgkeiiHg7WNVH/OyGRf9L+iKDh/SvD7LrFuJlltORwzY253l29vD6cucaYfFanTWwDaV6TKuatNiRXxzy7aSoOfs0y4qonXofIRb3BH2/DarGWxQtRi5LRrXFIpCRQfN7x+XGbeF8ZZBW/DHFYywH0E2fU9li/iafXVOKBRcV4WYTIA3sJJuObHFjxgRYMB2SWtw8noZvkK+pzQi6lCKA==,iv:WTbvAzkjoDLAuDIdbFjt9BU5+2pGDv5Ksc4xru0Wx20=,tag:b8ddMgRoq+kMlS994yPx9A==,type:str]
|
||||
OPENAI_TRANSLATIONS_KEY=ENC[AES256_GCM,data:QGIhIhzn775yXZq9OYKTwGwcLnQAIJn8TM95TRFb5jWY/gTevspeYLDVrrGDKv/WHX8aTg4r4z9t6cbgbSX677fSlrNy2c2YEb5uCLGh0QUK2LoPt9R9yF/iyi5CU2obRlqamtF6knUGG5mdOCewKx/k+updDLmOe+CPD91H6nWbE9+1Pmbdf22bM+rOt9t4+MS6UP0UbIYU0sBEtfPTvN68260fUA==,iv:eYzIudN/i0A38X1up+Jnxv7qE5aiUEu0WQHEtQ4DvlA=,tag:qM7noB1GvXt2F69aVd0KVQ==,type:str]
|
||||
AUTH_SECRET=ENC[AES256_GCM,data:70URX0qSS/z4AYb58kAmGA/gnEt9bBGPPN5L/xpg3vsna8A9jw+X,iv:iKicMbvIaBvPXncKcmJwsbjQzjxOa7xmkm8SXeZ8RUQ=,tag:2CXRV4I5YVz52aFm65WGhQ==,type:str]
|
||||
DATABASE_URL=ENC[AES256_GCM,data:t/E/j249UXAOY9gcnb15ozJOmvDC7Ffb9RnfzRUvH9HXEqVQPlh8jM8qonygiMHmXcWYqVnqtRcFbExGYNo1h6ZRoAMcHhGfA2RqagLB8JtuDYvJzd9dsdc1z/DdwBDqWiQy2s1hzYBYTSpa,iv:/1BkhzropZ+ZHMN1aCpllI//aCa5qU4T0n765LMsvW8=,tag:6wH1Aly//CIRGAANVz7RdQ==,type:str]
|
||||
DB_HOST=ENC[AES256_GCM,data:Jvk5Zes8XH50096YHApn,iv:GDU3tu24u8lwuMLQbrv1chNLspzW8oIaPgS9ZgiZkro=,tag:vmTdIZL2E7CRkb6TYxr5eg==,type:str]
|
||||
DB_PORT=ENC[AES256_GCM,data:DXNr+ZA2,iv:9kY1pEVj9LteVs/GPnCdZrblI+8ubrqNXwiS8IW4954=,tag:YgJalc+nsdYD6m4tNayJcw==,type:str]
|
||||
DB_USER=ENC[AES256_GCM,data:K+rYqv9CQpV5p8zDXV0=,iv:TiGMbFAiyVPtbe0JmXxsilAYv4WmgTdPu4muCgSmSgY=,tag:4/kA2T3w7BUytcW0CVyFCg==,type:str]
|
||||
DB_PASS=ENC[AES256_GCM,data:w49QEtuN2mFthnn4X/DvoxwbhImYVqDUYG1Ptq4ba22P9w==,iv:cSOmskpZAi/aYpzj3+QBmVIQZfEs2itDqaMZA9eX8v8=,tag:NJrdaN6zIRE0DBpVTzDL7g==,type:str]
|
||||
APP_URL=ENC[AES256_GCM,data:xr9j3Gt/y5Uq8bHzTyTKsYtpSRg4GKU=,iv:oV68MYwwipB5tLpXXDFln2YF4hPHWiufyRm3csamssg=,tag:Bj1K3tC3LyP8ezNJG8bpIA==,type:str]
|
||||
SMTP_HOST=ENC[AES256_GCM,data:wXOHpMWXQ2/spsVqVDH07w==,iv:FHffrNhUk9sY5Ew3b9h6LBQgNCRfCu3kFMKBOYXMY/o=,tag:/PJ/UrZHHbyWYg30+C6dfw==,type:str]
|
||||
SMTP_PORT=ENC[AES256_GCM,data:6r6Zsqg=,iv:eNzU1Dk3vx7kwJu72tMZDx4r9ngHUKx1t1zAYSyFFb4=,tag:aqryLBX9xPM6DvGd5Fwl5g==,type:str]
|
||||
SMTP_USER=ENC[AES256_GCM,data:IVpG+wm7l0Kk8UfhrAo=,iv:GiQChcgXDzD/h8fBHXAeKWez3IXR4NX90NU9582hThg=,tag:b8jcQY2G2HAo3pQjc7+B0Q==,type:str]
|
||||
SMTP_PASS=ENC[AES256_GCM,data:siqtgQg2WRrRbD6yqTawLAo++u2fWvpVLg==,iv:rL5AZQ6b3PaOJhG3vGZYzXeFxm6yd+qhxNzYIN6pxQk=,tag:r0LTjC8DQ9rsLTQa4XLhAg==,type:str]
|
||||
SMTP_FROM=ENC[AES256_GCM,data:WH8AW4SVhZkeXB6cNZCfj7opSTwoqYm8TA==,iv:Hy55AgFV0VSurplLVuPPqtLcyDQr/IIvGHF6Ppl01t0=,tag:vKtXoZRFgSeMYqSL9enElQ==,type:str]
|
||||
SMTP_TLS=ENC[AES256_GCM,data:42eaSf82,iv:sMLqYMHN5hzu9wrjmH9DhHcSPK06f0ZGqv/HPMsMPo8=,tag:rxoUQyT2jEC2VjRJMJV4Sw==,type:str]
|
||||
SMTP_SSL=ENC[AES256_GCM,data:Am3rKylCqQ==,iv:P6nQdWsV1QkBYfg2oeR3JbEsJYNGvd0h/ZFza2PObY8=,tag:z5VAAD4IjNi7hMISgjsisQ==,type:str]
|
||||
SMTP_REJECT_UNAUTHORIZED=ENC[AES256_GCM,data:1wLP1f9a,iv:exUSGxwA7CZ6IuBTBuAikerCe+algCybvhW6xji6h2U=,tag:Ehf8ok6P5Bx2Ky6kzOplIQ==,type:str]
|
||||
DKIM_SELECTOR=ENC[AES256_GCM,data:gv0POpTZG/69Cw==,iv:eBeiuZPNWo6DPQ25Uupo8+1EacBGOx3FUOR9arhELV8=,tag:/6gICHQGYlO4KLeu/r0VcA==,type:str]
|
||||
DKIM_DOMAIN=ENC[AES256_GCM,data:j+C08N0l0+7sM1UFgVtST5A=,iv:YB0enFjwfhhf4wJTN61saaCJx1qnUnHrroLzrlna96E=,tag:Ni6Q31jrzhgfFNvjIRcSNA==,type:str]
|
||||
DKIM_PRIVATE_KEY_PATH=ENC[AES256_GCM,data:JAykp2gN4keLwzM962rgdJ6T9af+4WFsD5VRUBaIedf5v5v7cGJNJ+FtdrUV,iv:Ds5/3TTcbepjIMXW2YJbEKZ/aBlqk2OlEv+dIFOcQKo=,tag:Sdwnh33uMnnbPlD73rvwRw==,type:str]
|
||||
AUTO_APPROVE_LISTINGS=ENC[AES256_GCM,data:gkLX0/36sA==,iv:ma0etTesxiB49Ohgtes/2obnrvSJpmRlDZZX1gbXYt4=,tag:OFHkkH0QOEJ9PVBQTauvTg==,type:str]
|
||||
OPENAI_API_KEY=ENC[AES256_GCM,data:eVJRCNymtUQOMl5IMySoGcxGC+pVoDvIyCD6idP7CoZQFqE+y5dJ1i4LW7Y9Rb5Su8+rutwkcVu2gh3wx9XxgEfFazHKQm1lm9QaeCvKWrT/MkO26kCPAJO/vkFUp8OFnK35TG5v/RrHZZUwviV7YXEcjqvB82E59Tz6AzqlUvt1ENloK7EsK5DkkSZUdapzdMcegwqOaUUpMxZn8n/lU+pWgAp/RQ==,iv:9hLQOS5qNllbLF2Ggi5GgUENHtnM9zHBxHAnv8ppaow=,tag:+8TAn4UmxZDtQoDbeDsGDQ==,type:str]
|
||||
OPENAI_TRANSLATIONS_KEY=ENC[AES256_GCM,data:Se/eY/f00J2NLSVgvh4EMyklMHDJvnkpmzdqZl2oZWMxnLdxtpTBh8nKEfO2I9ovChdMacRseXJ2J1+oBk/1zw7xSqMastwg5EdftC4y2G8TODkPwWqDnB1t1jNpObA/gXeSxtUdE8JtM8dU4U37+KSMRjM7afdQy++YHRes1OzqBnFUdFvAwNHfi//uya0Q4CEl2unsiha8iLWaNT+skf27EgFOUQ==,iv:oBu3SPls776zktVWH7t9OwEI/p3h4WM3jWEJzluI64M=,tag:tRyJtqFkuxdMcFqEfi7RbQ==,type:str]
|
||||
HETZNER_API_TOKEN="hoRULGviS8G3OGaJ68josx00M53efhuntVM5Rfft1AOvUR0ZQTXlO6yivhGqBM5o"
|
||||
HCLOUD_TOKEN=ENC[AES256_GCM,data:0qm+oR2daCd9e0qc+2wApobRod0RsUeA2QL1kQarQW77FvPAgQ7jBkML39kBP3xsYsrdmiZuIdg63Q3xeBQ9mVq2,iv:JUjR7vGyX00TOlUCOD8M9jiB6iio4IiX73sLzW/CADA=,tag:9p0kgDD7ul8s5T0cL7vsfA==,type:str]
|
||||
HETZNER_TOKEN=ENC[AES256_GCM,data:e95QAx49/qkoBnAmyup7NBs/Xm/fkWutMIWzlAv4P1ybSJNoCyC4ZibpNTCtYtK/3Hux5ItdOBm/jhssjlX363cI,iv:4UzDqJd0mYP3IJgeMn9xz9UjsVyODZ44CDAz5PPaXbk=,tag:ZEAqueoXY4kBfe+89nA+Kg==,type:str]
|
||||
JOKER_DYNDNS_USERNAME=ENC[AES256_GCM,data:FBAh4B+vxosvxfHbrZukSNea,iv:4FexfnCieAaZ7iUSvaiATr5THpqoHaebX/QigA9Wl58=,tag:dY6lQnHTJWOZa7WhHwPELA==,type:str]
|
||||
JOKER_DYNDNS_PASSWORD=ENC[AES256_GCM,data:1neBKFvQhFGfOgdPI6EHZKKI,iv:fC1J7QjAoQMUKxx6lMO32n6zjQ5tQa/icBDn19Lih2k=,tag:W110zqgwbsrVbkZzav/F0Q==,type:str]
|
||||
REGISTRY_USERNAME=ENC[AES256_GCM,data:Mt1mk15DPH8=,iv:Hxg4fRLRZ2hKRACTIIUMSODRBOgRv5Y6KfZ8477cwc0=,tag:Ufq2jYQjYzjiB88PF0gpBA==,type:str]
|
||||
REGISTRY_PASSWORD=ENC[AES256_GCM,data:4l2oMR65+X+1,iv:cfh6Esayb6zJEqKrtfY0LAncIrPi7lj/ckcbMgTMFys=,tag:vhsiagOyGVjN7+GNPwZ8LA==,type:str]
|
||||
NETDATA_USER=ENC[AES256_GCM,data:6laH3H7JKgpU,iv:PCI8HL6S68AaBRg11YlNbK9R12G1ROMqRIpXKIlsBDQ=,tag:qL1uWrtWz5+c6f6Xbv4Low==,type:str]
|
||||
NETDATA_PASS=ENC[AES256_GCM,data:CZxY7WeoHuUNmECOiL6XLVeTt/k5PAHFyV4dVBrF,iv:Zp00OVvA5hI8c9AD4pvDB1s99yw9iOlgBV4K4nbFFVg=,tag:Y077Bp8qk/SoEOajOdvMLg==,type:str]
|
||||
NETDATA_HOST_NODE1=ENC[AES256_GCM,data:QEHB348q+BQubmvIJkH/8mie2fjb0mE=,iv:ZUNZA64cuflqQq+JS/e0wByTzny0eE/4rvaxzNN1JFw=,tag:YfmJWJCd1MQW2Q2bWferLg==,type:str]
|
||||
NETDATA_HOST_DB1=ENC[AES256_GCM,data:B8mSqI+rusKxazFcziGabYMM1i1q,iv:1hDrheKjdo7dMF1m7qOWQ9llye2glEQm79LONauLiYk=,tag:H1BVhIKfymLBzGS6CZEp3Q==,type:str]
|
||||
NETDATA_PG_USER=ENC[AES256_GCM,data:n0G+MZWEWQK9,iv:11id08TbMpc5NUqtKsoXiIFfM5GJCFStzJ5aY1AGplc=,tag:gXWcgEXko4P7qMwVLGnF1Q==,type:str]
|
||||
NETDATA_PG_PASS=ENC[AES256_GCM,data:ylpbhrcFFKC5qiyt+RRPZhCWt098tPhGWxabgsO31tR7+g==,iv:O4b5DsNfCpqlhZ3GmXMRzp2/fUpMtLnhSqmoaD4+Q04=,tag:TG7ge49hjzkWcBO93hoXuA==,type:str]
|
||||
NETDATA_PG_ROLE=ENC[AES256_GCM,data:6/ESugRIiDgBM2e8,iv:kXJOKx7fbO4k9zV2APTXCxOrGJ/93dCkmVA33v9fzrY=,tag:HwEWzVagm6cDnPychaekaw==,type:str]
|
||||
ADMIN_EMAIL=ENC[AES256_GCM,data:AuhvA1iUOnMcE7el4TSE7cbLAg==,iv:eWNxWDA978OWQO10/ywc8CpKuUhC0uTqObmD5Tefgtc=,tag:pI4SXW1ZrZPthD2qRAPrIw==,type:str]
|
||||
ADMIN_INITIAL_PASSWORD=ENC[AES256_GCM,data:WiJm5VO57/qLu8fK0rZqFDk=,iv:2SwLMvMUjwkEDZo8VRtPHfs+cabz2L+9ZQAaWyn1o9c=,tag:+TunF3tuLxYvMeJsYNQuCQ==,type:str]
|
||||
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsNlZ6dWs5VnJXa0hrekdp\nYlpzYll6NUFnTnV0Sk0zbnB2WEw5alVwT2swCm82NmlUbjdabEJ2d1pHTGhYRG1m\nZmIyUHFQcVhvZHNzRTJuZVNqYThaWGMKLS0tIGtHTEhzQnh2SFVxdlBvRlpMblVW\nOWdVY0ErK2pVSzdtckc4Y0lPRTdrdmsKv5M0ojCoW5SQhnjXY116SmjvyCtSnehg\nQqtL6jElOv4MeLASHwYLYzznU6dxkZK3OKvcLh6mu+41Pnbl8u26yw==\n-----END AGE ENCRYPTED FILE-----\n
|
||||
HCLOUD_TOKEN=ENC[AES256_GCM,data:SZMlHgX2keD98hk2VU5G3aGdla3MsaSR4BqnCM4F9fNp3DNdx9zQyiRJ8LDBi62HUPBBubj1YSnUPnJFJ5bXtv6A,iv:mVor150MZPe7jMHEBtcXcoT434bAX1t7WSTLOG/hexY=,tag:DKWHOSg61vG/7qKsMDz+uw==,type:str]
|
||||
HETZNER_TOKEN=ENC[AES256_GCM,data:oe8Kp7a/j3PRc8IUnU0SUtu6zr4nqdUYWeR/TbmYz/vRoz5egDqbqi7SXi2GC0YD/JATMNiHL1ec5tYWPnceaKNS,iv:pm3IBAQSEEMqELno/hAFZgROXDixJdKv5GqEbAqSwMk=,tag:QLU06Q1D05pWSyQkKV2q3g==,type:str]
|
||||
JOKER_DYNDNS_USERNAME=ENC[AES256_GCM,data:jQEKhX2ISB9FlwMnnrp7oZ4D,iv:9nnUG5nD7DzPAAYiVDNd4C64YTgF9BSnq0+/WajJPVA=,tag:HLYprQoKRkTtaB82mPLnbQ==,type:str]
|
||||
JOKER_DYNDNS_PASSWORD=ENC[AES256_GCM,data:LUHbGwOLq+A3CsjYJ4iu9vCn,iv:Phl3sv3hCe8wRWX3TqlyeBsqhKiu2bvJF8M6f1ESnHY=,tag:lmQIynYBEl7jVg3pG/TOAg==,type:str]
|
||||
REGISTRY_USERNAME=ENC[AES256_GCM,data:IJKWI15AROA=,iv:crKgYBH39sKioPVNw69fv9H5NCGadc6X9DebqUIyC4M=,tag:7PBRGErLYdLi+ku/+U+9UA==,type:str]
|
||||
REGISTRY_PASSWORD=ENC[AES256_GCM,data:viuSD5MpNuGc,iv:/2Rcuv2cecP8iECeUfK/NowP3Fv2p+EPyqkCJoLQZnw=,tag:BApcmBr3jR+NoQ3ZqRaI8w==,type:str]
|
||||
NETDATA_USER=ENC[AES256_GCM,data:T51yECIfawfQ,iv:9q/drpZy7C6KzscGB+Lxgu0Rsg4Ov9qgvmUt2lfYfAE=,tag:XS1rN2wy3OAizpA6Ji5FhQ==,type:str]
|
||||
NETDATA_PASS=ENC[AES256_GCM,data:5j/hxKa4+9ovvaVqJdRWOns7cY2nG+nI5f5p3nkm,iv:/59+59bmpVv0XaeJrVOcLzS6sPEi3XTf3eNiwmsZFCI=,tag:1GrKhx0Zp0IztBtNpzVmxQ==,type:str]
|
||||
NETDATA_HOST_NODE1=ENC[AES256_GCM,data:9k1sqAJV2bLEStTC2L8KZ9OryFwLhDw=,iv:VWKvsEjOXBtfYHpiNG9wBX4HqG8i3XGW9wK6uOM0qbU=,tag:qKqYvnC0InnK+f3c7O3iTg==,type:str]
|
||||
NETDATA_HOST_DB1=ENC[AES256_GCM,data:ijCscUEJoup0CHh2vMtTTGzvkMtH,iv:taAvhzY9rpIgsdyR/B30Zo405l0lKO6/BvtAILwUWKA=,tag:ogOvtm7+y7R1TW82BfMWHQ==,type:str]
|
||||
NETDATA_PG_USER=ENC[AES256_GCM,data:ss8KoNmpRKk/,iv:kTHJyi4YpE+peQmXobd5jTFYrnhOWqypbnhsr251l+o=,tag:zTxRwUoqySysstFjOhF/NA==,type:str]
|
||||
NETDATA_PG_PASS=ENC[AES256_GCM,data:NEENSnAkLmBY7LxTzoqjNtG9YCFAdvF2yOj2wkzBvKbZew==,iv:7ZJNwLldd3i0BhO3uujzflhR01qDT9gDcqVtsUOXubg=,tag:rukJnDEItfa4csPxN3tvPg==,type:str]
|
||||
NETDATA_PG_ROLE=ENC[AES256_GCM,data:luPLkyzGXTf/MuYl,iv:VZf1TZlQzNkPqrFkdZhSQ/D+q9MTgz0jW4/88+4X3rs=,tag:ngpPhPvdad5U1ia2h0VMQw==,type:str]
|
||||
ADMIN_EMAIL=ENC[AES256_GCM,data:YUW3FIusETg/uRJQaP6ZCWjL1w==,iv:ibEncZK+qUsuNBXra6CxAaQQF4Oi9K4msaiOGggad7I=,tag:Sd+9MgTRx8/WA7GusZEuFg==,type:str]
|
||||
ADMIN_INITIAL_PASSWORD=ENC[AES256_GCM,data:VXiQWltYpxkts/xLNA4p8F8=,iv:as8Uq4S05e8peTP9phv5otdbyb7uyQe5gJ/ig7VKC2E=,tag:gsXXzVtft6AQBjQgrrhfng==,type:str]
|
||||
REDMINE_API_USER="lomavuokraus-bot"
|
||||
REDMINE_API_KEY="1826930a88e3732444c55efa5ea3b9ad6a7aeaff"
|
||||
REDMINE_URL="https://redmine.halla-aho.net"
|
||||
REDMINE_PROJECT_ID="1"
|
||||
REDMINE_TRACKER_BUG_ID="1"
|
||||
REDMINE_TRACKER_SECURITY_ID="1"
|
||||
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMQURYZEQ0SzhGV3NnaE1Z\nYWxucXdSY2FiT2Z1NnRIdlJmYkplRG11NVRRCmZab2VSalB5YjA3SjRYTi80dWhC\nbzVtYTlYeGxMaURHc3NueEg1ZWVjM0EKLS0tIFh3WWhRMXJQaHBCbWlhdEZrV0Fi\neTNZclVSZ3hhcGttWk53UFRIUGZLNncKXF7cGtBMBDVuFN2Y1lpN5hrbZacBOjvI\nUdq1/P3dOSFD3ciQBslZXgRJnK/hAQuP+f1RJ7KUwB0GvLXIiDQs6g==\n-----END AGE ENCRYPTED FILE-----\n
|
||||
sops_age__list_0__map_recipient=age1hkehkc2rryjl975c2mg5cghmjr54n4wjshncl292h2eg5l394fhs4uydrh
|
||||
sops_encrypted_regex=^(AUTH_SECRET|DATABASE_URL|DB_.*|APP_URL|SMTP_.*|DKIM_.*|AUTO_APPROVE_LISTINGS|OPENAI_.*|H(ETZNER|CLOUD)_TOKEN|JOKER_DYNDNS_.*|REGISTRY_.*|NETDATA_.*|ADMIN_.*)$
|
||||
sops_lastmodified=2025-12-11T11:36:56Z
|
||||
sops_mac=ENC[AES256_GCM,data:bnDwxj/t2X+vkq1nd2Bej23GBn3hALXW6PAp4FyoAlvwajztp9U2eyF7voLQDeX1kurVBuACPExzzMnerEXOebF9l5SGcIYfvtVj9kk4I0WRCbVBt/QKgEtqYJ3l1TXrDe8ZPTj6O2rK6WW36RDExFDu3tzzvVEaHErZMjAhD1U=,iv:hBwrHOvabZEqeWHSFGvk5sHYbJiF6/3wY1JXgaevB9w=,tag:2fZQ9AQoHQJ+zte2yLpi5g==,type:str]
|
||||
sops_lastmodified=2025-12-14T12:06:30Z
|
||||
sops_mac=ENC[AES256_GCM,data:R06Pa93hx3HOijkBKdgm+MgBMVgkAoMS2wzZe55E1b5TlHLfTaa8DuqBNHkor/bPsJW584ByO0AA3DiLkeCLgo9yMulYhuizRIpzKsOI2FH5KcrTGOxj1h5Wxno0ToRoXNtgy71s2aDf7hmzLQhGetAOsmEl+HFy1DMs2dauU5Q=,iv:jh4OLLqTG+eBac6MmwR0CXN4irX+QZZbeTUcrQUOyQ8=,tag:ViE3djLoGcrbqMJgD9r3SQ==,type:str]
|
||||
sops_version=3.11.0
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<li><a href="./architecture.html">Logical Architecture</a></li>
|
||||
<li><a href="./sequences.html">Feature Sequences</a></li>
|
||||
<li><a href="./security.html">Security Testing</a></li>
|
||||
<li><a href="./redmine.html">Redmine Integration</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="card">
|
||||
|
|
|
|||
50
docs/redmine.html
Normal file
50
docs/redmine.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Redmine Integration</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Redmine Integration</h1>
|
||||
<div class="meta">File tickets automatically when tests fail and review open work by tracker.</div>
|
||||
</header>
|
||||
<main class="grid">
|
||||
<section class="card">
|
||||
<h2>Setup</h2>
|
||||
<ul>
|
||||
<li>Env vars (see <code>.env.example</code>): <code>REDMINE_URL</code>, <code>REDMINE_API_KEY</code>, <code>REDMINE_PROJECT_ID</code>, <code>REDMINE_TRACKER_BUG_ID</code>, <code>REDMINE_TRACKER_SECURITY_ID</code> (optional, falls back to bug), <code>REDMINE_ASSIGNEE_ID</code> (optional default owner).</li>
|
||||
<li>Uses the Redmine REST API with the API key for authentication.</li>
|
||||
<li>Ensure the API key is scoped to the project and can create issues.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Automatic tickets on failures</h2>
|
||||
<ul>
|
||||
<li><code>scripts/run-test-suite.sh</code> now files a Redmine issue whenever any check fails. Security-related failures (npm audit, Trivy, ZAP) use the security tracker when configured.</li>
|
||||
<li>Issues are de-duplicated by fingerprinting the suite/target + failure details; reruns of the same failing state will re-use the open ticket instead of creating a duplicate.</li>
|
||||
<li>Manual wrapper for other test commands: <code>./scripts/run-tests-with-redmine.sh npm test</code> (set <code>TEST_NAME</code> or <code>TRACKER</code> to override labels).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>CLI tools</h2>
|
||||
<ul>
|
||||
<li>List open tickets grouped by tracker: <code>REDMINE_URL=... REDMINE_API_KEY=... REDMINE_PROJECT_ID=... ./scripts/redmine-report.js list-open</code></li>
|
||||
<li>Manual issue creation from a log file: <code>./scripts/redmine-report.js create-test-issue --suite my-tests --failures-file /path/to/log --tracker bug</code></li>
|
||||
<li>Outputs go to stdout; non-zero exit code on API errors so CI can fail fast.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Notes</h2>
|
||||
<ul>
|
||||
<li>Tickets include a short fingerprint in the subject and description; keep it when editing so future runs keep de-duplicating.</li>
|
||||
<li>Summary/log paths are included in the issue body to help locate artifacts from the run.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -7,6 +7,8 @@ set -euo pipefail
|
|||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SECRETS_FILE="${SECRETS_FILE:-$ROOT_DIR/creds/secrets.env}"
|
||||
ENCRYPTED_FILE="${ENCRYPTED_FILE:-$ROOT_DIR/creds/secrets.enc.env}"
|
||||
KUBECONFIG_FILE="${KUBECONFIG_FILE:-$ROOT_DIR/creds/kubeconfig.yaml}"
|
||||
KUBECONFIG_ENC_FILE="${KUBECONFIG_ENC_FILE:-$ROOT_DIR/creds/kubeconfig.enc.yaml}"
|
||||
|
||||
ensure_decrypted() {
|
||||
if [[ -f "$SECRETS_FILE" ]]; then
|
||||
|
|
@ -24,8 +26,33 @@ ensure_decrypted() {
|
|||
}
|
||||
|
||||
ensure_decrypted || exit 0
|
||||
|
||||
echo "Loading secrets from $SECRETS_FILE"
|
||||
|
||||
set -a
|
||||
source "$SECRETS_FILE"
|
||||
set +a
|
||||
|
||||
ensure_kubeconfig() {
|
||||
# If user already set KUBECONFIG, respect it.
|
||||
if [[ -n "${KUBECONFIG:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$KUBECONFIG_FILE" ]]; then
|
||||
export KUBECONFIG="$KUBECONFIG_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$KUBECONFIG_ENC_FILE" ]]; then
|
||||
if command -v sops >/dev/null 2>&1; then
|
||||
echo "Decrypting $KUBECONFIG_ENC_FILE -> $KUBECONFIG_FILE"
|
||||
sops -d "$KUBECONFIG_ENC_FILE" >"$KUBECONFIG_FILE"
|
||||
export KUBECONFIG="$KUBECONFIG_FILE"
|
||||
else
|
||||
echo "sops not found and kubeconfig is missing. Install sops or set KUBECONFIG manually." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_kubeconfig || true
|
||||
|
|
|
|||
297
scripts/redmine-report.js
Executable file
297
scripts/redmine-report.js
Executable file
|
|
@ -0,0 +1,297 @@
|
|||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const [command, ...argv] = process.argv.slice(2);
|
||||
|
||||
const parseArgs = (args) => {
|
||||
const parsed = {};
|
||||
let i = 0;
|
||||
while (i < args.length) {
|
||||
const arg = args[i];
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.replace(/^--/, '');
|
||||
const next = args[i + 1];
|
||||
if (next && !next.startsWith('--')) {
|
||||
parsed[key] = next;
|
||||
i += 2;
|
||||
} else {
|
||||
parsed[key] = true;
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
parsed._ = parsed._ || [];
|
||||
parsed._.push(arg);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const args = parseArgs(argv);
|
||||
|
||||
const ensureEnv = (key, optional = false) => {
|
||||
const value = process.env[key];
|
||||
if (!value && !optional) {
|
||||
throw new Error(`Missing required env: ${key}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const readConfig = () => {
|
||||
const baseUrl = ensureEnv('REDMINE_URL');
|
||||
const url = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
||||
return {
|
||||
url,
|
||||
apiKey: ensureEnv('REDMINE_API_KEY'),
|
||||
projectId: ensureEnv('REDMINE_PROJECT_ID'),
|
||||
trackerBugId: ensureEnv('REDMINE_TRACKER_BUG_ID'),
|
||||
trackerSecurityId: process.env.REDMINE_TRACKER_SECURITY_ID,
|
||||
assigneeId: process.env.REDMINE_ASSIGNEE_ID,
|
||||
};
|
||||
};
|
||||
|
||||
const redmineUrl = (config, path, params = {}) => {
|
||||
const url = new URL(path.replace(/^\//, ''), config.url);
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
return url;
|
||||
};
|
||||
|
||||
const fetchJson = async (config, path, options = {}, params = {}) => {
|
||||
const url = redmineUrl(config, path, params);
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'X-Redmine-API-Key': config.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Redmine request failed (${res.status}): ${text}`);
|
||||
}
|
||||
|
||||
if (res.status === 204) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const fetchAllOpenIssues = async (config) => {
|
||||
const issues = [];
|
||||
const limit = 100;
|
||||
let offset = 0;
|
||||
let total = null;
|
||||
|
||||
while (total === null || offset < total) {
|
||||
const data = await fetchJson(
|
||||
config,
|
||||
'/issues.json',
|
||||
{},
|
||||
{
|
||||
project_id: config.projectId,
|
||||
status_id: 'open',
|
||||
limit,
|
||||
offset,
|
||||
sort: 'updated_on:desc',
|
||||
},
|
||||
);
|
||||
if (Array.isArray(data.issues)) {
|
||||
issues.push(...data.issues);
|
||||
}
|
||||
total = data.total_count ?? issues.length;
|
||||
offset += limit;
|
||||
if (!data.issues || data.issues.length === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
};
|
||||
|
||||
const findExistingIssue = async (config, fingerprint) => {
|
||||
if (!fingerprint) return null;
|
||||
const issues = await fetchAllOpenIssues(config);
|
||||
return issues.find(
|
||||
(issue) =>
|
||||
issue.subject?.includes(fingerprint) ||
|
||||
issue.description?.includes(`Fingerprint: ${fingerprint}`),
|
||||
);
|
||||
};
|
||||
|
||||
const computeFingerprint = (seed) => {
|
||||
const hash = crypto.createHash('sha1').update(seed).digest('hex');
|
||||
return hash.slice(0, 12);
|
||||
};
|
||||
|
||||
const readFailures = (opts) => {
|
||||
const failures = [];
|
||||
if (opts['failures-file']) {
|
||||
try {
|
||||
const text = fs.readFileSync(opts['failures-file'], 'utf8');
|
||||
text
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((line) => failures.push(line));
|
||||
} catch (err) {
|
||||
console.error(`Could not read failures file: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.failures) {
|
||||
failures.push(opts.failures);
|
||||
}
|
||||
|
||||
if (failures.length === 0) {
|
||||
failures.push('Test failure (no details provided)');
|
||||
}
|
||||
|
||||
return failures;
|
||||
};
|
||||
|
||||
const createIssue = async (config, payload) => {
|
||||
const body = {
|
||||
issue: {
|
||||
project_id: config.projectId,
|
||||
tracker_id: payload.trackerId,
|
||||
subject: payload.subject,
|
||||
description: payload.description,
|
||||
},
|
||||
};
|
||||
|
||||
if (config.assigneeId) {
|
||||
body.issue.assigned_to_id = config.assigneeId;
|
||||
}
|
||||
|
||||
const res = await fetchJson(config, '/issues.json', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return res.issue || res;
|
||||
};
|
||||
|
||||
const handleCreateTestIssue = async () => {
|
||||
const config = readConfig();
|
||||
const suite = args.suite || 'tests';
|
||||
const tracker = (args.tracker || 'bug').toLowerCase();
|
||||
const trackerId =
|
||||
tracker === 'security'
|
||||
? config.trackerSecurityId || config.trackerBugId
|
||||
: config.trackerBugId;
|
||||
|
||||
if (!trackerId) {
|
||||
throw new Error('Missing tracker id. Set REDMINE_TRACKER_BUG_ID (and optionally REDMINE_TRACKER_SECURITY_ID).');
|
||||
}
|
||||
|
||||
const failCount = Number(args['fail-count'] || args.failcount || 0);
|
||||
const failures = readFailures(args);
|
||||
const fingerprintSeed =
|
||||
args['fingerprint-seed'] ||
|
||||
`${suite}|${args.target || ''}|${failCount}|${failures.join('|')}`;
|
||||
const fingerprint = args.fingerprint || computeFingerprint(fingerprintSeed);
|
||||
const subject = `[${suite}] ${failCount || failures.length} failure${failCount === 1 ? '' : 's'} (${tracker}) [${fingerprint}]`;
|
||||
|
||||
const descriptionLines = [
|
||||
`Suite: ${suite}`,
|
||||
args.run ? `Run: ${args.run}` : null,
|
||||
args.target ? `Target: ${args.target}` : null,
|
||||
`Tracker: ${tracker}`,
|
||||
`Failures (${failures.length}):`,
|
||||
...failures.map((line) => `- ${line}`),
|
||||
args['summary-file'] ? `Summary: ${args['summary-file']}` : null,
|
||||
args['failures-file'] ? `Log: ${args['failures-file']}` : null,
|
||||
`Fingerprint: ${fingerprint}`,
|
||||
].filter(Boolean);
|
||||
|
||||
const existing = await findExistingIssue(config, fingerprint);
|
||||
if (existing) {
|
||||
console.log(
|
||||
`Open issue #${existing.id} already exists for fingerprint ${fingerprint}; not creating a duplicate.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const created = await createIssue(config, {
|
||||
trackerId,
|
||||
subject,
|
||||
description: descriptionLines.join('\n'),
|
||||
});
|
||||
|
||||
console.log(`Created Redmine issue #${created.id}: ${subject}`);
|
||||
};
|
||||
|
||||
const handleListOpen = async () => {
|
||||
const config = readConfig();
|
||||
const issues = await fetchAllOpenIssues(config);
|
||||
if (!issues.length) {
|
||||
console.log('No open issues found for the configured project.');
|
||||
return;
|
||||
}
|
||||
|
||||
const groups = issues.reduce((acc, issue) => {
|
||||
const tracker = issue.tracker?.name || 'Unknown';
|
||||
acc[tracker] = acc[tracker] || [];
|
||||
acc[tracker].push(issue);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.entries(groups).forEach(([trackerName, trackerIssues]) => {
|
||||
console.log(`${trackerName} (${trackerIssues.length})`);
|
||||
trackerIssues.forEach((issue) => {
|
||||
const status = issue.status?.name ? ` [${issue.status.name}]` : '';
|
||||
const priority = issue.priority?.name ? ` (${issue.priority.name})` : '';
|
||||
console.log(`- #${issue.id}${status}${priority}: ${issue.subject}`);
|
||||
});
|
||||
console.log('');
|
||||
});
|
||||
};
|
||||
|
||||
const printHelp = () => {
|
||||
console.log(`Usage:
|
||||
redmine-report.js create-test-issue --suite <name> --run <id> --fail-count <n> --failures-file <path> [--target <url>] [--tracker bug|security] [--fingerprint <id>]
|
||||
redmine-report.js list-open
|
||||
|
||||
Env:
|
||||
REDMINE_URL Base URL, e.g. https://redmine.example.com
|
||||
REDMINE_API_KEY API key
|
||||
REDMINE_PROJECT_ID Project to file against
|
||||
REDMINE_TRACKER_BUG_ID Tracker id for bugs
|
||||
REDMINE_TRACKER_SECURITY_ID Tracker id for security issues (optional)
|
||||
REDMINE_ASSIGNEE_ID Default assignee (optional)
|
||||
`);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
switch (command) {
|
||||
case 'create-test-issue':
|
||||
await handleCreateTestIssue();
|
||||
break;
|
||||
case 'list-open':
|
||||
await handleListOpen();
|
||||
break;
|
||||
case '-h':
|
||||
case '--help':
|
||||
case undefined:
|
||||
printHelp();
|
||||
process.exit(command ? 0 : 1);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown command: ${command}`);
|
||||
printHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Redmine command failed: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
|
@ -97,6 +97,49 @@ ${items:-" <li>No runs found.</li>"}
|
|||
EOF
|
||||
}
|
||||
|
||||
notify_redmine() {
|
||||
local fail_lines=()
|
||||
local failures_file="$RUN_DIR/failures.txt"
|
||||
|
||||
for row in "${SUMMARY_TEXT_ROWS[@]}"; do
|
||||
if [[ "$row" == *"FAIL"* ]]; then
|
||||
fail_lines+=("$row")
|
||||
fi
|
||||
done
|
||||
|
||||
[ "${#fail_lines[@]}" -gt 0 ] || return
|
||||
|
||||
printf "%s\n" "${fail_lines[@]}" >"$failures_file"
|
||||
|
||||
local tracker="bug"
|
||||
for row in "${fail_lines[@]}"; do
|
||||
case "$row" in
|
||||
npm\ audit:*) tracker="security" ;;
|
||||
Trivy*) tracker="security" ;;
|
||||
OWASP\ ZAP\ baseline*) tracker="security" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if command -v node >/dev/null 2>&1 && [ -x "${BASH_SOURCE%/*}/redmine-report.js" ]; then
|
||||
log "Reporting failures to Redmine (${tracker})..."
|
||||
if node "${BASH_SOURCE%/*}/redmine-report.js" create-test-issue \
|
||||
--suite "run-test-suite" \
|
||||
--run "$RUN_TS" \
|
||||
--fail-count "$FAIL_COUNT" \
|
||||
--failures-file "$failures_file" \
|
||||
--summary-file "$SUMMARY_FILE" \
|
||||
--target "$TARGET" \
|
||||
--tracker "$tracker" \
|
||||
--fingerprint-seed "${TARGET}|${fail_lines[*]}"; then
|
||||
log "Redmine notification complete."
|
||||
else
|
||||
log "Redmine notification failed or skipped (see output above)."
|
||||
fi
|
||||
else
|
||||
log "Redmine reporter not available; skipping Redmine notification."
|
||||
fi
|
||||
}
|
||||
|
||||
# 1) npm audit
|
||||
if command -v npm >/dev/null 2>&1; then
|
||||
log "Running npm audit (high)..."
|
||||
|
|
@ -193,7 +236,7 @@ cat >"$SUMMARY_FILE" <<EOF
|
|||
<tbody>
|
||||
${SUMMARY_ROWS[*]}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
|
@ -207,6 +250,10 @@ FAIL_COUNT=${FAIL_COUNT}
|
|||
SKIP_COUNT=${SKIP_COUNT}
|
||||
EOF
|
||||
|
||||
if [ "$FAIL_COUNT" -gt 0 ]; then
|
||||
notify_redmine
|
||||
fi
|
||||
|
||||
update_index
|
||||
|
||||
log "Summary:"
|
||||
|
|
|
|||
59
scripts/run-tests-with-redmine.sh
Executable file
59
scripts/run-tests-with-redmine.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Wraps any test command and files a Redmine ticket on failure.
|
||||
# Usage: ./scripts/run-tests-with-redmine.sh <command ...>
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage: $0 <command ...>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RUN_TS=$(date +"%Y%m%d-%H%M%S")
|
||||
RUN_DIR="reports/runs/manual-${RUN_TS}"
|
||||
mkdir -p "$RUN_DIR"
|
||||
|
||||
CMD=("$@")
|
||||
NAME="${TEST_NAME:-${CMD[*]}}"
|
||||
LOG_FILE="$RUN_DIR/command.log"
|
||||
|
||||
echo "[run-with-redmine] Running ${CMD[*]}..."
|
||||
set +e
|
||||
"${CMD[@]}" >"$LOG_FILE" 2>&1
|
||||
STATUS=$?
|
||||
set -e
|
||||
|
||||
if [ "$STATUS" -eq 0 ]; then
|
||||
echo "[run-with-redmine] Command succeeded. Log: $LOG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[run-with-redmine] Command failed with status ${STATUS}. Log: $LOG_FILE"
|
||||
|
||||
TAIL_SNIPPET=$(tail -n 40 "$LOG_FILE" 2>/dev/null || true)
|
||||
FAILURE_FILE="$RUN_DIR/failures.txt"
|
||||
{
|
||||
echo "Command: ${CMD[*]}"
|
||||
echo "Exit status: ${STATUS}"
|
||||
echo "--- tail ---"
|
||||
echo "${TAIL_SNIPPET}"
|
||||
} >"$FAILURE_FILE"
|
||||
|
||||
if command -v node >/dev/null 2>&1 && [ -x "${BASH_SOURCE%/*}/redmine-report.js" ]; then
|
||||
if node "${BASH_SOURCE%/*}/redmine-report.js" create-test-issue \
|
||||
--suite "${NAME}" \
|
||||
--run "$RUN_TS" \
|
||||
--fail-count 1 \
|
||||
--failures-file "$FAILURE_FILE" \
|
||||
--summary-file "$LOG_FILE" \
|
||||
--tracker "${TRACKER:-bug}" \
|
||||
--fingerprint-seed "${NAME}|${CMD[*]}|${STATUS}|${TAIL_SNIPPET}"; then
|
||||
echo "[run-with-redmine] Redmine notification complete."
|
||||
else
|
||||
echo "[run-with-redmine] Redmine notification failed or skipped."
|
||||
fi
|
||||
else
|
||||
echo "[run-with-redmine] Redmine reporter not available; skipping Redmine notification."
|
||||
fi
|
||||
|
||||
exit "$STATUS"
|
||||
Loading…
Add table
Reference in a new issue