local M = {}
local API_BASE = "/apis/external-secrets.io/v1beta1"
function M.client(url, token)
local base_url = url:gsub("/+$", "")
local function headers()
return { Authorization = "Bearer " .. token }
end
local function api_get(path)
local resp = http.get(base_url .. path, { headers = headers() })
if resp.status == 404 then return nil end
if resp.status ~= 200 then
error("eso: GET " .. path .. " HTTP " .. resp.status .. ": " .. resp.body)
end
return json.parse(resp.body)
end
local function api_list(path)
local resp = http.get(base_url .. path, { headers = headers() })
if resp.status ~= 200 then
error("eso: LIST " .. path .. " HTTP " .. resp.status .. ": " .. resp.body)
end
return json.parse(resp.body)
end
local function find_condition(conditions, cond_type)
for _, cond in ipairs(conditions or {}) do
if cond.type == cond_type then
return cond
end
end
return nil
end
local c = {}
c.external_secrets = {}
function c.external_secrets:list(namespace)
return api_list(API_BASE .. "/namespaces/" .. namespace .. "/externalsecrets")
end
function c.external_secrets:get(namespace, name)
return api_get(API_BASE .. "/namespaces/" .. namespace .. "/externalsecrets/" .. name)
end
function c.external_secrets:status(namespace, name)
local es = api_get(API_BASE .. "/namespaces/" .. namespace .. "/externalsecrets/" .. name)
if not es then
error("eso: ExternalSecret " .. namespace .. "/" .. name .. " not found")
end
local st = es.status or {}
local conditions = st.conditions or {}
local ready_cond = find_condition(conditions, "Ready")
return {
ready = ready_cond ~= nil and ready_cond.status == "True",
status = ready_cond and ready_cond.reason or "Unknown",
sync_hash = st.syncedResourceVersion or "",
conditions = conditions,
}
end
function c.external_secrets:is_synced(namespace, name)
local es = api_get(API_BASE .. "/namespaces/" .. namespace .. "/externalsecrets/" .. name)
if not es then return false end
local conditions = (es.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
return ready_cond ~= nil and ready_cond.status == "True"
end
function c.external_secrets:wait_synced(namespace, name, timeout_secs)
timeout_secs = timeout_secs or 60
local elapsed = 0
while elapsed < timeout_secs do
local synced = c.external_secrets:is_synced(namespace, name)
if synced then return true end
sleep(5)
elapsed = elapsed + 5
end
error("eso: ExternalSecret " .. namespace .. "/" .. name .. " not synced after " .. timeout_secs .. "s")
end
function c.external_secrets:all_synced(namespace)
local list = c.external_secrets:list(namespace)
local synced = 0
local failed = 0
local total = 0
local failed_names = {}
for _, es in ipairs(list.items or {}) do
total = total + 1
local conditions = (es.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
if ready_cond and ready_cond.status == "True" then
synced = synced + 1
else
failed = failed + 1
failed_names[#failed_names + 1] = es.metadata.name
end
end
return { synced = synced, failed = failed, total = total, failed_names = failed_names }
end
c.secret_stores = {}
function c.secret_stores:list(namespace)
return api_list(API_BASE .. "/namespaces/" .. namespace .. "/secretstores")
end
function c.secret_stores:get(namespace, name)
return api_get(API_BASE .. "/namespaces/" .. namespace .. "/secretstores/" .. name)
end
function c.secret_stores:status(namespace, name)
local ss = api_get(API_BASE .. "/namespaces/" .. namespace .. "/secretstores/" .. name)
if not ss then
error("eso: SecretStore " .. namespace .. "/" .. name .. " not found")
end
local conditions = (ss.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
return {
ready = ready_cond ~= nil and ready_cond.status == "True",
conditions = conditions,
}
end
function c.secret_stores:is_ready(namespace, name)
local ss = api_get(API_BASE .. "/namespaces/" .. namespace .. "/secretstores/" .. name)
if not ss then return false end
local conditions = (ss.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
return ready_cond ~= nil and ready_cond.status == "True"
end
function c.secret_stores:all_ready(namespace)
local list = c.secret_stores:list(namespace)
local ready = 0
local not_ready = 0
local total = 0
local not_ready_names = {}
for _, ss in ipairs(list.items or {}) do
total = total + 1
local conditions = (ss.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
if ready_cond and ready_cond.status == "True" then
ready = ready + 1
else
not_ready = not_ready + 1
not_ready_names[#not_ready_names + 1] = ss.metadata.name
end
end
return { ready = ready, not_ready = not_ready, total = total, not_ready_names = not_ready_names }
end
c.cluster_secret_stores = {}
function c.cluster_secret_stores:list()
return api_list(API_BASE .. "/clustersecretstores")
end
function c.cluster_secret_stores:get(name)
return api_get(API_BASE .. "/clustersecretstores/" .. name)
end
function c.cluster_secret_stores:is_ready(name)
local css = api_get(API_BASE .. "/clustersecretstores/" .. name)
if not css then return false end
local conditions = (css.status or {}).conditions or {}
local ready_cond = find_condition(conditions, "Ready")
return ready_cond ~= nil and ready_cond.status == "True"
end
c.cluster_external_secrets = {}
function c.cluster_external_secrets:list()
return api_list(API_BASE .. "/clusterexternalsecrets")
end
function c.cluster_external_secrets:get(name)
return api_get(API_BASE .. "/clusterexternalsecrets/" .. name)
end
return c
end
return M