Add service environment vault to all 40 un-inception implementations
Implements encrypted vault for storing service environment variables: - service env status <id> - Check vault status (GET /services/:id/env) - service env set <id> -e KEY=VAL - Set vault contents (PUT /services/:id/env) - service env export <id> - Export vault as .env format (POST /services/:id/env/export) - service env delete <id> - Delete vault (DELETE /services/:id/env) - Auto-vault on service creation with -e or --env-file flags All implementations use HMAC-SHA256 authentication and text/plain content type for vault PUT requests.
This commit is contained in:
parent
1b35f1099e
commit
87397949d1
40 changed files with 7523 additions and 291 deletions
226
un.ml
226
un.ml
|
|
@ -231,6 +231,151 @@ let curl_delete api_key endpoint =
|
|||
check_clock_drift output;
|
||||
output
|
||||
|
||||
let curl_put_text endpoint body =
|
||||
let (public_key, secret_key) = get_api_keys () in
|
||||
let auth_headers = build_auth_headers public_key secret_key "PUT" endpoint body in
|
||||
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.txt" (Random.int 999999) in
|
||||
let oc = open_out tmp_file in
|
||||
output_string oc body;
|
||||
close_out oc;
|
||||
let cmd = Printf.sprintf "curl -s -o /dev/null -w '%%{http_code}' -X PUT https://api.unsandbox.com%s -H 'Content-Type: text/plain'%s -d @%s"
|
||||
endpoint auth_headers tmp_file in
|
||||
let ic = Unix.open_process_in cmd in
|
||||
let status = try input_line ic with End_of_file -> "0" in
|
||||
let _ = Unix.close_process_in ic in
|
||||
Sys.remove tmp_file;
|
||||
let code = int_of_string (String.trim status) in
|
||||
code >= 200 && code < 300
|
||||
|
||||
let max_env_content_size = 65536
|
||||
|
||||
let read_env_file path =
|
||||
if not (Sys.file_exists path) then begin
|
||||
Printf.fprintf stderr "%sError: Env file not found: %s%s\n" red path reset;
|
||||
exit 1
|
||||
end;
|
||||
read_file path
|
||||
|
||||
let build_env_content envs env_file =
|
||||
let lines = ref envs in
|
||||
(match env_file with
|
||||
| Some path ->
|
||||
let content = read_env_file path in
|
||||
let file_lines = String.split_on_char '\n' content in
|
||||
List.iter (fun line ->
|
||||
let trimmed = String.trim line in
|
||||
if String.length trimmed > 0 && trimmed.[0] <> '#' then
|
||||
lines := trimmed :: !lines
|
||||
) file_lines
|
||||
| None -> ());
|
||||
String.concat "\n" (List.rev !lines)
|
||||
|
||||
let service_env_status service_id =
|
||||
let api_key = get_api_key () in
|
||||
curl_get api_key (Printf.sprintf "/services/%s/env" service_id)
|
||||
|
||||
let service_env_set service_id env_content =
|
||||
if String.length env_content > max_env_content_size then begin
|
||||
Printf.fprintf stderr "%sError: Env content exceeds maximum size of 64KB%s\n" red reset;
|
||||
false
|
||||
end else
|
||||
curl_put_text (Printf.sprintf "/services/%s/env" service_id) env_content
|
||||
|
||||
let service_env_export service_id =
|
||||
let api_key = get_api_key () in
|
||||
let (public_key, secret_key) = get_api_keys () in
|
||||
let endpoint = Printf.sprintf "/services/%s/env/export" service_id in
|
||||
let auth_headers = build_auth_headers public_key secret_key "POST" endpoint "{}" in
|
||||
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
|
||||
let oc = open_out tmp_file in
|
||||
output_string oc "{}";
|
||||
close_out oc;
|
||||
let cmd = Printf.sprintf "curl -s -X POST https://api.unsandbox.com%s -H 'Content-Type: application/json'%s -d @%s"
|
||||
endpoint auth_headers tmp_file in
|
||||
let ic = Unix.open_process_in cmd in
|
||||
let rec read_all acc =
|
||||
try let line = input_line ic in read_all (acc ^ line ^ "\n")
|
||||
with End_of_file -> acc
|
||||
in
|
||||
let response = read_all "" in
|
||||
let _ = Unix.close_process_in ic in
|
||||
Sys.remove tmp_file;
|
||||
response
|
||||
|
||||
let service_env_delete service_id =
|
||||
let api_key = get_api_key () in
|
||||
try
|
||||
let _ = curl_delete api_key (Printf.sprintf "/services/%s/env" service_id) in
|
||||
true
|
||||
with _ -> false
|
||||
|
||||
let service_env_command action target envs env_file =
|
||||
match action with
|
||||
| "status" ->
|
||||
(match target with
|
||||
| Some sid ->
|
||||
let response = service_env_status sid in
|
||||
let has_vault = match extract_json_value response "has_vault" with
|
||||
| Some "true" -> true
|
||||
| _ -> false
|
||||
in
|
||||
if has_vault then begin
|
||||
Printf.printf "%sVault: configured%s\n" green reset;
|
||||
(match extract_json_value response "env_count" with
|
||||
| Some c -> Printf.printf "Variables: %s\n" c
|
||||
| None -> ());
|
||||
(match extract_json_value response "updated_at" with
|
||||
| Some u -> Printf.printf "Updated: %s\n" u
|
||||
| None -> ())
|
||||
end else
|
||||
Printf.printf "%sVault: not configured%s\n" yellow reset
|
||||
| None ->
|
||||
Printf.fprintf stderr "%sError: service env status requires service ID%s\n" red reset;
|
||||
exit 1)
|
||||
| "set" ->
|
||||
(match target with
|
||||
| Some sid ->
|
||||
if envs = [] && env_file = None then begin
|
||||
Printf.fprintf stderr "%sError: service env set requires -e or --env-file%s\n" red reset;
|
||||
exit 1
|
||||
end;
|
||||
let env_content = build_env_content envs env_file in
|
||||
if service_env_set sid env_content then
|
||||
Printf.printf "%sVault updated for service %s%s\n" green sid reset
|
||||
else begin
|
||||
Printf.fprintf stderr "%sError: Failed to update vault%s\n" red reset;
|
||||
exit 1
|
||||
end
|
||||
| None ->
|
||||
Printf.fprintf stderr "%sError: service env set requires service ID%s\n" red reset;
|
||||
exit 1)
|
||||
| "export" ->
|
||||
(match target with
|
||||
| Some sid ->
|
||||
let response = service_env_export sid in
|
||||
(match extract_json_value response "content" with
|
||||
| Some content -> Printf.printf "%s" (unescape_json content)
|
||||
| None -> ())
|
||||
| None ->
|
||||
Printf.fprintf stderr "%sError: service env export requires service ID%s\n" red reset;
|
||||
exit 1)
|
||||
| "delete" ->
|
||||
(match target with
|
||||
| Some sid ->
|
||||
if service_env_delete sid then
|
||||
Printf.printf "%sVault deleted for service %s%s\n" green sid reset
|
||||
else begin
|
||||
Printf.fprintf stderr "%sError: Failed to delete vault%s\n" red reset;
|
||||
exit 1
|
||||
end
|
||||
| None ->
|
||||
Printf.fprintf stderr "%sError: service env delete requires service ID%s\n" red reset;
|
||||
exit 1)
|
||||
| _ ->
|
||||
Printf.fprintf stderr "%sError: Unknown env action: %s%s\n" red action reset;
|
||||
Printf.fprintf stderr "Usage: un.ml service env <status|set|export|delete> <service_id>\n";
|
||||
exit 1
|
||||
|
||||
(* Extract JSON value - simple regex-based parser *)
|
||||
let extract_json_value json_str key =
|
||||
let pattern = "\"" ^ key ^ "\"\\s*:\\s*\"\\([^\"]*\\)\"" in
|
||||
|
|
@ -465,9 +610,17 @@ let session_command action shell network vcpu input_files =
|
|||
| _ -> ()
|
||||
|
||||
(* Service command *)
|
||||
let service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files =
|
||||
let service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files envs env_file =
|
||||
let api_key = get_api_key () in
|
||||
match action with
|
||||
| "env" ->
|
||||
service_env_command (match name with Some n -> n | None -> "") (match ports with Some p -> Some p | None -> None) envs env_file
|
||||
| "env_cmd" ->
|
||||
(match (name, ports) with
|
||||
| (Some act, target) -> service_env_command act target envs env_file
|
||||
| _ ->
|
||||
Printf.fprintf stderr "Error: service env requires action\n";
|
||||
exit 1)
|
||||
| "list" ->
|
||||
let response = curl_get api_key "/services" in
|
||||
Printf.printf "%s\n" response
|
||||
|
|
@ -622,7 +775,17 @@ let service_command action name ports bootstrap bootstrap_file service_type netw
|
|||
let _ = Unix.close_process_in ic in
|
||||
Sys.remove tmp_file;
|
||||
Printf.printf "%sService created%s\n" green reset;
|
||||
Printf.printf "%s\n" response
|
||||
Printf.printf "%s\n" response;
|
||||
(* Auto-set vault if env vars were provided *)
|
||||
(match extract_json_value response "id" with
|
||||
| Some service_id when envs <> [] || env_file <> None ->
|
||||
let env_content = build_env_content envs env_file in
|
||||
if String.length env_content > 0 then
|
||||
if service_env_set service_id env_content then
|
||||
Printf.printf "%sVault configured with environment variables%s\n" green reset
|
||||
else
|
||||
Printf.printf "%sWarning: Failed to set vault%s\n" yellow reset
|
||||
| _ -> ())
|
||||
| None ->
|
||||
Printf.fprintf stderr "Error: --name required to create service\n";
|
||||
exit 1)
|
||||
|
|
@ -649,7 +812,10 @@ let () =
|
|||
Printf.printf "Usage: un.ml [options] <source_file>\n";
|
||||
Printf.printf " un.ml session [options]\n";
|
||||
Printf.printf " un.ml service [options]\n";
|
||||
Printf.printf " un.ml key [--extend]\n";
|
||||
Printf.printf " un.ml service env <action> <service_id>\n";
|
||||
Printf.printf " un.ml key [--extend]\n\n";
|
||||
Printf.printf "Service options: --name, --ports, --bootstrap, --bootstrap-file, -e KEY=VALUE, --env-file FILE\n";
|
||||
Printf.printf "Service env commands: status, set, export, delete\n";
|
||||
exit 1
|
||||
| "key" :: rest ->
|
||||
let extend = List.mem "--extend" rest in
|
||||
|
|
@ -675,28 +841,40 @@ let () =
|
|||
parse_session "create" None None None rest
|
||||
| "service" :: rest ->
|
||||
let input_files = parse_input_files [] rest in
|
||||
let rec parse_service action name ports bootstrap bootstrap_file service_type network vcpu = function
|
||||
| [] -> service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files
|
||||
| "--list" :: rest -> parse_service "list" name ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) bootstrap_file service_type network vcpu rest
|
||||
| "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) service_type network vcpu rest
|
||||
| "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap bootstrap_file service_type network vcpu rest
|
||||
| "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) bootstrap_file service_type network vcpu rest
|
||||
| "--bootstrap-file" :: f :: rest -> parse_service action name ports bootstrap (Some f) service_type network vcpu rest
|
||||
| "--type" :: t :: rest -> parse_service action name ports bootstrap bootstrap_file (Some t) network vcpu rest
|
||||
| "-n" :: net :: rest -> parse_service action name ports bootstrap bootstrap_file service_type (Some net) vcpu rest
|
||||
| "-v" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) rest
|
||||
| "-f" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu rest (* skip -f, already parsed *)
|
||||
| _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu rest
|
||||
let rec parse_envs acc = function
|
||||
| [] -> List.rev acc
|
||||
| "-e" :: kv :: rest -> parse_envs (kv :: acc) rest
|
||||
| _ :: rest -> parse_envs acc rest
|
||||
in
|
||||
parse_service "create" None None None None None None None rest
|
||||
let envs = parse_envs [] rest in
|
||||
let rec parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file = function
|
||||
| [] -> service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files envs env_file
|
||||
| "env" :: env_action :: target :: rest when not (String.length target > 0 && target.[0] = '-') ->
|
||||
parse_service "env_cmd" (Some env_action) (Some target) bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "env" :: env_action :: rest ->
|
||||
parse_service "env_cmd" (Some env_action) None bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--list" :: rest -> parse_service "list" name ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) bootstrap_file service_type network vcpu env_file rest
|
||||
| "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) service_type network vcpu env_file rest
|
||||
| "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
| "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) bootstrap_file service_type network vcpu env_file rest
|
||||
| "--bootstrap-file" :: f :: rest -> parse_service action name ports bootstrap (Some f) service_type network vcpu env_file rest
|
||||
| "--type" :: t :: rest -> parse_service action name ports bootstrap bootstrap_file (Some t) network vcpu env_file rest
|
||||
| "-n" :: net :: rest -> parse_service action name ports bootstrap bootstrap_file service_type (Some net) vcpu env_file rest
|
||||
| "-v" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) env_file rest
|
||||
| "--env-file" :: f :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu (Some f) rest
|
||||
| "-e" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest (* skip -e, already parsed *)
|
||||
| "-f" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest (* skip -f, already parsed *)
|
||||
| _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest
|
||||
in
|
||||
parse_service "create" None None None None None None None None rest
|
||||
| args ->
|
||||
let rec parse_execute file env_vars artifacts out_dir network vcpu = function
|
||||
| [] -> execute_command file env_vars artifacts out_dir network vcpu
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue