Загрузка данных


dmitriev-aal@VDI-Dmitriev-A:~$ kubectl --context devstbl -n isys-sinv-int-dev \
>   get cm sinv-int-dev-envoy-filters -o yaml
apiVersion: v1
data:
  lua__appfarm__platform__filters__envoy-platform-lua-filter__envoy-platform-lua-filter-0.3.7.lua: |
    local forwarder_host = os.getenv("HTTP_LOG_FORWARDER_HOST")
    local forwarder_port = os.getenv("HTTP_LOG_FORWARDER_PORT")
    local pod_name = os.getenv("POD_NAME")
    local namespace_name = os.getenv("POD_NAMESPACE")
    local target_service_name = os.getenv("PSVC_NAME")
    local target_system_name = os.getenv("ISYS_NAME")

    local SOURCE_SYSTEM_HEADER_NAME = "x-appfarm-source-system"
    local SOURCE_SERVICE_HEADER_NAME = "x-appfarm-source-service"

    local request_id = ""
    local method = ""
    local path = ""

    local content_types_for_log = {
        "application/json",
        "application/xml",
        "application/x-www-form-urlencoded",
        "message/http",
        "text/cmd",
        "text/plain",
        "text/xml",
        "text/markdown"
    }

    -- Инициализация FFI один раз при загрузке
    local ok_ffi, ffi = pcall(require, "ffi")

    local CLOCK_REALTIME = 0
    local ts = nil

    -- Если FFI недоступен, логируем предупреждение и используем запасной вариант формирования таймстампа без миллисекунд
    if not ok_ffi then
        local ns = namespace_name or "unknown-namespace"
        local pod = pod_name or "unknown-pod"
        print("[appfarm][lua-filter][" .. ns .. "/" .. pod .. "] Модуль LuaJIT FFI недоступен (Таймстамп будет формироваться без миллисекунд.")
    else
      ffi.cdef[[
        typedef long time_t;
        struct timespec { time_t tv_sec; long tv_nsec; };
        int clock_gettime(int clk_id, struct timespec *tp);
      ]]
      ts = ffi.new("struct timespec")
    end

    function envoy_on_request(request_handle)
        handle_interaction(request_handle, "request")
    end

    function envoy_on_response(response_handle)
        handle_interaction(response_handle, "response")
    end

    function handle_interaction(handle, direction)
        -- Превращаем кастомную структуру, полученную из функции в мапу, в которой все заголовки окажутся уже в нижнем регистре сами по себе
        -- Это необходимо, т.к. кастомная структура из функции превращается в nil, если её заюзать внутри цикла "for chunk in handle:bodyChunks() do"
        -- Причём, это особенность именно Энвоя, а не Луа
        local headers = headers_to_map(handle:headers())
        local is_GRPC = is_GRPC(headers)

        -- Сохраняем метаданные из запроса, чтобы использовать их при логгировании ответа
        save_metadata(headers)

        if not (need_log_body(headers) or is_GRPC) then
            send_log(handle, direction, headers, "", false)
            return
        end

        if is_GRPC then
            handle_grpc(handle, direction, headers)
            return
        end

        local request_body = ""
        for chunk in handle:bodyChunks() do
            if (chunk:length() > 0) then
                request_body = request_body .. chunk:getBytes(0, chunk:length())
            end
        end

        if (request_body:len() > 0) then
            send_log(handle, direction, headers, request_body, false)
        end

    end

    function handle_grpc(handle, direction, headers)
        local request_body = ""
        local message_remainder = ""
        for chunk in handle:bodyChunks() do
            if (chunk:length() > 0) then

                request_body = request_body .. chunk:getBytes(0, chunk:length())

                -- Если в одном дата-фрейме HTTP/2 будет находиться конец одного сообщения и начало следующего,
                -- оставляем кусочек нового сообщения для следующей итерации
                if message_remainder:len() > 0 then
                    request_body = message_remainder .. request_body
                    message_remainder = ""
                end
                -- В gRPC длина сообщения записана в 2-5 байтах
                -- chunk:getBytes по какой-то причине возвращает строку, поэтому надо сконвертить её в массив байт и превратить в число
                local messageSize = string_bytes_to_number(request_body:sub(2, 5)) + 5
                if request_body:len() >= messageSize then
                    -- Если мы уже прочитали больше длины сообщения, логаем его и записываем лишние данные в остаток,
                    -- если они есть
                    send_log(handle, direction, headers, request_body:sub(1, messageSize), true)
                    message_remainder = request_body:sub(messageSize + 1, request_body:len())
                    request_body = ""
                end
            end
        end
        if (request_body:len() > 0) then
            send_log(handle, direction, headers, request_body, true)
        end
    end

    function save_metadata(headers)
        for key, value in pairs(headers) do
            local sanitized_key = string.gsub(key, ":", "")
            -- Save data from request
            if sanitized_key == "x-request-id" then
                request_id = value
            end
            if sanitized_key == "method" then
                method = value
            end
            if sanitized_key == "path" then
                path = value
            end
        end
    end

    function prepare_request(direction, headers)
        local prepared_headers = {}
        local url = "outbound|" .. forwarder_port .. "||" .. forwarder_host
        for key, value in pairs(headers) do
            local sanitized_key = string.lower(key)
            sanitized_key = string.gsub(sanitized_key, ":", "")
            prepared_headers["x-original-" .. sanitized_key] = value
            if direction == "response" then
                put_if_not_empty(prepared_headers, "x-original-x-request-id", request_id)
                put_if_not_empty(prepared_headers, "x-original-method", method)
                put_if_not_empty(prepared_headers, "x-original-path", path)
            end
        end

        put_if_not_empty(prepared_headers, "x-appfarm-target-service", target_service_name)
        put_if_not_empty(prepared_headers, "x-appfarm-target-system", target_system_name)
        put_if_not_empty(prepared_headers, SOURCE_SERVICE_HEADER_NAME, headers[SOURCE_SERVICE_HEADER_NAME])
        put_if_not_empty(prepared_headers, SOURCE_SYSTEM_HEADER_NAME, headers[SOURCE_SYSTEM_HEADER_NAME])

        prepared_headers[":method"] = "POST"
        prepared_headers["x-original-timestamp"] = timestamp_rfc3339_ms()
        prepared_headers[":path"] = "/"
        prepared_headers["accept"] = "*/*"
        prepared_headers["x-namespace-name"] = namespace_name
        prepared_headers["x-pod-name"] = pod_name
        prepared_headers["x-original-direction"] = direction
        prepared_headers["content-type"] = "application/json"
        prepared_headers[":authority"] = forwarder_host

        return prepared_headers, url
    end

    function put_if_not_empty(map, key, value)
        if value == nil or value:len() == 0 then
            return
        end
        map[key] = value
    end

    function send_log(handle, direction, headers, body, do_hex_encode)
        local prepared_headers, url = prepare_request(direction, headers)

        local prepared_body = body
        if do_hex_encode then
            prepared_body = encode_to_hex(prepared_body)
        end

        local _, _ = handle:httpCall(
                url,
                prepared_headers,
                prepared_body,
                5000,
                true
        )
    end

    function need_log_body(headers)
        if is_empty_source(headers) or is_inter_system_communication(headers) then
            return false
        end

        local content_type = headers["content-type"]
        for _, content_type_for_log in pairs(content_types_for_log) do
            if content_type == content_type_for_log then
                return true
            end
        end

        return false
    end

    function is_empty_source(headers)
        local source_system_name = headers[SOURCE_SYSTEM_HEADER_NAME]
        if source_system_name == nil or source_system_name == "" then
            return true
        end

        local source_service_name = headers[SOURCE_SERVICE_HEADER_NAME]
        if source_service_name == nil or source_service_name == "" then
            return true
        end
    end

    function is_inter_system_communication(headers)
        local source_system_name = headers[SOURCE_SYSTEM_HEADER_NAME]

        return target_system_name == source_system_name
    end

    -- application/grpc+json пока не поддерживаем, т.к. это редкость
    -- если понадобится поддержать, надо будет тело логать не в хекс виде, а текстовом
    function is_GRPC(headers)
        local content_type = headers["content-type"]
        return content_type == "application/grpc" or content_type == "application/grpc+proto"
    end

    function headers_to_map(headers)
        local headers_map = {}
        for k, v in pairs(headers) do
            headers_map[k] = v
        end
        return headers_map
    end

    function encode_to_hex(str)
        return (str:gsub(".", function(char)
            return string.format("%02x", char:byte())
        end))
    end

    function string_bytes_to_number(s)
        local number = 0
        local length = s:len()
        for i = 1, length do
            local c = s:byte(i)
            -- bit - либа для битовых операций в LuaJIT, который юзает Энвой
            -- В самом Lua сейчас уже есть битовые операции, но в LuaJIT только через вызов либы
            number = bit.lshift(number, 8) + c
        end
        return number
    end

    function timestamp_rfc3339_ms()
      -- Основной путь: FFI clock_gettime(CLOCK_REALTIME)
      if ok_ffi and ts ~= nil and ffi.C.clock_gettime(CLOCK_REALTIME, ts) == 0 then
        local s = tonumber(ts.tv_sec)
        local ms = math.floor(tonumber(ts.tv_nsec) / 1e6 + 0.5)
        if ms >= 1000 then s = s + 1; ms = 0 end
        -- Формируем UTC-метку времени (RFC3339) с миллисекундами
        return os.date("!%Y-%m-%dT%H:%M:%S", s) .. string.format(".%03dZ", ms)
      end
      -- Запасной вариант: без миллисекунд (на случай, если FFI вдруг недоступен)
      return os.date("!%Y-%m-%dT%H:%M:%SZ")
    end
  lua__rshbintech__integrations__ckpr__platform__filters__envoy-platform-lua-filter-out__envoy-platform-lua-filter-out-0.0.2.lua: |-
    local service_name = os.getenv("PSVC_NAME")
    local system_name = os.getenv("ISYS_NAME")


    function envoy_on_request(request_handle)
        add_system_info(request_handle)
    end

    function add_system_info(request_handle)
        add_header_if_not_empty(request_handle, "x-appfarm-source-service", service_name)
        add_header_if_not_empty(request_handle, "x-appfarm-source-system", system_name)
    end

    function add_header_if_not_empty(request_handle, key, value)
        if value == nil or value:len() == 0 then
            return
        end
        request_handle:headers():add(key, value)
    end
kind: ConfigMap
metadata:
  creationTimestamp: "2024-07-18T09:44:04Z"
  name: sinv-int-dev-envoy-filters
  namespace: isys-sinv-int-dev
  resourceVersion: "23357390018"
  uid: 8a1666e2-efbb-4b68-b32b-34a3aeca91d1
dmitriev-aal@VDI-Dmitriev-A:~$ kubectl --context devstbl -n isys-sinv-int-dev \
>   exec deploy/quotes -c istio-proxy -- \
>   ls -lah /var/local/lib/wasm-filters
total 20K
drwxrwsrwx 3 root  1001 4.0K Aug 25 07:27 .
drwxr-sr-x 3 root staff 4.0K Aug 25 12:59 ..
drwxr-sr-x 2 root  1001 4.0K Aug 25 07:27 ..2026_08_25_07_27_17.78035742
lrwxrwxrwx 1 root  1001   30 Aug 25 07:27 ..data -> ..2026_08_25_07_27_17.78035742
lrwxrwxrwx 1 root  1001  102 Aug 25 07:27 lua__appfarm__platform__filters__envoy-platform-lua-filter__envoy-platform-lua-filter-0.3.7.lua -> ..data/lua__appfarm__platform__filters__envoy-platform-lua-filter__envoy-platform-lua-filter-0.3.7.lua
lrwxrwxrwx 1 root  1001  133 Aug 25 07:27 lua__rshbintech__integrations__ckpr__platform__filters__envoy-platform-lua-filter-out__envoy-platform-lua-filter-out-0.0.2.lua -> ..data/lua__rshbintech__integrations__ckpr__platform__filters__envoy-platform-lua-filter-out__envoy-platform-lua-filter-out-0.0.2.lua
dmitriev-aal@VDI-Dmitriev-A:~$ kubectl --context devstbl -n isys-sinv-int-dev \
>   exec deploy/quotes -c app -- \
>   find /secrets -maxdepth 2 -type f -printf '%p\n'
find: unrecognized: -printf
BusyBox v1.37.0 (2025-12-16 14:19:28 UTC) multi-call binary.

Usage: find [-HL] [PATH]... [OPTIONS] [ACTIONS]

Search for files and perform actions on them.
First failed action stops processing of current file.
Defaults: PATH is current directory, action is '-print'

	-L,-follow	Follow symlinks
	-H		...on command line only
	-xdev		Don't descend directories on other filesystems
	-maxdepth N	Descend at most N levels. -maxdepth 0 applies
			actions to command line arguments only
	-mindepth N	Don't act on first N levels
	-depth		Act on directory *after* traversing it

Actions:
	( ACTIONS )	Group actions for -o / -a
	! ACT		Invert ACT's success/failure
	ACT1 [-a] ACT2	If ACT1 fails, stop, else do ACT2
	ACT1 -o ACT2	If ACT1 succeeds, stop, else do ACT2
			Note: -a has higher priority than -o
	-name PATTERN	Match file name (w/o directory name) to PATTERN
	-iname PATTERN	Case insensitive -name
	-path PATTERN	Match path to PATTERN
	-ipath PATTERN	Case insensitive -path
	-regex PATTERN	Match path to regex PATTERN
	-type X		File type is X (one of: f,d,l,b,c,s,p)
	-executable	File is executable
	-perm MASK	At least one mask bit (+MASK), all bits (-MASK),
			or exactly MASK bits are set in file's mode
	-mtime DAYS	mtime is greater than (+N), less than (-N),
			or exactly N days in the past
	-atime DAYS	atime +N/-N/N days in the past
	-ctime DAYS	ctime +N/-N/N days in the past
	-mmin MINS	mtime is greater than (+N), less than (-N),
			or exactly N minutes in the past
	-newer FILE	mtime is more recent than FILE's
	-inum N		File has inode number N
	-user NAME/ID	File is owned by given user
	-group NAME/ID	File is owned by given group
	-size N[bck]	File size is N (c:bytes,k:kbytes,b:512 bytes(def.))
			+/-N: file size is bigger/smaller than N
	-links N	Number of links is greater than (+N), less than (-N),
			or exactly N
	-empty		Match empty file/directory
	-prune		If current file is directory, don't descend into it
If none of the following actions is specified, -print is assumed
	-print		Print file name
	-print0		Print file name, NUL terminated
	-exec CMD ARG ;	Run CMD with all instances of {} replaced by
			file name. Fails if CMD exits with nonzero
	-exec CMD ARG + Run CMD with {} replaced by list of file names
	-ok CMD ARG ;   Prompt and run CMD with {} replaced
	-delete		Delete current file/directory. Turns on -depth option
	-quit		Exit
command terminated with exit code 1
dmitriev-aal@VDI-Dmitriev-A:~$