Add --add-domain, --remove-domain, --set-domains flags to C and Python CLIs
Post-creation custom domain management for services. All three flags
require --domains with a comma-separated list. Calls PUT /services/{id}/domains
with add, remove, or custom_domains action. CLI_SPEC.md updated.
This commit is contained in:
parent
2ae90fce12
commit
cf07b22a72
3 changed files with 208 additions and 0 deletions
|
|
@ -140,6 +140,9 @@ un service --unfreeze ID # Resume service
|
||||||
un service --destroy ID # Delete service
|
un service --destroy ID # Delete service
|
||||||
un service --lock ID # Prevent deletion
|
un service --lock ID # Prevent deletion
|
||||||
un service --unlock ID # Allow deletion
|
un service --unlock ID # Allow deletion
|
||||||
|
un service --add-domain ID --domains example.com # Add domains
|
||||||
|
un service --remove-domain ID --domains example.com # Remove domains
|
||||||
|
un service --set-domains ID --domains a.com,b.com # Replace all domains
|
||||||
un service --execute ID 'cmd' # Run command in service
|
un service --execute ID 'cmd' # Run command in service
|
||||||
un service --redeploy ID # Re-run bootstrap
|
un service --redeploy ID # Re-run bootstrap
|
||||||
un service --snapshot ID # Create snapshot
|
un service --snapshot ID # Create snapshot
|
||||||
|
|
@ -165,6 +168,9 @@ un service --snapshot ID # Create snapshot
|
||||||
| `--destroy ID` | Delete service |
|
| `--destroy ID` | Delete service |
|
||||||
| `--lock ID` | Prevent deletion |
|
| `--lock ID` | Prevent deletion |
|
||||||
| `--unlock ID` | Allow deletion |
|
| `--unlock ID` | Allow deletion |
|
||||||
|
| `--add-domain ID` | Add domains (requires --domains) |
|
||||||
|
| `--remove-domain ID` | Remove domains (requires --domains) |
|
||||||
|
| `--set-domains ID` | Replace all domains (requires --domains) |
|
||||||
| `--auto-unfreeze ID` | Enable auto-unfreeze on HTTP request |
|
| `--auto-unfreeze ID` | Enable auto-unfreeze on HTTP request |
|
||||||
| `--no-auto-unfreeze ID` | Disable auto-unfreeze |
|
| `--no-auto-unfreeze ID` | Disable auto-unfreeze |
|
||||||
| `--show-freeze-page ID` | Show payment page when frozen (default) |
|
| `--show-freeze-page ID` | Show payment page when frozen (default) |
|
||||||
|
|
|
||||||
|
|
@ -3766,6 +3766,115 @@ static int unlock_service(const UnsandboxCredentials *creds, const char *service
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update custom domains for a service
|
||||||
|
// action: "custom_domains" (full replace), "add", or "remove"
|
||||||
|
// domains: comma-separated domain list
|
||||||
|
static int update_service_domains(const UnsandboxCredentials *creds, const char *service_id, const char *action, const char *domains) {
|
||||||
|
CURL *curl = curl_easy_init();
|
||||||
|
if (!curl) return 1;
|
||||||
|
|
||||||
|
struct ResponseBuffer response = {0};
|
||||||
|
response.data = malloc(1);
|
||||||
|
response.size = 0;
|
||||||
|
|
||||||
|
char url[512];
|
||||||
|
snprintf(url, sizeof(url), "%s/services/%s/domains", API_BASE, service_id);
|
||||||
|
|
||||||
|
char path[256];
|
||||||
|
snprintf(path, sizeof(path), "/services/%s/domains", service_id);
|
||||||
|
|
||||||
|
// Build JSON payload: {"action": ["domain1", "domain2"]}
|
||||||
|
size_t payload_size = strlen(domains) * 2 + 256;
|
||||||
|
char *payload = malloc(payload_size);
|
||||||
|
if (!payload) {
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
char *p = payload;
|
||||||
|
p += sprintf(p, "{\"%s\":[", action);
|
||||||
|
|
||||||
|
char *domains_copy = strdup(domains);
|
||||||
|
char *token = strtok(domains_copy, ",");
|
||||||
|
int first = 1;
|
||||||
|
while (token) {
|
||||||
|
while (*token == ' ') token++;
|
||||||
|
char *end = token + strlen(token) - 1;
|
||||||
|
while (end > token && *end == ' ') *end-- = '\0';
|
||||||
|
if (!first) p += sprintf(p, ",");
|
||||||
|
char *esc = escape_json_string(token);
|
||||||
|
p += sprintf(p, "\"%s\"", esc);
|
||||||
|
free(esc);
|
||||||
|
first = 0;
|
||||||
|
token = strtok(NULL, ",");
|
||||||
|
}
|
||||||
|
free(domains_copy);
|
||||||
|
p += sprintf(p, "]}");
|
||||||
|
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
headers = add_hmac_auth_headers(headers, creds, "PUT", path, payload);
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||||
|
|
||||||
|
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);
|
||||||
|
free(payload);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
|
||||||
|
free(response.data);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_code == 404) {
|
||||||
|
fprintf(stderr, "Error: Service not found\n");
|
||||||
|
free(response.data);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_code == 403) {
|
||||||
|
fprintf(stderr, "Error: Not authorized to modify this service\n");
|
||||||
|
free(response.data);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_code != 200) {
|
||||||
|
fprintf(stderr, "Error: HTTP %ld\n", http_code);
|
||||||
|
if (response.data) fprintf(stderr, "%s\n", response.data);
|
||||||
|
free(response.data);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\033[32mCustom domains updated successfully\033[0m\n");
|
||||||
|
// Print resulting domains from response
|
||||||
|
if (response.data) {
|
||||||
|
// Quick parse for custom_domains array
|
||||||
|
char *cd = strstr(response.data, "\"custom_domains\"");
|
||||||
|
if (cd) {
|
||||||
|
char *start = strchr(cd, '[');
|
||||||
|
char *end = start ? strchr(start, ']') : NULL;
|
||||||
|
if (start && end) {
|
||||||
|
char tmp = *(end + 1);
|
||||||
|
*(end + 1) = '\0';
|
||||||
|
printf("Domains: %s\n", start);
|
||||||
|
*(end + 1) = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(response.data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Set unfreeze_on_demand for a service
|
// Set unfreeze_on_demand for a service
|
||||||
static int set_unfreeze_on_demand(const UnsandboxCredentials *creds, const char *service_id, int enabled) {
|
static int set_unfreeze_on_demand(const UnsandboxCredentials *creds, const char *service_id, int enabled) {
|
||||||
CURL *curl = curl_easy_init();
|
CURL *curl = curl_easy_init();
|
||||||
|
|
@ -9735,6 +9844,9 @@ int main(int argc, char *argv[]) {
|
||||||
const char *restore_snapshot_id = NULL;
|
const char *restore_snapshot_id = NULL;
|
||||||
const char *snapshot_name = NULL;
|
const char *snapshot_name = NULL;
|
||||||
int hot_snapshot = 0;
|
int hot_snapshot = 0;
|
||||||
|
int do_add_domain = 0;
|
||||||
|
int do_remove_domain = 0;
|
||||||
|
int do_set_domains = 0;
|
||||||
int create_unfreeze_on_demand = 0; // For --unfreeze-on-demand flag on create
|
int create_unfreeze_on_demand = 0; // For --unfreeze-on-demand flag on create
|
||||||
|
|
||||||
// Environment variables for service create
|
// Environment variables for service create
|
||||||
|
|
@ -9877,6 +9989,18 @@ int main(int argc, char *argv[]) {
|
||||||
do_unlock = 1;
|
do_unlock = 1;
|
||||||
i++;
|
i++;
|
||||||
service_id = argv[i];
|
service_id = argv[i];
|
||||||
|
} else if (strcmp(argv[i], "--add-domain") == 0 && i + 1 < argc) {
|
||||||
|
do_add_domain = 1;
|
||||||
|
i++;
|
||||||
|
service_id = argv[i];
|
||||||
|
} else if (strcmp(argv[i], "--remove-domain") == 0 && i + 1 < argc) {
|
||||||
|
do_remove_domain = 1;
|
||||||
|
i++;
|
||||||
|
service_id = argv[i];
|
||||||
|
} else if (strcmp(argv[i], "--set-domains") == 0 && i + 1 < argc) {
|
||||||
|
do_set_domains = 1;
|
||||||
|
i++;
|
||||||
|
service_id = argv[i];
|
||||||
} else if (strcmp(argv[i], "--auto-unfreeze") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--auto-unfreeze") == 0 && i + 1 < argc) {
|
||||||
do_auto_unfreeze = 1;
|
do_auto_unfreeze = 1;
|
||||||
i++;
|
i++;
|
||||||
|
|
@ -10086,6 +10210,30 @@ int main(int argc, char *argv[]) {
|
||||||
ret = lock_service(creds, service_id);
|
ret = lock_service(creds, service_id);
|
||||||
} else if (do_unlock) {
|
} else if (do_unlock) {
|
||||||
ret = unlock_service(creds, service_id);
|
ret = unlock_service(creds, service_id);
|
||||||
|
} else if (do_add_domain) {
|
||||||
|
if (!service_domains) {
|
||||||
|
fprintf(stderr, "Error: --domains required with --add-domain\n");
|
||||||
|
curl_global_cleanup();
|
||||||
|
free_credentials(creds);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ret = update_service_domains(creds, service_id, "add", service_domains);
|
||||||
|
} else if (do_remove_domain) {
|
||||||
|
if (!service_domains) {
|
||||||
|
fprintf(stderr, "Error: --domains required with --remove-domain\n");
|
||||||
|
curl_global_cleanup();
|
||||||
|
free_credentials(creds);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ret = update_service_domains(creds, service_id, "remove", service_domains);
|
||||||
|
} else if (do_set_domains) {
|
||||||
|
if (!service_domains) {
|
||||||
|
fprintf(stderr, "Error: --domains required with --set-domains\n");
|
||||||
|
curl_global_cleanup();
|
||||||
|
free_credentials(creds);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ret = update_service_domains(creds, service_id, "custom_domains", service_domains);
|
||||||
} else if (do_auto_unfreeze) {
|
} else if (do_auto_unfreeze) {
|
||||||
ret = set_unfreeze_on_demand(creds, service_id, 1);
|
ret = set_unfreeze_on_demand(creds, service_id, 1);
|
||||||
} else if (do_no_auto_unfreeze) {
|
} else if (do_no_auto_unfreeze) {
|
||||||
|
|
|
||||||
|
|
@ -1305,6 +1305,30 @@ def unlock_service(
|
||||||
return _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
|
return _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
|
||||||
|
|
||||||
|
|
||||||
|
def update_service_domains(
|
||||||
|
service_id: str,
|
||||||
|
domains: list,
|
||||||
|
action: str = "custom_domains",
|
||||||
|
public_key: Optional[str] = None,
|
||||||
|
secret_key: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Update custom domains for a service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
service_id: Service ID
|
||||||
|
domains: List of domain strings
|
||||||
|
action: "custom_domains" (replace), "add" (merge), or "remove" (subtract)
|
||||||
|
public_key: Optional API key
|
||||||
|
secret_key: Optional API secret
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response dict with updated domains
|
||||||
|
"""
|
||||||
|
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||||
|
return _make_request("PUT", f"/services/{service_id}/domains", public_key, secret_key, {action: domains})
|
||||||
|
|
||||||
|
|
||||||
def set_unfreeze_on_demand(
|
def set_unfreeze_on_demand(
|
||||||
service_id: str,
|
service_id: str,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
|
@ -2362,6 +2386,12 @@ Examples:
|
||||||
help="Prevent deletion")
|
help="Prevent deletion")
|
||||||
service_group.add_argument("--unlock", metavar="ID",
|
service_group.add_argument("--unlock", metavar="ID",
|
||||||
help="Allow deletion")
|
help="Allow deletion")
|
||||||
|
service_group.add_argument("--add-domain", metavar="ID",
|
||||||
|
help="Add domains (with --domains)")
|
||||||
|
service_group.add_argument("--remove-domain", metavar="ID",
|
||||||
|
help="Remove domains (with --domains)")
|
||||||
|
service_group.add_argument("--set-domains", metavar="ID",
|
||||||
|
help="Replace all domains (with --domains)")
|
||||||
service_group.add_argument("--resize", metavar="ID",
|
service_group.add_argument("--resize", metavar="ID",
|
||||||
help="Resize service (with --vcpu)")
|
help="Resize service (with --vcpu)")
|
||||||
service_group.add_argument("--redeploy", metavar="ID",
|
service_group.add_argument("--redeploy", metavar="ID",
|
||||||
|
|
@ -2667,6 +2697,30 @@ def _handle_service_command(args, public_key: str, secret_key: str):
|
||||||
elif args.unlock:
|
elif args.unlock:
|
||||||
result = unlock_service(args.unlock, public_key, secret_key)
|
result = unlock_service(args.unlock, public_key, secret_key)
|
||||||
print(f"Service {args.unlock} unlocked")
|
print(f"Service {args.unlock} unlocked")
|
||||||
|
elif getattr(args, 'add_domain', None):
|
||||||
|
if not args.domains:
|
||||||
|
print("Error: --domains required with --add-domain", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
domains = [d.strip() for d in args.domains.split(",")]
|
||||||
|
result = update_service_domains(args.add_domain, domains, "add", public_key, secret_key)
|
||||||
|
print(f"Domains added to {args.add_domain}")
|
||||||
|
print(f"Domains: {result.get('custom_domains', [])}")
|
||||||
|
elif getattr(args, 'remove_domain', None):
|
||||||
|
if not args.domains:
|
||||||
|
print("Error: --domains required with --remove-domain", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
domains = [d.strip() for d in args.domains.split(",")]
|
||||||
|
result = update_service_domains(args.remove_domain, domains, "remove", public_key, secret_key)
|
||||||
|
print(f"Domains removed from {args.remove_domain}")
|
||||||
|
print(f"Domains: {result.get('custom_domains', [])}")
|
||||||
|
elif getattr(args, 'set_domains', None):
|
||||||
|
if not args.domains:
|
||||||
|
print("Error: --domains required with --set-domains", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
domains = [d.strip() for d in args.domains.split(",")]
|
||||||
|
result = update_service_domains(args.set_domains, domains, "custom_domains", public_key, secret_key)
|
||||||
|
print(f"Domains set for {args.set_domains}")
|
||||||
|
print(f"Domains: {result.get('custom_domains', [])}")
|
||||||
elif args.resize:
|
elif args.resize:
|
||||||
vcpu = getattr(args, 'vcpu', 1) or 1
|
vcpu = getattr(args, 'vcpu', 1) or 1
|
||||||
result = update_service(args.resize, public_key, secret_key, vcpu=vcpu)
|
result = update_service(args.resize, public_key, secret_key, vcpu=vcpu)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue