diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..a55bb221 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,43 @@ +name: Lint + +on: + pull_request: {} + workflow_dispatch: {} + push: + branches: + - main + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lua-check: + name: Lua Check + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + checks: write + pull-requests: write + if: (github.actor != 'dependabot[bot]') + + steps: + - name: Checkout source code + uses: actions/checkout@v3 + + # Optional step to run on only changed files + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@54849deb963ca9f24185fb5de2965e002d066e6b # v37 + with: + files: | + **.lua + + - name: Lua Check + if: steps.changed-files.outputs.any_changed == 'true' + uses: Kong/public-shared-actions/code-check-actions/lua-lint@c03e30a36e8a2dde5cbd463229a96aaad7ccad24 + with: + additional_args: '--no-default-config --config .luacheckrc' + files: ${{ steps.changed-files.outputs.all_changed_files }} diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml new file mode 100644 index 00000000..e41c4b97 --- /dev/null +++ b/.github/workflows/sast.yml @@ -0,0 +1,31 @@ +name: SAST + +on: + pull_request: + paths: + - lib/**.lua + push: + branches: + - master + - main + paths: + - lib/**.lua + workflow_dispatch: {} + + +jobs: + semgrep: + name: Semgrep SAST + runs-on: ubuntu-latest + permissions: + # required for all workflows + security-events: write + # only required for workflows in private repositories + actions: read + contents: read + + if: (github.actor != 'dependabot[bot]') + + steps: + - uses: actions/checkout@v3 + - uses: Kong/public-shared-actions/security-actions/semgrep@c03e30a36e8a2dde5cbd463229a96aaad7ccad24 diff --git a/readme.md b/README.md similarity index 88% rename from readme.md rename to README.md index 78065a1e..8d94e4ac 100644 --- a/readme.md +++ b/README.md @@ -91,6 +91,17 @@ for the complete API. Versioning is strictly based on [Semantic Versioning](https://semver.org/) +### Releasing new versions: + +* update changelog below (PR's should be merged including a changelog entry) +* based on changelog determine new SemVer version +* create a new rockspec +* render the docs using `ldoc` (don't do this within PR's) +* commit as "release x.x.x" (do not include rockspec revision) +* tag the commit with "x.x.x" (do not include rockspec revision) +* push commit and tag +* upload rock to luarocks: `luarocks upload rockspecs/[name] --api-key=abc` + ### 1.6.3 (06-Sep-2023) * Feature: Added support for https_sni [#49](https://github.com/Kong/lua-resty-healthcheck/pull/49) (backport) @@ -126,6 +137,24 @@ Versioning is strictly based on [Semantic Versioning](https://semver.org/) serialization API. If it is unavailable, lua-resty-healthcheck fallbacks to cjson. [#109](https://github.com/Kong/lua-resty-healthcheck/pull/109) +### 1.5.3 (14-Nov-2022) + +* Fix: avoid raising worker events for new targets that were marked for delayed + removal, i.e. targets that already exist in memory only need the removal flag + cleared when added back. [#121](https://github.com/Kong/lua-resty-healthcheck/pull/121) + +### 1.5.2 (07-Jul-2022) + +* Better handling of `resty.lock` failure modes, adding more checks to ensure the + lock is held before running critical code, and improving the decision whether a + function should be retried after a timeout trying to acquire a lock. + [#113](https://github.com/Kong/lua-resty-healthcheck/pull/113) +* Increased logging for locked function failures. + [#114](https://github.com/Kong/lua-resty-healthcheck/pull/114) +* The cleanup frequency of deleted targets was lowered, cutting the number of + created locks in a short period. + [#116](https://github.com/Kong/lua-resty-healthcheck/pull/116) + ### 1.5.1 (23-Mar-2022) * Fix: avoid breaking active health checks when adding or removing targets. diff --git a/config.ld b/config.ld index 70432802..192b8cc8 100644 --- a/config.ld +++ b/config.ld @@ -4,7 +4,7 @@ description='Provides active and passive healthchecks (http and tcp) for OpenRes format='discount' file='./lib/' dir='docs' -readme='readme.md' +readme='README.md' sort=true sort_modules=true all=false diff --git a/lib/resty/healthcheck.lua b/lib/resty/healthcheck.lua index c5411366..6fc84cc9 100644 --- a/lib/resty/healthcheck.lua +++ b/lib/resty/healthcheck.lua @@ -32,6 +32,7 @@ local tostring = tostring local ipairs = ipairs local table_insert = table.insert local table_remove = table.remove +local table_concat = table.concat local string_format = string.format local ssl = require("ngx.ssl") local resty_timer = require "resty.timer" @@ -918,7 +919,7 @@ function checker:set_all_target_statuses_for_hostname(hostname, port, is_healthy end end - return all_ok, #errs > 0 and table.concat(errs, "; ") or nil + return all_ok, #errs > 0 and table_concat(errs, "; ") or nil end @@ -1043,7 +1044,7 @@ function checker:run_single_check(ip, port, hostname, hostheader) if headers_length > 0 then if is_array(req_headers) then self:log(WARN, "array headers is deprecated") - headers = table.concat(req_headers, "\r\n") + headers = table_concat(req_headers, "\r\n") else headers = new_tab(0, headers_length) local idx = 0 @@ -1058,7 +1059,7 @@ function checker:run_single_check(ip, port, hostname, hostheader) headers[idx] = key .. ": " .. tostring(values) end end - headers = table.concat(headers, "\r\n") + headers = table_concat(headers, "\r\n") end if #headers > 0 then headers = headers .. "\r\n" @@ -1384,7 +1385,7 @@ local MAXNUM = 2^31 - 1 local function fail(ctx, k, msg) ctx[#ctx + 1] = k - error(table.concat(ctx, ".") .. ": " .. msg, #ctx + 1) + error(table_concat(ctx, ".") .. ": " .. msg, #ctx + 1) end @@ -1425,51 +1426,52 @@ local function fill_in_settings(opts, defaults, ctx) end -local defaults = { - name = NO_DEFAULT, - shm_name = NO_DEFAULT, - type = NO_DEFAULT, - events_module = "resty.worker.events", - checks = { - active = { - type = "http", - timeout = 1, - concurrency = 10, - http_path = "/", - https_sni = NO_DEFAULT, - https_verify_certificate = true, - headers = {""}, - healthy = { - interval = 0, -- 0 = disabled by default - http_statuses = { 200, 302 }, - successes = 2, +local function get_defaults() + return { + name = NO_DEFAULT, + shm_name = NO_DEFAULT, + type = NO_DEFAULT, + events_module = "resty.worker.events", + checks = { + active = { + type = "http", + timeout = 1, + concurrency = 10, + http_path = "/", + https_sni = NO_DEFAULT, + https_verify_certificate = true, + headers = {""}, + healthy = { + interval = 0, -- 0 = disabled by default + http_statuses = { 200, 302 }, + successes = 2, + }, + unhealthy = { + interval = 0, -- 0 = disabled by default + http_statuses = { 429, 404, + 500, 501, 502, 503, 504, 505 }, + tcp_failures = 2, + timeouts = 3, + http_failures = 5, + }, }, - unhealthy = { - interval = 0, -- 0 = disabled by default - http_statuses = { 429, 404, - 500, 501, 502, 503, 504, 505 }, - tcp_failures = 2, - timeouts = 3, - http_failures = 5, + passive = { + type = "http", + healthy = { + http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, + 300, 301, 302, 303, 304, 305, 306, 307, 308 }, + successes = 5, + }, + unhealthy = { + http_statuses = { 429, 500, 503 }, + tcp_failures = 2, + timeouts = 7, + http_failures = 5, + }, }, }, - passive = { - type = "http", - healthy = { - http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, - 300, 301, 302, 303, 304, 305, 306, 307, 308 }, - successes = 5, - }, - unhealthy = { - http_statuses = { 429, 500, 503 }, - tcp_failures = 2, - timeouts = 7, - http_failures = 5, - }, - }, - }, -} - + } +end local function to_set(tbl, key) local set = {} @@ -1539,6 +1541,8 @@ function _M.new(opts) local active_type = (((opts or EMPTY).checks or EMPTY).active or EMPTY).type local passive_type = (((opts or EMPTY).checks or EMPTY).passive or EMPTY).type + -- create a new defaults table within new() as defaults table will be modified by to_set function later + local defaults = get_defaults() local self = fill_in_settings(opts, defaults) load_events_module(self) diff --git a/t/with_worker-events/00-new.t b/t/with_worker-events/00-new.t index d8d11c6b..d01274f4 100644 --- a/t/with_worker-events/00-new.t +++ b/t/with_worker-events/00-new.t @@ -3,7 +3,7 @@ use Cwd qw(cwd); workers(1); -plan tests => repeat_each() * (blocks() * 3) - 2; +plan tests => repeat_each() * (blocks() * 3) - 3; my $pwd = cwd(); @@ -228,3 +228,63 @@ false false false false + +=== TEST 8: new() was called multiple times with input which do not have healthy/unhealthy config +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + + -- CASE 1: default http_statuses should be set correctly when new() was called multiple times + local hc1 = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + type = "http", + }, + } + }) + -- make sure checks.active.healthy.http_statuses is filled with defaults + ngx.say(hc1.checks.active.healthy.http_statuses[200]) + + local hc2 = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + type = "http", + }, + } + }) + -- make sure checks.active.healthy.http_statuses is filled with defaults + ngx.say(hc2.checks.active.healthy.http_statuses[200]) + + -- CASE 2: the given http_statuses should not be overridden by default + local hc3 = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + type = "http", + healthy = { + http_statuses = {201} + } + }, + } + }) + -- make sure defaults won't override the given input + ngx.say(hc3.checks.active.healthy.http_statuses[200]) + ngx.say(hc3.checks.active.healthy.http_statuses[201]) + } + } +--- request +GET /t +--- response_body +true +true +nil +true