fix(c): Use static CLI functions instead of library API
The CLI was using library-only types and functions (unsandbox_languages_t, unsandbox_image_list_t, etc.) that are only available when UNSANDBOX_LIBRARY is defined. This caused build failures when building as CLI. Changes: - Add static list_languages_cli() for CLI language listing - Fix image list to parse JSON directly instead of using library types - Fix spawn_from_image() call with missing bootstrap/network_mode args - Fix image_publish/clone_image calls to use static functions Also update CLAUDE.md with release/versioning documentation.
This commit is contained in:
parent
af995cdc1a
commit
cc7674f7d7
3 changed files with 177 additions and 47 deletions
33
CLAUDE.md
33
CLAUDE.md
|
|
@ -272,6 +272,39 @@ git remote set-url --add --push origin ssh://git@git.unturf.com:2222/engineering
|
|||
git remote set-url --add --push origin git@github.com:russellballestrini/un-inception.git
|
||||
```
|
||||
|
||||
## Releases & Versioning
|
||||
|
||||
**ALWAYS update the VERSION file BEFORE creating a release tag.**
|
||||
|
||||
The `VERSION` file in the repo root contains the current semantic version (e.g., `0.0.2`). This file is the source of truth for the release version.
|
||||
|
||||
### Release Process
|
||||
|
||||
```bash
|
||||
# 1. Update VERSION file first
|
||||
echo "0.0.3" > VERSION
|
||||
|
||||
# 2. Commit the version bump
|
||||
git add VERSION
|
||||
git commit -m "chore: Bump version to 0.0.3"
|
||||
|
||||
# 3. Create and push the tag (triggers full 42-language test matrix)
|
||||
git tag -a v0.0.3 -m "Release v0.0.3 - Description of changes"
|
||||
git push origin main v0.0.3
|
||||
```
|
||||
|
||||
### Tag Triggers
|
||||
|
||||
- **Tag push** (`v*.*.*`) triggers the **full test matrix** with all 42 languages
|
||||
- **Regular push to main** only tests changed SDKs (smart detection)
|
||||
|
||||
### Version Format
|
||||
|
||||
Use semantic versioning: `MAJOR.MINOR.PATCH`
|
||||
- `MAJOR` - Breaking API changes
|
||||
- `MINOR` - New features, backward compatible
|
||||
- `PATCH` - Bug fixes, backward compatible
|
||||
|
||||
## SDK Testing Philosophy
|
||||
|
||||
**SDKs are LIBRARIES for embedding in other people's code.** They are NOT just CLIs.
|
||||
|
|
|
|||
|
|
@ -1834,6 +1834,87 @@ static int list_sessions(const UnsandboxCredentials *creds) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// List supported languages (CLI helper)
|
||||
static int list_languages_cli(const UnsandboxCredentials *creds, int json_output) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return 1;
|
||||
|
||||
struct ResponseBuffer response = {0};
|
||||
response.data = malloc(1);
|
||||
response.size = 0;
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s/languages", API_BASE);
|
||||
|
||||
struct curl_slist *headers = NULL;
|
||||
headers = add_hmac_auth_headers(headers, creds, "GET", "/languages", NULL);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "un-cli/2.0");
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || http_code != 200) {
|
||||
fprintf(stderr, "Error: Failed to fetch languages (HTTP %ld)\n", http_code);
|
||||
free(response.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find the languages array in JSON: "languages":["lang1","lang2",...] or just ["lang1",...]
|
||||
const char *langs_start = strstr(response.data, "\"languages\":[");
|
||||
if (!langs_start) {
|
||||
langs_start = strchr(response.data, '[');
|
||||
}
|
||||
|
||||
if (!langs_start) {
|
||||
fprintf(stderr, "Error: Invalid response format\n");
|
||||
free(response.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *arr_start = strchr(langs_start, '[');
|
||||
if (!arr_start) {
|
||||
fprintf(stderr, "Error: Invalid response format\n");
|
||||
free(response.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (json_output) {
|
||||
// Output as JSON array - find matching ]
|
||||
const char *arr_end = strchr(arr_start, ']');
|
||||
if (arr_end) {
|
||||
int len = arr_end - arr_start + 1;
|
||||
printf("%.*s\n", len, arr_start);
|
||||
}
|
||||
} else {
|
||||
// Output one language per line
|
||||
const char *p = arr_start + 1;
|
||||
while (*p && *p != ']') {
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
const char *end = strchr(p, '"');
|
||||
if (end) {
|
||||
printf("%.*s\n", (int)(end - p), p);
|
||||
p = end + 1;
|
||||
}
|
||||
} else {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(response.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a session via HTTP API
|
||||
// multiplexer: NULL for no multiplexer (default), "screen", or "tmux"
|
||||
// input_files: array of files to include (written to /tmp/ in container)
|
||||
|
|
@ -8336,33 +8417,10 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
unsandbox_languages_t *langs = unsandbox_get_languages(creds->public_key, creds->secret_key);
|
||||
int ret = list_languages_cli(creds, json_output);
|
||||
curl_global_cleanup();
|
||||
|
||||
if (!langs) {
|
||||
fprintf(stderr, "Error: Failed to fetch languages list\n");
|
||||
free_credentials(creds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (json_output) {
|
||||
// Output as JSON array
|
||||
printf("[");
|
||||
for (int i = 0; i < langs->count; i++) {
|
||||
printf("\"%s\"", langs->languages[i]);
|
||||
if (i < langs->count - 1) printf(",");
|
||||
}
|
||||
printf("]\n");
|
||||
} else {
|
||||
// Output one per line (pipe-friendly)
|
||||
for (int i = 0; i < langs->count; i++) {
|
||||
printf("%s\n", langs->languages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
unsandbox_free_languages(langs);
|
||||
free_credentials(creds);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Check for snapshot command
|
||||
|
|
@ -8595,26 +8653,66 @@ int main(int argc, char *argv[]) {
|
|||
if (do_list) {
|
||||
char *response = list_images(creds, NULL);
|
||||
if (response) {
|
||||
// Parse and display image list
|
||||
unsandbox_image_list_t *images = unsandbox_image_list(creds->public_key, creds->secret_key);
|
||||
if (images && images->count > 0) {
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n", "ID", "NAME", "VISIBILITY", "LOCKED", "SOURCE");
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n", "----------------------------------------",
|
||||
"--------------------", "----------", "--------", "----------");
|
||||
for (size_t i = 0; i < images->count; i++) {
|
||||
unsandbox_image_t *img = &images->images[i];
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n",
|
||||
img->id ? img->id : "",
|
||||
img->name ? img->name : "",
|
||||
img->visibility ? img->visibility : "",
|
||||
img->locked ? "yes" : "no",
|
||||
img->source_type ? img->source_type : "");
|
||||
}
|
||||
unsandbox_free_image_list(images);
|
||||
} else {
|
||||
// Parse and display image list from JSON response
|
||||
const char *images_start = strstr(response, "\"images\":[");
|
||||
if (!images_start) {
|
||||
printf("No images found.\n");
|
||||
free(response);
|
||||
} else {
|
||||
// Count images
|
||||
int count = 0;
|
||||
const char *p = images_start;
|
||||
while ((p = strchr(p + 1, '{')) != NULL && strchr(images_start, ']') && p < strchr(images_start, ']')) {
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
printf("No images found.\n");
|
||||
} else {
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n", "ID", "NAME", "VISIBILITY", "LOCKED", "SOURCE");
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n", "----------------------------------------",
|
||||
"--------------------", "----------", "--------", "----------");
|
||||
|
||||
// Parse each image object
|
||||
const char *pos = strchr(images_start, '[');
|
||||
if (pos) {
|
||||
while ((pos = strchr(pos, '{')) != NULL) {
|
||||
// Check we haven't passed the end of the array
|
||||
const char *arr_end = strchr(images_start, ']');
|
||||
if (arr_end && pos > arr_end) break;
|
||||
|
||||
char *img_id = extract_json_string(pos, "id");
|
||||
char *img_name = extract_json_string(pos, "name");
|
||||
char *img_visibility = extract_json_string(pos, "visibility");
|
||||
char *img_source = extract_json_string(pos, "source_type");
|
||||
|
||||
// Extract locked boolean
|
||||
const char *locked_str = strstr(pos, "\"locked\":");
|
||||
int locked = 0;
|
||||
if (locked_str && locked_str < strchr(pos, '}')) {
|
||||
locked_str += 9;
|
||||
while (*locked_str == ' ') locked_str++;
|
||||
locked = (strncmp(locked_str, "true", 4) == 0);
|
||||
}
|
||||
|
||||
printf("%-40s %-20s %-10s %-8s %-10s\n",
|
||||
img_id ? img_id : "",
|
||||
img_name ? img_name : "",
|
||||
img_visibility ? img_visibility : "",
|
||||
locked ? "yes" : "no",
|
||||
img_source ? img_source : "");
|
||||
|
||||
if (img_id) free(img_id);
|
||||
if (img_name) free(img_name);
|
||||
if (img_visibility) free(img_visibility);
|
||||
if (img_source) free(img_source);
|
||||
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(response);
|
||||
}
|
||||
free(response);
|
||||
} else {
|
||||
fprintf(stderr, "Error: Failed to list images\n");
|
||||
ret = 1;
|
||||
|
|
@ -8656,7 +8754,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
} else if (do_spawn) {
|
||||
char *result = spawn_from_image(creds, image_id, spawn_name, spawn_ports);
|
||||
char *result = spawn_from_image(creds, image_id, spawn_name, spawn_ports, NULL, NULL);
|
||||
if (result) {
|
||||
printf("%s\n", result);
|
||||
free(result);
|
||||
|
|
@ -8669,8 +8767,7 @@ int main(int argc, char *argv[]) {
|
|||
fprintf(stderr, "Error: --source-type required for --publish (service or snapshot)\n");
|
||||
ret = 1;
|
||||
} else {
|
||||
char *result = unsandbox_image_publish(creds->public_key, creds->secret_key,
|
||||
source_type, image_id, spawn_name, NULL);
|
||||
char *result = image_publish(creds, source_type, image_id, spawn_name, NULL);
|
||||
if (result) {
|
||||
printf("Image published: %s\n", result);
|
||||
free(result);
|
||||
|
|
@ -8680,7 +8777,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
} else if (do_clone) {
|
||||
char *result = unsandbox_image_clone(creds->public_key, creds->secret_key, image_id, spawn_name);
|
||||
char *result = clone_image(creds, image_id, spawn_name, NULL);
|
||||
if (result) {
|
||||
printf("Image cloned: %s\n", result);
|
||||
free(result);
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue