From 76e4f5790ff8614a19e7c69a3e1de97163343308 Mon Sep 17 00:00:00 2001 From: Michael McGuinness Date: Mon, 9 Jun 2025 23:30:06 +0000 Subject: [PATCH] Merged in feature/health (pull request #163) Health Endpoint * health --- api/queryAPI/api.gen.go | 164 ++++++++++-------- api/queryAPI/health.go | 11 ++ api/queryAPI/health_test.go | 25 +++ build/Dockerfile | 3 + cmd/healthcheck/main.go | 20 +++ deployments/compose.aws.yaml | 3 + docs/README.md | 4 + internal/cognitoauth/middleware.go | 2 +- internal/cognitoauth/token_test.go | 2 + internal/serviceconfig/auth/config.go | 12 ++ internal/serviceconfig/auth/config_test.go | 9 + .../observability/prometheus/prometheus.go | 3 + internal/test/container.go | 7 +- mocks/queryapi/mock_ClientInterface.go | 73 ++++++++ .../mock_ClientWithResponsesInterface.go | 73 ++++++++ pkg/queryAPI/api.gen.go | 126 ++++++++++++++ serviceAPIs/queryAPI.yaml | 26 +++ test/queryAPI/accessory_test.go | 5 + 18 files changed, 489 insertions(+), 79 deletions(-) create mode 100644 api/queryAPI/health.go create mode 100644 api/queryAPI/health_test.go create mode 100644 cmd/healthcheck/main.go diff --git a/api/queryAPI/api.gen.go b/api/queryAPI/api.gen.go index 40d326f2..4c581ecb 100644 --- a/api/queryAPI/api.gen.go +++ b/api/queryAPI/api.gen.go @@ -428,6 +428,9 @@ type ServerInterface interface { // Check export state. // (GET /export/{id}) ExportState(ctx echo.Context, id ExportID) error + // Provide health endpoint + // (GET /health) + IsHealthy(ctx echo.Context) error // Get the home page menu // (GET /home) GetHomePage(ctx echo.Context) error @@ -653,6 +656,15 @@ func (w *ServerInterfaceWrapper) ExportState(ctx echo.Context) error { return err } +// IsHealthy converts echo context to params. +func (w *ServerInterfaceWrapper) IsHealthy(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.IsHealthy(ctx) + return err +} + // GetHomePage converts echo context to params. func (w *ServerInterfaceWrapper) GetHomePage(ctx echo.Context) error { var err error @@ -826,6 +838,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/client/:id/status", wrapper.GetStatusByClientId) router.GET(baseURL+"/document/:id", wrapper.GetDocument) router.GET(baseURL+"/export/:id", wrapper.ExportState) + router.GET(baseURL+"/health", wrapper.IsHealthy) router.GET(baseURL+"/home", wrapper.GetHomePage) router.GET(baseURL+"/login", wrapper.Login) router.GET(baseURL+"/login-callback", wrapper.LoginCallback) @@ -841,81 +854,82 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xceW8juXL/KkTnAQF2dUs+gyDxs2d2tJhrbU/eS8aOQHWXJO52kxqSbVsz8HcPePXd", - "uix5F4gA/2F18ygWq34sFn/sH57PojmjQKXwzn94M8ABcP3vNZbwnkREqh8BCJ+TuSSMeuf6FQrVO0To", - "hPEIqxeIUGR+oDtPv/23R0ID9vjvkkTQNP/feV7DgycczUPwzr1up+MKdTtewxP+DCKseqwsc6zKRPjp", - "PdCpnHnn/V7Dm2MpgSux/vdrp3l2/7MrbH79zWt4cjFXDQnJCZ16z8/PqhbHEUg71suQAJXDq/JQb2eA", - "fP0WDa9aXsMj6ukcy5nX8CiOVLsk8Boeh28x4RB455LHkB3J3zhMvHPvX9qpqtvmrWgnHSuZrpgfR0vk", - "COz7vUiS6VzJ8uZpznitJKDf7kWOpGMlxW8x8EWdEMMrxCZIzgB9U8V2L4rrXRsMBzFnVIC2lyFVJofD", - "N5wzrh74jEqg2lXwfB4SX3tE+3ehpP2x7tBVax9ACDwF02l+0K5XJIA/AEegyqth13ltVWe2bDstqHsa", - "0gcckuAavsUg5CsOSXeLuOkXjVmw2NGIbhn7gOnCjki82pCuraGgeM4okoyhCNOFG6HYwega3jVIvmhe", - "TCTwsm98jKMxcOUbAnxGA4HGMGEckOQLQqcITzGhrSwMd3udrE8YEFe+Q2W/ZxCXRHHknZ8eDzqdhhcR", - "an53EmwlVMIUuFLIc8P7QnEsZ4yT78rnXlvxjzOgKM6IsBOLenYqyqwYl5jeLKhfnoOhASa7chCBcBiy", - "R619X5IHQGJBfZEuTWPGQsBUza1tmQOWUA18c87mwCUBodZb5KuihOkpTV+pqiRYf/1xgLlO+Y+qpAFF", - "h6tfDdDqNu6TYbHx7+DLdFQrVlh4sgCn20ojgIuLi8K6f5xb91tVq3za599ZsFjaLwlepruyJupV8NFq", - "uVYYrcL86DlIyCugd7SBBm4klrEod3ozB59MlBkpWxW6lIINbEXRGEGVn3/1hh9HN//98dJreB8/3ep/", - "31xlfgw//pIZc7UA1dNw4cZt+88GlEZ35bkRyYBWz48dfHGObBP18/RlHqzjgHKGJYrwAo0V4KsqFbbk", - "YzoSFidWS+xAZTuXLA+HBfBfwAUxWFsRVoJQWkE+CwA9mJJqDNll4HiQXQbOer1+/6TX6R+fHg1OTo47", - "uUWhW14UlBRhCL5kFetV8gpFLICwrD4DmaOHdBDL9OHG+tzwjGWNNoPBCYEwWG1cTui3pvhzwwuxBCG3", - "ENNqbuSrJQD4ui1kJzbTioQnuVUTBQ9JlZfopF7Umu5LSmkUJ7PSAXO6rTZaLVFxJdRLrqtctqO1vCnX", - "t/Gqhqc3GGvYUbpjyOvSYnrSzOpR1y8TLJbzWFoFqIZb2y0NBQOu17LC5LJqiYRoQy/RVoqfhqamltOK", - "hTnHi5xUN1CRd/iMFyHDgZ5rDbY6oEKX9VO+NXRsjQJ/HWcuWdgV8w3QrVzVCkHAjlazPykcbaQS31dr", - "RSc+VuRcUh1UaORl60yt32EaaMd7wGEMCcI5kbJB4g8n0aKbeLmplgq76Hnng176s++yIe7BkXf+tdvo", - "Nfr3VdYzw2K2anzvVJm1ZrqQbirNXHb10T0nCls2hyszZybST2PrTvfs6LQTTJrj/uS0edIfnDbP8Ljb", - "7MLR0bg/6Z5CcJINheJYS1TYipTA1clzE0cR5osVQglTaqmJva72dW9Vis7twUujukp/KQjR+am8lUau", - "qrdgMYpiIRGhfhgHgDByL5+Lg4/qOrSSIPN0rIxeL1Q4FpCf6KS7qdp9R4CmjAXZzcYWi2hBc07KSr3p", - "rOYVSExCsXxhs7lVycl0Chy5vOOOUMdED6OQmRRMtWW6t2rhf5wRf6a1agX7TuZoQkJAjyQM1bZnwmJa", - "cCvRP2+3cfuibeq0e53eUafX7beV/7R88VDQdmdwmlO3rt/6Wf3ZFnQy/8fpc7v1892daqEyqFlvY2gm", - "o2ZjmAWeJZvEtfLjNWgDzUFw1G2edPt+8+wUoDk4HsBp/7TbnXT7W6BNbjzVSzsTgozDRDA1MIMybn+v", - "1BSChEBn0UdzzqYchAr5J5iEEFTu7k3Ht8ZSl1u1NWcdsFErRtmkNcSPJiR0BzP5Bt/qFyZb4bM5oDEW", - "ECBGbTRsQ2O97om1g1Qdu5mm14hQCZ2CUPJsI2ZSGaXnT2UtAA1G9cmHEAuJ1GsFsUmDuQ27etuUJILy", - "aVmVy3BZ2x0RaEL4DjssRxZVsUZ2SmrUa0J/n4VxRMvQyGhA5Boxc6ajy6SOC85G228YjQlWT6B5h2IB", - "kzhEkmlDMcaUs9nN93Sp6XY7naLpFnAuM8JGRl+J6PfLp+Uyq+ElgGMcc5JMWdJTDn1CEGIkZ1j1P9WZ", - "b+5++iETEIwIlcAfcOg1PDYHmv0dwkSOysU4mc6qntt4QwOz+a8K2t7ZeGtJ2GbD0mXrWM08DYPaEOoC", - "CUKnISC9UtZkpfNVvlDyLQZEAqCSTAhwE0ZQSeSitfFysl4u+z0R0gWRYrme0vRBsp9cC5aLMfRqaFZC", - "/RYDJ1Wed4EESIVg30yJsm6/1VVV7RZqrjUCnQ3Ky33U7a3wSydFldZNgxVDC9mU+CimRMsJT+DH1SdC", - "26dSGZ2Q6VojvjRF19qIJAmzF6RPnfZGmQlcVvXalrd966yNUfUast6qgpV7Jt1EKb9ZGlftvF4mGi6m", - "x9XzmJuI3CUBEsrDMvi5u/vR+unu7rkShEyny44ZkxAFucGq1UofN6rtWiJCae3d3FL+/DnUtWsnpy7Q", - "1zqozyrA5PioeXJ8ctw8DeCseTYY9I/wWb/fP8FbxPlGeBAyw9JYeTps5wkpI3QkhPKkObAebZY7cCnt", - "TZ22oPts78Uma6fEKMJsjqs14bbOaMJZlFNEWQEmV1ZmuoGIQ5njGSUNbLzwF0ZtuqwfnzXnZWe2qmaJ", - "BOWCql9vPn0cvfnn7fXF5e2na6/hXX76ePvmn7ejt1/ev68MenS/2x98Zu3tz156Xg4oVfuSUqnaWCFB", - "TGMzw6sNwwbjXSsCnrWOdWtOdPPEnl53cDI47R8PTlawexqeAD/mRC5ulLgO8qeUSHYRy4qYWT1VwahN", - "KcUqvkUXEf7OKLo0FdHw6nMLDRV6mojx+u3l6UnvCP36j1s0VsA158p8fItpTgQ9ppA9GvuydB/dzSUL", - "oPTwCw+9c28m5Vyct9sB878vWqpAKxZNwEI2uy2s5bLjafksajNVotdOyESapMrmdqceYaLavPB9EEKt", - "jrEA/q8CmRdmr6Ig1fsFJPo0vLpEkv0BdsM6IYbfUazsXikLVKVfIrfpLmvM+rlq+/dH6WZsDJgDf+vM", - "49d/3HpFdpWaCt0YYmOJCYUA4YkEnswhzs3zttOpnUAzorRIKYiq4RvWF6ET5khl2JfZeYgifxoTSkGI", - "/8SYgwSli5QM+oH4Mwwh+uD/Yot5DS/W6jVqVaVLxLKLz8NkN5OPwrSrok/cn4GQNkITwB/U0NRoQuKD", - "XZ6sBDG1z4Kk58fHx5aNHVz/kkhtGlXtX3weql26c32v0+q0ujq1OweK58Q79/qtTqvv6QVppu207Sfn", - "fnNWFTiYOFAgjCg8OkbOI5Em8zvn7IEEEKDAJLBbxrKNQMMgqW9PFw36gpCO8bMT+l+OF1dB/zOYp+Wr", - "jlf9rHQpG7jI7e11ujuW2TLQKmQ2762IARKxhoJJHIa7YsEOOp26Gsmo2wXyr67WXV0tx/JUlXpnqysV", - "abnPDe9oPRmzlOvsQuSdf/2RgtnX++f7hifcwZu1zJxhK1DBU6FiJMsRMy7r3atmra+0f5DgWYk1rWIi", - "XIPkBB60xwgTkvnObcYLRKSw5Pi8m/wCMuMjOaPr7Mzo0mP+eovLOPLByF5uZGp1x5n5H14tsbH85ZOv", - "1TKlRTLHd/ca0v2KIMsE7cKcrRChiTFZFFfGXwvepvIrgLfdWqwAbxUImYKrobpTsZSZYbs9yQFTd2/u", - "Zh6zS+qaeNr2szTQDZA1oYhacM1dySpjrCv+94V1nmCfgJtyW6sANxH9gLn7wNysZSRWkbXIlFG4TwC+", - "Aem6T0VaDb039ca6BxDOkitXg/AY9JFJJbF2FS4PltG8VasHWN69R9zkPWK5ExSROcjQIFcAs7KG0Oa5", - "0lO+8SJ75akKmXOHhq8DzflzyiXxcDIMNbCDQe7GIHUyVObOgk2KuBQ3JHO0K5SuzHJ8mWs+EnZn7JrG", - "ZgkY9k6thmymK+AQRSBxgCWuCJlVS1cpE7cer6M4lGSOuWxPGI+aujlN8vFZQOjU0J4MO9LWurVp3ozV", - "z4OJPmGahzqxOcGhgIYn5EKniVTDXnpBMUMHTnhVIdQxjY0KYj2gbI54TKiaxdw5R7dz0j8ZdE97gwy/", - "JD0/UI3RyosLn5xG/VhIFqGkZPbYzFlJy4w20++gc5YjwLR+Xnm6oodcPlwpI4AZuVHEWE3fVrsOZwpW", - "kTu6c3rAkNRn9YQuRYzikmZYhtobdo0kQ0ok0TvuDDN2zpmKacoLn2VIGrrknoK7PBezJrqzglr71upr", - "vWpCNGU+VUj4Jsd8Puzd9+JP1kJSFm7GpyyRuNahUnr1GhGiWFDfXepda9dubwm/zpa9eC+5PjS0Qzjs", - "3ne+e3cXv1ND2U/WVJmxiy3WTevLzF5Y34ZLaJ/Wlg3np2TEuZBwfwl+00eF0SZxyMFed26vQUG3qSns", - "dieTZVhp47V3YJaa7uUM/D+M3fox5w65ND0od+Mib7Lp3RHYp8nm7z/Vr/0zLNAYgKLkPsrBgHd0Dqrs", - "I3f/p7Vk5d/UZNOvdmmDnTGzCawD2ZhTY6rvbj+8RxHQGM3x1FxxzfBIINCUGFGJtO9YBJ9VJLnSbCU8", - "yfZMRmHeXvNSaUlspSTnqcZhJFMyZnfHurnC3nhwenRSuKfxU83n5wp9J90oKQ4GvzPErpxDZ/OqWj7W", - "DdmU0Fq7ze/5dFk0CdmjWgY4BISDr89dJcvy2mxBJUBFRlR3WDDgfqdX5TSmg2zract5k3lfe7nzy/V7", - "l++qbKTiG2AxJyu4ro6Y1vqpMilzMOClBvzcKPAnvzrSYMNzLEJHCCymV/XU2fnMrM2rbbzp4zAcY/+P", - "WmN/h2kQWlP/pFrpIVcHBTF3N62t9djEB7oGH4gLn3PcS/N1IU3EzjoHpgGCJ3+G6RQEIhZ52R9ARY23", - "XDrJS0tUmXa6pHf3sUhNz00Jgqrc0u9FLnOErxfN/8HN753m2d1dc1T96dFG6eBQB2nJULQCLm+u3yql", - "SoUoej6rZBWyiiNRI2y30xtsLOz9psiUMULLDk1TOMZWNkQqh6v1rW0PWf9Rh1n/n9AnByhFRwcazBnJ", - "HdRUAgqL648N37OpgQMVySEWmw20UZz5gI85fRFCGw0N6tZS008iUyU8KEFesJqq9nWkoMLQjCknIcTL", - "rDd0Ah5sdk/njWYKNc5vsiZ+c/cqVzKSwvxV0OwHCPSdf58TCZzg6sNvdzl1zwferpuKncZFcQSH7fUO", - "j7q/JfPrbE0T+HPb6jXo9+bK0Mbs+99seLCP06XsZdFtuPffMrL9BU6azL2KA+3+lWj3bvZrvCKB4C04", - "98ZXllPuU8fYE+jai/51dnZIxe+B+JlMfI7tWcbbjdKYydXLzZj2GcBeg2i/d5xeTrM3xHj3FZrko4L2", - "tmhyeXcLDoyx9gPx/hWI9xshalvajwW8wBkqw5Y3+kMjGpDN5wViag5L6yKY/FevCiQZEHLvrpH9ekKN", - "fxQ+l7AJT6azD2HtFw6qpDUyijg8MFZ3xY5RKl3tXro9/lCdefzMWRD7yV1gfYk5rrjC7bOo/dDV7mX7", - "KbMmrYOoqDrUqKqCas1sEGkiME+VKKcYa5pxnPBsS0We+LqNJQTftK3SQfi6baW7KNtSTvvrtmJOOjOt", - "5I84120mf6s9bS2bRHi+f/6/AAAA//+RS2POSGsAAA==", + "H4sIAAAAAAAC/+xceW8juXL/KkTnAQF2dUs+gyDxs2d2tJhrbU/eS8aOQXWXJO52kxqSbY9m4O8e8OqT", + "rcMjexeIAP9hdfMoFn/1Y7FY7O9ByJIFo0ClCE6/B3PAEXD97yWW8JYkRKofEYiQk4UkjAan+hWK1TtE", + "6JTxBKsXiFBkfqCbQL/9twdCI/bw75Ik0Db/3wRBK4CvOFnEEJwG/V7PFer3glYgwjkkWPXoLXOoyiT4", + "61ugMzkPToeDVrDAUgJXYv3v51775PZnV9j8+lvQCuRyoRoSkhM6Cx4fH1UtjhOQdqznMQEqxxf1oV7P", + "AYX6LRpfdIJWQNTTBZbzoBVQnKh2SRS0Ag5fUsIhCk4lT6E4kr9xmAanwb90c1V3zVvRzTpWMl2wME1W", + "yBHZ988iSaFzJcurrwvGGyUB/fZZ5Mg6VlL8lgJfNgkxvkBsiuQc0BdVbPeiuN41YDiIBaMCNF7GVEEO", + "x684Z1w9CBmVQLWp4MUiJqG2iO7vQkn7fdOhq9begRB4BqbT8qBdr0gAvweOQJVXw26yWl9ntmw3L6h7", + "GtN7HJPoEr6kIOQLDkl3i7jpF01YtNzRiK4Ze4fp0o5IvNiQLi1QULpgFEnGUILp0o1Q7GB0reASJF+2", + "z6YSeN023qfJBLiyDQEho5FAE5gyDkjyJaEzhGeY0E6RhvuDXtEmDIkr26FyODCMS5I0CU6PD0e9XitI", + "CDW/exm3EiphBlwp5LEVfKI4lXPGyTdlcy+t+Ic5UJQWRNgJoh6digorxjmmV0sa1udgbIjJrhxEIBzH", + "7EFrP5TkHpBY0lDkS9OEsRgwVXNrW+aAJfiJb8HZArgkINR6i0JVlDA9pfkrVZVEm68/jjA3Kf9elTSk", + "6Hj1syFa3cZtNiw2+R1CmY9qzQoLXy3B6bZyD+Ds7Kyy7h+W1v2Ob5XP+/w7i5Yr+yXRj+murolmFby3", + "Wm4URquwPHoOEsoKGBxsoYEriWUq6p1eLSAkUwUjhVWhSynawFYUzRFU2fnnYPz+7uq/358HreD9h2v9", + "76uLwo/x+18KY/YL4J+GMzdu23/RoTS6q8+NyAa0fn7s4KtzZJtonqdPi2gTA5RzLFGCl2iiCF9V8WAp", + "xPROWJ5YL7EjlaeZZH04LIL/Ai6I4VqPWwlCaQWFLAJ0b0qqMRSXgcNRcRk4GQyGw6NBb3h4fDA6Ojrs", + "lRaFfn1RUFLEMYSSedar7BVKWARxXX2GMu/u80Gs0ocb62MrMMi6244GpwTiaD24nNCvTfHHVhBjCUI+", + "QUyrubtQLQHAN22hOLGFViR8lU9qomIhufIynTSL2tB9TSmt6mR6DbCkWz9otUTVlVAvua5yHUcbWVOp", + "b2NVrUBvMDbAUb5jKOvScnrWzPpRNy8TLJWLVFoFqIY7T1saKgBu1rLi5LpqiYRkSyvRKMVfx6amltOK", + "hTnHy5JUV+CJO3zEy5jhSM+1JlvtUKHz5il/MnU8mQX+OsZcQ9gFCw3RrV3VKk7AjlazP8kdbeUS3/q1", + "ogMfa2IuuQ48GvmxdabR7jCNtOHd4ziFjOGcSEUn8buTaNnPrNxUy4VdDoLT0SD/OXTREPfgIDj93G8N", + "WsNbH3rmWMzXje+NKrPRTFfCTbWZK64+uudMYavmcG3kzHj6uW/d658cHPeiaXsynB63j4aj4/YJnvTb", + "fTg4mAyn/WOIjoquUJpqiSpbkRq5Onmu0iTBfLlGKGFKrYTYy2pf9+ZTdGkPXhvVRf5LUYiOT5VRmriq", + "wZKlKEmFRISGcRoBwsi9fKwOPmnq0EqCzNOJAr1eqHAqoDzRWXcztftOAM0Yi4qbjScsohXNOSm9etNR", + "zQuQmMRi9cJmY6uSk9kMOHJxxx2xjvEe7mJmQjB+ZLq3auF/mJNwrrVqBftGFmhKYkAPJI7VtmfKUlox", + "KzE87XZx96xr6nQHvcFBb9AfdpX9dEJxX9F2b3RcUreu3/lZ/dkWdDD/+/Fjt/PzzY1qwevUbLYxNJPR", + "sDEsEs+KTeJG8fEGtoH2KDrot4/6w7B9cgzQHh2O4Hh43O9P+8MnsE1pPP6lnQlBJnEmmBqYYRm3v1dq", + "ikFCpKPodwvOZhyEcvmnmMQQeXf3puNrg9TVqLZw1g4btWLUIa0p/m5KYncwU27wtX5hohUhWwCaYAER", + "YtR6w9Y11uue2NhJ1b6baXoDD5XQGQglz1PEzCqj/PyprgWg0V1z8CHGQiL1WlFs1mBpw67etiVJoH5a", + "5jMZLhu7IwJNCd9hh3XPwudrFKekQb3G9Q9ZnCa0To2MRkRu4DMXOjrP6jjn7O7pG0YDQf8EmncoFTBN", + "YySZBooBUwmz2+/pcuj2e70qdCs8Vxhhq6CvTPTb1dNyXtTwCsIxhjnNpizrqcQ+MQhxJ+dY9T/TkW/u", + "foYxExDdESqB3+M4aAVsAbT4O4apvKsX42Q29z23/oYmZvOfj9reWH9rhdtm3dJV61jDPI2jRhfqDAlC", + "ZzEgvVI2RKXLVT5R8iUFRCKgkkwJcONGUEnksrP1crJZLPstEdI5kWK1nvLwQbaf3IiWqz70empWQv2W", + "Aic+yztDAqRisC+mRF23X5qqqnYrNTcagY4GleU+6A/W2KWTwqd106BnaDGbkRCllGg54SuEqf9E6Omh", + "VEanZLbRiM9N0Y02IlnA7AfCp057d4UJXFX10pa3feuojVH1BrJeq4LePZNuohbfrI2rcV7PMw1Xw+Pq", + "ecqNR+6CAFnKwyr6ubn53vnp5ubRS0Km01XHjJmLgtxg1WqljxvVdi0Tobb2bo+UP38Ode3GyWly9LUO", + "mqMKMD08aB8dHh22jyM4aZ+MRsMDfDIcDo/wE/x8IzwIWcjSWHs6bOcJKRC6JIT6pDmyvtsuduBC2tsa", + "bUX3xd6rTTZOiVGE2Rz7NeG2zmjKWVJSRF0BJlZWz3QDkcaylGeUNbD1wl8ZtemyeXwWzqvObFXNWhKU", + "c6p+vfrw/u7VP68vz86vP1wGreD8w/vrV/+8vnv96e1br9Oj+336wWcRb3/20vPjhOLbl9RKNfoKGWMa", + "zIwvtnQbjHWtcXg2OtZtONEtJ/YM+qOj0fHwcHS0JrunFQgIU07k8kqJ6yh/RolkZ6n0+MzqqXJGbUgp", + "Vf4tOkvwN0bRuamIxhcfO2is2NN4jJevz4+PBgfo139co4kirgVX8AktpzkR9Jhi9mDwZdN9dDfnLILa", + "w088Dk6DuZQLcdrtRiz8tuyoAp1UtAEL2e53sJbLjqcTsqTLVIlBN0sm0kmqbGF36gkmqs2zMAQh1OqY", + "CuD/KpB5YfYqilKDX0CiD+OLcyTZH2A3rFNi8juqld0rhUBV+kfkNt0Vwayfq7Z/f5BuxiaAOfDXDh6/", + "/uM6qGZXqanQjSE2kZhQiBCeSuDZHOLSPD91OrUR6IwoLVJOomr4JuuL0ClzSWU4lMV5SJJwlhJKQYj/", + "xJiDBKWLPBn0HQnnGGL0LvzFFgtaQarVa9SqStcSy84+jrPdTNkL06aKPvBwDkJaD00Av1dDU6OJSQh2", + "ebISpNQ+i7KeHx4eOtZ3cP1LIjU0fO2ffRyrXboz/aDX6XX6OrS7AIoXJDgNhp1eZxjoBWmucdoNs3O/", + "BfM5DsYPFAgjCg8uI+eBSBP5XXB2TyKIUGQC2B2DbCPQOMrq29NFw74gpMv42Un6XykvzpP+ZzhPy+f3", + "V8OidHk2cDW3d9Dr71hmm4Hmkdm8tyJGSKSaCqZpHO8qC3bU6zXVyEbdrST/6mr99dVKWZ6q0uBkfaVq", + "Wu5jKzjYTMZiynVxIQpOP3/Pyezz7eNtKxDu4M0iswRsRSp4JpSPZHPEjMkGt6pZayvd7yR6VGLNfJkI", + "lyA5gXttMcK4ZKEzm8kSESlscnzZTH4BWbCREuh6OwNdfszfjLiCIe9B9uMgU6s7Lsz/+GIFxsqXTz77", + "ZcqLFI7vbjWlhx4nyzjtwpytEKETY4osrsDfSN6m8guQt91arCFv5QiZguupuudZysyw3Z5kz6m7h7uZ", + "x+KSuiGfdsNiGugWzJqliFpyLV3JqnOsK/73pTWe6DkJN89t9RFuJvqec5+Dc4vIyFBRRGSeUficBHwF", + "0nWfi7Seeq+awfoMJFxMrlxPwhPQRybexNp1vDxaleatWt3T8u4t4qpsEauNoMrMUSENcg0xKzTENs6V", + "n/JNlsUrTz5mLh0avgw1l88pV/jD2TDUwPaA3A0gdTBUls6CTYi45jdkc7QrlvZGOT4tdD4SdmfsOo3N", + "JmDYO7WaspmugGOUgMQRltjjMquWLvJM3Ga+TtJYkgXmsjtlPGnr5nSST8giQmcm7clkR9pa1zbMW0D9", + "IprqE6ZFrAObUxwLaAVCLnWYSDUc5BcUC+nAWV5VDE2ZxkYFqR5QMUY8IVTNYumco987Gh6N+seDUSG/", + "JD8/UI1R78WFD06jYSokS1BWsnhs5lDSMaMt9DvqnZQSYDo/rz1d0UOuH67UGcCM3ChioqbvSbsOBwWr", + "yB3dOd1zSG6zekJXMkZ1STNZhtoads0kY0ok0TvuQmbsgjPl09QXPpshadIln8m5K+diNnh3VlCLb62+", + "zosGRPPMJ4+Er0qZz/u9+7PYk0VInoVbsCmbSNxoUHl69QYeoljS0F3q3WjXbm8Jv8yWvXovudk1tEPY", + "7953vnt3F79zoDxP1FTB2PkWm4b1ZWEvrG/DZWmfFssm56cG4pJL+HwBftOHB7SZH7LH687xGlV0m0Nh", + "tzuZYoaVBq+9A7MSuudzCP8wuA1Tzh1z6fSg0o2LMmTzuyPwnJAt339qXvvnWKAJAEXZfZQ9gHd0Dqrw", + "Ubr/01mx8m8L2fyrXRqwc8CxySzxYvUKAJFptu0mISAiULrQF155Sqna0FWROhZvdKvLYJP9mC3r2t+j", + "aBsUlZHz0WR/IDOpCGi0YKQUvTHKLvuNc2biAE3rbMqpYas31+/eogRoihZ4Zm45F1KJINJZUcK72L5h", + "CXxUm4m1zCXhq+zOZRKXKauCGSWJrZSFvdU4jGRKxmKARDdXCY+Mjg+OKld1fmr4AmGl76wbJcUerTtb", + "tL1z6ICrqpVhG7MZoY24LW/7dVk0jdmD8gQ4RIRDqI/eJSumNtqCSgBPUFx3WAHwsDfwGY3poNh63nIZ", + "Mm8b7/d+unzrQp7eRjyfgUs5WZPu7HITOz9543J7AK+m21YlhfazyxttBS6R1OWEViPseursfBbcs/UY", + "b4c4jic4/KMR7G8wjWIL9Q+qlQFydVCUcnfZ3qLHxr7QJYRA3A6qlH5rPjClc/GLxqHWfPgazjGdgUDE", + "Mi/7A6hosJZzJ3nNS6lnHq/o3X0vVGdo5zmiqtzKT4auMoTPZ+3/we1vvfbJzU37zv/12VbNHdJ+ejYU", + "rYDzq8vXSqlSMYqeT5+sQvrSZBqE7fcGo62Fvd2WmQogtAnCeRTPYGVLpnK82tza0ynrP5o46/+ts1c1", + "dI+35yUUljafHL9lM0MHypNDLDUxFKM48w0nsxMQQoNG7QH8a6npJ5PJSw9KkB9YTVX72lNQbmgBypkL", + "8WPojZ2Ae8w+05GzmULN89usiV/c1dq1SWlx+TZw8RsU+rMPIScSOMH+/Ad3P/mZcx5cN56dxll1BPu9", + "8Q6zHb5k8+uwpu9wlCIrG9zAMLfGtr6A8Zt1D57jgLF4X/gp1y++FGT7Cxw2mqs1+5sXL3Tzws1+g1Vk", + "FPyEaxfGVlbfusgN45lI137roQln+9OYZ8j9zSa+lPBb59utItnZ7dvtLlsUCHuDuxbPztOrb1qYuxHu", + "Q0TZdyXtheHs/vYT0qAM2vd3L17g7sVWjNqV9nsRP2AMXrfllf7WjCZk84WJlJrz8iYPpvzhs0qeFAj5", + "7KZR/IBGg31UvpixTapU7zmEtR+58ElrZBRpvE9a3lWClFLpevPS7fF7f+TxI2dRGmbXwfU99tRziz9k", + "Sfe+r83L9lNPnLUGorzqWLOqcqp1covIA4HlbJl6iLGhGXctoNhS9arApo1lOd55W7VciE3byndRtqWS", + "9jdtxRx2F1opn3Jv2kz5wwZ5a8UgwqZt2YPxrI3yyenj7eP/BQAA///MtX56kW0AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/queryAPI/health.go b/api/queryAPI/health.go new file mode 100644 index 00000000..e105f635 --- /dev/null +++ b/api/queryAPI/health.go @@ -0,0 +1,11 @@ +package queryapi + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (s *Controllers) IsHealthy(ctx echo.Context) error { + return ctx.NoContent(http.StatusOK) +} diff --git a/api/queryAPI/health_test.go b/api/queryAPI/health_test.go new file mode 100644 index 00000000..35cc6464 --- /dev/null +++ b/api/queryAPI/health_test.go @@ -0,0 +1,25 @@ +package queryapi_test + +import ( + "net/http" + "testing" + + queryapi "queryorchestration/api/queryAPI" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsHealthy(t *testing.T) { + t.Parallel() + cfg := &ControllerConfig{} + + svc := createControllerServices(cfg) + cons := queryapi.NewControllers(svc) + + ctx, rec := createContext(t) + + err := cons.IsHealthy(ctx) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) +} diff --git a/build/Dockerfile b/build/Dockerfile index 32a3fdd8..54c27d09 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -52,3 +52,6 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /app/bin/ . EXPOSE 8080 + +HEALTHCHECK --interval=3s --timeout=3s --start-period=1s --retries=10 \ + CMD ["./healthcheck"] diff --git a/cmd/healthcheck/main.go b/cmd/healthcheck/main.go new file mode 100644 index 00000000..6d6bcb04 --- /dev/null +++ b/cmd/healthcheck/main.go @@ -0,0 +1,20 @@ +// cmd/healthcheck/main.go +package main + +import ( + "net/http" + "os" +) + +func main() { + resp, err := http.Get("http://localhost:8080/health") + if err != nil { + os.Exit(1) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + os.Exit(1) + } + os.Exit(0) +} diff --git a/deployments/compose.aws.yaml b/deployments/compose.aws.yaml index 9e32dbb1..7464e180 100644 --- a/deployments/compose.aws.yaml +++ b/deployments/compose.aws.yaml @@ -142,6 +142,7 @@ services: COGNITO_CLIENT_SECRET: "fillin" COGNITO_DOMAIN: "fillin" COGNITO_CLIENT_ID: "fillin" + BUCKET: ${BUCKET_IN} networks: - aws-server-network @@ -183,6 +184,7 @@ services: COGNITO_CLIENT_SECRET: "fillin" COGNITO_DOMAIN: "fillin" COGNITO_CLIENT_ID: "fillin" + BUCKET: ${BUCKET_IN} networks: - aws-server-network @@ -291,6 +293,7 @@ services: COGNITO_CLIENT_SECRET: "fillin" COGNITO_DOMAIN: "fillin" COGNITO_CLIENT_ID: "fillin" + BUCKET: ${BUCKET_IN} networks: - aws-server-network diff --git a/docs/README.md b/docs/README.md index dcdf3a2a..4f0d21c5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,6 +58,10 @@ If the document text is not already extracted according to the collector standar Otherwise, add an event to the QUERYSYNC queue. +## healthcheck + +cmd/healthcheck/main.go + ## metricsExample\_test This main.go serves as an example of how to use the custom Prometheus metrics package. diff --git a/internal/cognitoauth/middleware.go b/internal/cognitoauth/middleware.go index 3d996f69..d34a311d 100644 --- a/internal/cognitoauth/middleware.go +++ b/internal/cognitoauth/middleware.go @@ -36,7 +36,7 @@ func TokenValidationMiddleware(config auth.ConfigProvider) echo.MiddlewareFunc { config.GetAuthLogger().Debug("Processing request", "path", requestPath, "method", c.Request().Method) // Skip token validation for specified public paths - publicPaths := []string{config.GetAuthHomePath(), config.GetAuthLogoutPath()} + publicPaths := []string{config.GetAuthHomePath(), config.GetAuthLogoutPath(), config.GetHealthPath()} for _, path := range publicPaths { if requestPath == path { config.GetAuthLogger().Debug("Skipping token validation for public path", "path", requestPath) diff --git a/internal/cognitoauth/token_test.go b/internal/cognitoauth/token_test.go index f05136db..36fb3e5e 100644 --- a/internal/cognitoauth/token_test.go +++ b/internal/cognitoauth/token_test.go @@ -44,6 +44,8 @@ func (m *mockConfigProvider) GetAuthHomePath() string { return "/home" } func (m *mockConfigProvider) SetAuthHomePath(s string) {} func (m *mockConfigProvider) GetAuthLogoutPath() string { return "/logout" } func (m *mockConfigProvider) SetAuthLogoutPath(s string) {} +func (m *mockConfigProvider) GetHealthPath() string { return "/health" } +func (m *mockConfigProvider) SetHealthPath(s string) {} func (m *mockConfigProvider) GetAuthLogger() *slog.Logger { return slog.Default() } func (m *mockConfigProvider) SetAuthLogger(*slog.Logger) {} func (m *mockConfigProvider) GetAuthRoutePermissions() map[string][]string { diff --git a/internal/serviceconfig/auth/config.go b/internal/serviceconfig/auth/config.go index 9c7221d0..3f251e6c 100644 --- a/internal/serviceconfig/auth/config.go +++ b/internal/serviceconfig/auth/config.go @@ -29,6 +29,7 @@ type CognitoConfig struct { AuthCallbackPath string `env:"COGNITO_CALLBACK_PATH" envDefault:"/login-callback"` AuthHomePath string `env:"COGNITO_HOME_PATH" envDefault:"/home"` AuthLogoutPath string `env:"COGNITO_LOGOUT_PATH" envDefault:"/logout"` + HealthPath string `env:"HEALTH_PATH" envDefault:"/health"` AuthLogger *slog.Logger // Logger instance ( normally just the same logger as main) AuthRoutePermissions map[string][]string // Map of routes to required permissions } @@ -63,6 +64,8 @@ type ConfigProvider interface { SetAuthHomePath(string) GetAuthLogoutPath() string SetAuthLogoutPath(string) + GetHealthPath() string + SetHealthPath(string) GetAuthLogger() *slog.Logger SetAuthLogger(*slog.Logger) GetAuthRoutePermissions() map[string][]string @@ -159,6 +162,7 @@ func (c *CognitoConfig) PrettyPrintDebugOnly() { "AuthCallbackPath", c.AuthCallbackPath, "AuthHomePath", c.AuthHomePath, "AuthLogoutPath", c.AuthLogoutPath, + "HealthPath", c.HealthPath, "DisableAuth", c.DisableAuth, "AuthDomain", c.AuthDomain) @@ -276,6 +280,14 @@ func (c *CognitoConfig) SetAuthLogoutPath(val string) { c.AuthLogoutPath = val } +func (c *CognitoConfig) GetHealthPath() string { + return c.HealthPath +} + +func (c *CognitoConfig) SetHealthPath(val string) { + c.HealthPath = val +} + func (c *CognitoConfig) GetAuthLogger() *slog.Logger { return c.AuthLogger } diff --git a/internal/serviceconfig/auth/config_test.go b/internal/serviceconfig/auth/config_test.go index c1dd267f..1ac94873 100644 --- a/internal/serviceconfig/auth/config_test.go +++ b/internal/serviceconfig/auth/config_test.go @@ -21,6 +21,7 @@ func TestInitializeAuthConfig(t *testing.T) { "COGNITO_CALLBACK_PATH": "/custom-callback", "COGNITO_HOME_PATH": "/custom-home", "COGNITO_LOGOUT_PATH": "/custom-logout", + "HEALTH_PATH": "/custom-health", "DEBUG": "true", } @@ -67,6 +68,7 @@ func TestInitializeAuthConfig(t *testing.T) { {"AuthCallbackPath", cfg.AuthCallbackPath, "/custom-callback"}, {"AuthHomePath", cfg.AuthHomePath, "/custom-home"}, {"AuthLogoutPath", cfg.AuthLogoutPath, "/custom-logout"}, + {"HealthPath", cfg.HealthPath, "/custom-health"}, {"AuthRedirectURI", cfg.AuthRedirectURI, "https://example.com/custom-callback"}, {"AuthTokenURL", cfg.AuthTokenURL, "test-domain.auth.us-east-2.amazoncognito.com/oauth2/token"}, {"AuthURL", cfg.AuthURL, "test-domain.auth.us-east-2.amazoncognito.com/oauth2/authorize"}, @@ -134,6 +136,7 @@ func TestInitializeAuthConfigWithDefaults(t *testing.T) { {"AuthCallbackPath", cfg.AuthCallbackPath, "/login-callback"}, {"AuthHomePath", cfg.AuthHomePath, "/home"}, {"AuthLogoutPath", cfg.AuthLogoutPath, "/logout"}, + {"HealthPath", cfg.HealthPath, "/health"}, } for _, tt := range defaults { @@ -246,6 +249,12 @@ func TestGettersAndSetters(t *testing.T) { getter: func() interface{} { return cfg.GetAuthLogoutPath() }, expected: "/custom-logout", }, + { + name: "HealthPath", + setter: func() { cfg.SetHealthPath("/custom-health") }, + getter: func() interface{} { return cfg.GetHealthPath() }, + expected: "/custom-health", + }, { name: "AuthLogger", setter: func() { cfg.SetAuthLogger(testLogger) }, diff --git a/internal/serviceconfig/observability/prometheus/prometheus.go b/internal/serviceconfig/observability/prometheus/prometheus.go index 68a583bb..c657c0cc 100644 --- a/internal/serviceconfig/observability/prometheus/prometheus.go +++ b/internal/serviceconfig/observability/prometheus/prometheus.go @@ -120,6 +120,9 @@ func New(ctx context.Context, config MetricsConfig) (*Metrics, error) { if config.ServeHttp { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) m.server = &http.Server{ Addr: fmt.Sprintf(":%d", config.Port), diff --git a/internal/test/container.go b/internal/test/container.go index 8127fdd5..f50910f7 100644 --- a/internal/test/container.go +++ b/internal/test/container.go @@ -83,9 +83,10 @@ func createContainer(t testing.TB, ctx context.Context, network string, cfg *con } req := testcontainers.ContainerRequest{ - Image: imageTag, - Env: env, - WaitingFor: wait.ForLog(cfg.WaitForMsg), + Image: imageTag, + Env: env, + //WaitingFor: wait.ForLog(cfg.WaitForMsg), + WaitingFor: wait.ForHealthCheck(), Entrypoint: []string{fmt.Sprintf("./%s", cfg.Name)}, Networks: []string{network}, } diff --git a/mocks/queryapi/mock_ClientInterface.go b/mocks/queryapi/mock_ClientInterface.go index b91d0427..1dc4c78a 100644 --- a/mocks/queryapi/mock_ClientInterface.go +++ b/mocks/queryapi/mock_ClientInterface.go @@ -841,6 +841,79 @@ func (_c *MockClientInterface_GetStatusByClientId_Call) RunAndReturn(run func(co return _c } +// IsHealthy provides a mock function with given fields: ctx, reqEditors +func (_m *MockClientInterface) IsHealthy(ctx context.Context, reqEditors ...queryapi.RequestEditorFn) (*http.Response, error) { + _va := make([]interface{}, len(reqEditors)) + for _i := range reqEditors { + _va[_i] = reqEditors[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for IsHealthy") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...queryapi.RequestEditorFn) (*http.Response, error)); ok { + return rf(ctx, reqEditors...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...queryapi.RequestEditorFn) *http.Response); ok { + r0 = rf(ctx, reqEditors...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...queryapi.RequestEditorFn) error); ok { + r1 = rf(ctx, reqEditors...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClientInterface_IsHealthy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsHealthy' +type MockClientInterface_IsHealthy_Call struct { + *mock.Call +} + +// IsHealthy is a helper method to define mock.On call +// - ctx context.Context +// - reqEditors ...queryapi.RequestEditorFn +func (_e *MockClientInterface_Expecter) IsHealthy(ctx interface{}, reqEditors ...interface{}) *MockClientInterface_IsHealthy_Call { + return &MockClientInterface_IsHealthy_Call{Call: _e.mock.On("IsHealthy", + append([]interface{}{ctx}, reqEditors...)...)} +} + +func (_c *MockClientInterface_IsHealthy_Call) Run(run func(ctx context.Context, reqEditors ...queryapi.RequestEditorFn)) *MockClientInterface_IsHealthy_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]queryapi.RequestEditorFn, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(queryapi.RequestEditorFn) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *MockClientInterface_IsHealthy_Call) Return(_a0 *http.Response, _a1 error) *MockClientInterface_IsHealthy_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClientInterface_IsHealthy_Call) RunAndReturn(run func(context.Context, ...queryapi.RequestEditorFn) (*http.Response, error)) *MockClientInterface_IsHealthy_Call { + _c.Call.Return(run) + return _c +} + // ListDocumentsByClientId provides a mock function with given fields: ctx, id, reqEditors func (_m *MockClientInterface) ListDocumentsByClientId(ctx context.Context, id queryapi.ClientID, reqEditors ...queryapi.RequestEditorFn) (*http.Response, error) { _va := make([]interface{}, len(reqEditors)) diff --git a/mocks/queryapi/mock_ClientWithResponsesInterface.go b/mocks/queryapi/mock_ClientWithResponsesInterface.go index 83ace41e..85ccec41 100644 --- a/mocks/queryapi/mock_ClientWithResponsesInterface.go +++ b/mocks/queryapi/mock_ClientWithResponsesInterface.go @@ -839,6 +839,79 @@ func (_c *MockClientWithResponsesInterface_GetStatusByClientIdWithResponse_Call) return _c } +// IsHealthyWithResponse provides a mock function with given fields: ctx, reqEditors +func (_m *MockClientWithResponsesInterface) IsHealthyWithResponse(ctx context.Context, reqEditors ...queryapi.RequestEditorFn) (*queryapi.IsHealthyResponse, error) { + _va := make([]interface{}, len(reqEditors)) + for _i := range reqEditors { + _va[_i] = reqEditors[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for IsHealthyWithResponse") + } + + var r0 *queryapi.IsHealthyResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...queryapi.RequestEditorFn) (*queryapi.IsHealthyResponse, error)); ok { + return rf(ctx, reqEditors...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...queryapi.RequestEditorFn) *queryapi.IsHealthyResponse); ok { + r0 = rf(ctx, reqEditors...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*queryapi.IsHealthyResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...queryapi.RequestEditorFn) error); ok { + r1 = rf(ctx, reqEditors...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClientWithResponsesInterface_IsHealthyWithResponse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsHealthyWithResponse' +type MockClientWithResponsesInterface_IsHealthyWithResponse_Call struct { + *mock.Call +} + +// IsHealthyWithResponse is a helper method to define mock.On call +// - ctx context.Context +// - reqEditors ...queryapi.RequestEditorFn +func (_e *MockClientWithResponsesInterface_Expecter) IsHealthyWithResponse(ctx interface{}, reqEditors ...interface{}) *MockClientWithResponsesInterface_IsHealthyWithResponse_Call { + return &MockClientWithResponsesInterface_IsHealthyWithResponse_Call{Call: _e.mock.On("IsHealthyWithResponse", + append([]interface{}{ctx}, reqEditors...)...)} +} + +func (_c *MockClientWithResponsesInterface_IsHealthyWithResponse_Call) Run(run func(ctx context.Context, reqEditors ...queryapi.RequestEditorFn)) *MockClientWithResponsesInterface_IsHealthyWithResponse_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]queryapi.RequestEditorFn, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(queryapi.RequestEditorFn) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *MockClientWithResponsesInterface_IsHealthyWithResponse_Call) Return(_a0 *queryapi.IsHealthyResponse, _a1 error) *MockClientWithResponsesInterface_IsHealthyWithResponse_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClientWithResponsesInterface_IsHealthyWithResponse_Call) RunAndReturn(run func(context.Context, ...queryapi.RequestEditorFn) (*queryapi.IsHealthyResponse, error)) *MockClientWithResponsesInterface_IsHealthyWithResponse_Call { + _c.Call.Return(run) + return _c +} + // ListDocumentsByClientIdWithResponse provides a mock function with given fields: ctx, id, reqEditors func (_m *MockClientWithResponsesInterface) ListDocumentsByClientIdWithResponse(ctx context.Context, id queryapi.ClientID, reqEditors ...queryapi.RequestEditorFn) (*queryapi.ListDocumentsByClientIdResponse, error) { _va := make([]interface{}, len(reqEditors)) diff --git a/pkg/queryAPI/api.gen.go b/pkg/queryAPI/api.gen.go index 0a7d999e..7b266cb4 100644 --- a/pkg/queryAPI/api.gen.go +++ b/pkg/queryAPI/api.gen.go @@ -505,6 +505,9 @@ type ClientInterface interface { // ExportState request ExportState(ctx context.Context, id ExportID, reqEditors ...RequestEditorFn) (*http.Response, error) + // IsHealthy request + IsHealthy(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetHomePage request GetHomePage(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -719,6 +722,18 @@ func (c *Client) ExportState(ctx context.Context, id ExportID, reqEditors ...Req return c.Client.Do(req) } +func (c *Client) IsHealthy(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewIsHealthyRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetHomePage(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetHomePageRequest(c.Server) if err != nil { @@ -1284,6 +1299,33 @@ func NewExportStateRequest(server string, id ExportID) (*http.Request, error) { return req, nil } +// NewIsHealthyRequest generates requests for IsHealthy +func NewIsHealthyRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/health") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetHomePageRequest generates requests for GetHomePage func NewGetHomePageRequest(server string) (*http.Request, error) { var err error @@ -1701,6 +1743,9 @@ type ClientWithResponsesInterface interface { // ExportStateWithResponse request ExportStateWithResponse(ctx context.Context, id ExportID, reqEditors ...RequestEditorFn) (*ExportStateResponse, error) + // IsHealthyWithResponse request + IsHealthyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*IsHealthyResponse, error) + // GetHomePageWithResponse request GetHomePageWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHomePageResponse, error) @@ -2018,6 +2063,31 @@ func (r ExportStateResponse) StatusCode() int { return 0 } +type IsHealthyResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *InvalidRequest + JSON401 *Unauthorized + JSON429 *TooManyRequests + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r IsHealthyResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r IsHealthyResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetHomePageResponse struct { Body []byte HTTPResponse *http.Response @@ -2378,6 +2448,15 @@ func (c *ClientWithResponses) ExportStateWithResponse(ctx context.Context, id Ex return ParseExportStateResponse(rsp) } +// IsHealthyWithResponse request returning *IsHealthyResponse +func (c *ClientWithResponses) IsHealthyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*IsHealthyResponse, error) { + rsp, err := c.IsHealthy(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseIsHealthyResponse(rsp) +} + // GetHomePageWithResponse request returning *GetHomePageResponse func (c *ClientWithResponses) GetHomePageWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHomePageResponse, error) { rsp, err := c.GetHomePage(ctx, reqEditors...) @@ -3056,6 +3135,53 @@ func ParseExportStateResponse(rsp *http.Response) (*ExportStateResponse, error) return response, nil } +// ParseIsHealthyResponse parses an HTTP response from a IsHealthyWithResponse call +func ParseIsHealthyResponse(rsp *http.Response) (*IsHealthyResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &IsHealthyResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest InvalidRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Unauthorized + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest TooManyRequests + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetHomePageResponse parses an HTTP response from a GetHomePageWithResponse call func ParseGetHomePageResponse(rsp *http.Response) (*GetHomePageResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/serviceAPIs/queryAPI.yaml b/serviceAPIs/queryAPI.yaml index 46d453e7..28717d7b 100644 --- a/serviceAPIs/queryAPI.yaml +++ b/serviceAPIs/queryAPI.yaml @@ -27,8 +27,34 @@ tags: description: Operations related to exports - name: AuthService description: Operations related to authentication + - name: HealthService + description: Operations related to health paths: + /health: + get: + operationId: isHealthy + tags: + - HealthService + summary: Provide health endpoint + description: See if the service is up and running + security: + - {} + responses: + "200": + description: Healthy service. + headers: + RateLimit: + $ref: "#/components/headers/RateLimit" + "400": + $ref: "#/components/responses/InvalidRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalError" + /client: post: operationId: createClient diff --git a/test/queryAPI/accessory_test.go b/test/queryAPI/accessory_test.go index cc68394e..3dee8e80 100644 --- a/test/queryAPI/accessory_test.go +++ b/test/queryAPI/accessory_test.go @@ -41,5 +41,10 @@ func TestQueryAPIAccessories(t *testing.T) { assert.NotNil(t, resp) assert.Equal(t, http.StatusOK, resp.StatusCode) + resp, err = http.Get(fmt.Sprintf("%s/health", c.API.URI)) + require.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + test.PrintContainerLogs(t, c.API.Container) }