CUDly is a self-hosted tool. Two CLI subcommands handle the one-time credential bootstrap for Azure and GCP: configure-azure and configure-gcp. Both write credentials to AWS Secrets Manager, where the CUDly server reads them at runtime.
These are one-time setup operations, not part of the regular analysis/purchase workflow.
For the full Terraform deployment guide (supported runtimes, quick-deploy script, manual steps, tfvars reference, accessing the dashboard after deploy), see the Deployment (self-hosted via Terraform) section in the README.
The ci-cd-permissions/ module within each environment provisions the CI/CD deploy identity and is applied once, manually, by a privileged operator. The main deploy workflow (.github/workflows/deploy-*.yml) then runs terraform init / plan / apply against the environment directory using OIDC keyless authentication.
- An AWS profile with
secretsmanager:ListSecretsandsecretsmanager:UpdateSecretpermissions on the secrets created by the Terraform deployment. - The target Secrets Manager secret must already exist (created by the Terraform deployment). Both commands locate the secret by listing secrets with a name prefix (
<stack-name>-AzureCredentialsor<stack-name>-GCPCredentials) and updating the first match.
Store Azure Service Principal credentials in Secrets Manager.
cudly configure-azure [flags]
| Flag | Default | Description |
|---|---|---|
--stack-name |
cudly |
Deployment stack name prefix. Used to locate the Secrets Manager secret (<stack-name>-AzureCredentials). Must match the name used when the Terraform deployment created the secret. |
--profile |
(AWS default chain) | AWS profile to use when writing to Secrets Manager. |
--tenant-id |
Azure AD Tenant ID (UUID format). | |
--client-id |
Azure Service Principal Client ID (UUID format). | |
--client-secret |
Azure Service Principal Client Secret. Read securely from stdin if omitted. | |
--subscription-id |
Azure Subscription ID (UUID format). | |
--interactive / -i |
false |
Prompt for all credential fields interactively, even if some are provided as flags. |
--skip-setup |
false |
Skip the guided Azure CLI steps (az login, az account list, az ad sp create-for-rbac). Use when you already have a Service Principal and just want to store the credentials. |
When --skip-setup is not set, the command runs an interactive guided flow:
az login- opens a browser window for Azure authentication (can be skipped at the prompt).az account list --output table- lists subscriptions so you can identify the Subscription ID.az ad sp create-for-rbac --name CUDly --role "Reservations Administrator" --scopes /subscriptions/<id>- creates a Service Principal with the correct role (can be skipped).
After the guided steps (or immediately with --skip-setup), the command prompts for or accepts any missing credential fields and writes them as JSON to the <stack-name>-AzureCredentials secret.
# Provide all credentials as flags (--client-secret is read from a variable to avoid shell history)
AZURE_SECRET="$(cat /run/secrets/azure-client-secret)"
cudly configure-azure \
--stack-name prod-cudly \
--profile cudly-admin \
--tenant-id 12345678-1234-1234-1234-123456789012 \
--client-id 87654321-4321-4321-4321-210987654321 \
--client-secret "$AZURE_SECRET" \
--subscription-id aaaabbbb-cccc-dddd-eeee-ffffaaaabbbb \
--skip-setup# Let the command guide you through Azure CLI setup and credential collection
cudly configure-azure --stack-name prod-cudly --profile cudly-adminThe Service Principal must have the Reservations Administrator role at the subscription scope. This is the minimum role needed to create reservation purchases.
# Example: grant the role manually if the guided step was skipped
az role assignment create \
--assignee "<client-id>" \
--role "Reservations Administrator" \
--scope "/subscriptions/<subscription-id>"Store GCP Service Account credentials in Secrets Manager.
cudly configure-gcp [flags]
| Flag | Short | Default | Description |
|---|---|---|---|
--stack-name |
cudly |
Deployment stack name prefix. Used to locate the secret (<stack-name>-GCPCredentials). Must match the name used when the Terraform deployment created the secret. |
|
--profile |
(AWS default chain) | AWS profile to use when writing to Secrets Manager. | |
--credentials-file |
-f |
Path to a GCP Service Account JSON key file. Supports ~ expansion. |
|
--project-id |
GCP Project ID. Overrides the value embedded in the credentials file if set. | ||
--interactive / -i |
false |
Prompt for the credentials file path interactively. | |
--skip-setup |
false |
Skip the guided gcloud steps (gcloud auth login, create service account, grant roles, create key). Use when you already have a JSON key file. |
When --skip-setup is not set, the command runs an interactive guided flow:
gcloud auth login- opens a browser window for GCP authentication.gcloud projects list- lists projects so you can identify your Project ID.gcloud config set project <id>- sets the active project.- Creates a
cudly-service-accountService Account with the display name "CUDly Service Account". - Grant IAM roles via SDK - creates or validates the project custom role
cudlyCommitmentPurchaser, then grants it androles/compute.viewerto the Service Account. gcloud iam service-accounts keys create ~/cudly-gcp-key.json- downloads a JSON key to your home directory.
After the guided steps (or with --skip-setup --credentials-file <path>), the command reads and validates the JSON file and writes it to the <stack-name>-GCPCredentials secret.
# Store an existing key file
cudly configure-gcp \
--stack-name prod-cudly \
--profile cudly-admin \
--credentials-file ~/cudly-gcp-key.json \
--skip-setup# Let the command guide you through gcloud setup
cudly configure-gcp --stack-name prod-cudly --profile cudly-adminThe Service Account needs the following roles:
| Role | Purpose |
|---|---|
roles/compute.viewer |
Read Compute Engine resources and commitment operations |
projects/PROJECT_ID/roles/cudlyCommitmentPurchaser |
Purchase commitments with only compute.commitments.create |
The setup operator needs iam.roles.get, iam.roles.create,
resourcemanager.projects.getIamPolicy, and resourcemanager.projects.setIamPolicy
for this step. These setup permissions are not granted to the Service Account.
An existing custom role must have exactly the purchase permission and be enabled;
the wizard refuses incompatible roles rather than changing them. It also refuses
to widen an existing conditional-only grant for this Service Account.
Rerunning the wizard does not remove broad grants from older installations.
Review existing roles/compute.admin grants separately. Recommendation access
requires additional Recommender permissions; neither role above provides them.
If you manage Cloud SQL or Memorystore commitments, you may need additional roles. Check the GCP documentation for the minimum required permissions per commitment type.
The command expects a standard GCP Service Account JSON key file (type service_account) with at minimum:
{
"type": "service_account",
"project_id": "your-project-id",
"client_email": "cudly-service-account@your-project-id.iam.gserviceaccount.com",
"private_key": "<YOUR_SERVICE_ACCOUNT_PRIVATE_KEY>"
}Any missing required field causes the command to exit with an error before writing to Secrets Manager.
| Scenario | Notes |
|---|---|
| First-time setup | Deploy with Terraform first (terraform/environments/<cloud>/). The secrets are created as part of that deployment; these commands only update them. |
| Rotating credentials | Both commands update (not create) the secret, so they can be run again to rotate credentials without redeploying. |