{"id":16862200,"url":"https://github.com/maelvls/c4","last_synced_at":"2025-09-05T21:43:42.011Z","repository":{"id":194441246,"uuid":"368799783","full_name":"maelvls/c4","owner":"maelvls","description":"Monitor and nuke unused VMs from GCP, AWS and OpenStack and save costs","archived":false,"fork":false,"pushed_at":"2023-09-12T16:15:31.000Z","size":42,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-18T15:56:26.788Z","etag":null,"topics":["aws","cli","gcp","openstack"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maelvls.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-05-19T08:37:18.000Z","updated_at":"2023-09-12T16:14:04.000Z","dependencies_parsed_at":"2023-09-13T10:38:42.383Z","dependency_job_id":null,"html_url":"https://github.com/maelvls/c4","commit_stats":null,"previous_names":["maelvls/c4"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/maelvls/c4","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maelvls%2Fc4","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maelvls%2Fc4/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maelvls%2Fc4/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maelvls%2Fc4/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maelvls","download_url":"https://codeload.github.com/maelvls/c4/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maelvls%2Fc4/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273826795,"owners_count":25175234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","cli","gcp","openstack"],"created_at":"2024-10-13T14:34:50.428Z","updated_at":"2025-09-05T21:43:41.979Z","avatar_url":"https://github.com/maelvls.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C4, the VM annihilator\n\n[![CI](https://github.com/maelvls/c4/workflows/CI/badge.svg)](https://github.com/maelvls/c4/actions)\n\n## Download and install\n\nGet the [binary](https://github.com/maelvls/c4/releases) or go get it\nwith:\n\n```sh\ngo install github.com/maelvls/c4@latest\n```\n\nYou can use `c4` to monitor all the VMs that are created for testing\npurposes using `watch --color c4`:\n\n![Demo of c4. This gif is hosted in the description of PR #1](https://user-images.githubusercontent.com/2195781/77187108-3ef3d680-6ad4-11ea-986a-eff98cda5b20.gif)\n\n## Why c4\n\nWe keep having many leftover VMs when running integration tests. `c4` aims\nat removing anything that costs $$. Claudia wanted to call it\n`small-spender` but I went to fast and called it `c4`.\n\n- **Why not use [cloud-nuke](https://github.com/gruntwork-io/cloud-nuke)?**\n  That's because they do much more than deleting VMs and SGs, and they are\n  specific to AWS.\n- **Why not duct-tape a bash script?** Because we want a `--older-than 1h`\n  flag, and doing that through scripting is a lot of pain.\n- **Why the hell did you use environment variables?** Env vars are bad and\n  hard to discover! That's because it allows me to use `.envrc` (direnv)\n  locally. To remediate the issue of discoverability, I did two things:\n  - environment variables must be not empty, so it's easy not to miss any,\n  - `--help` shows all the env vars with their description.\n\nImportant: for AWS and OpenStack, it will only focus on one specific\nregion. For GCP, it will work on all regions simultanously.\n\n## How is it automated?\n\nThe `c4` binary is built by Github Actions and uploaded as a Github\n[release](https://github.com/maelvls/c4/releases) using\n[goreleaser](https://github.com/goreleaser/goreleaser).\n\nYou can tell `c4` to look for VMs names with a regex `(test|example)` and\nwith `--older-than 1h` so that only \"old\" VMs get deleted. Whenever a VM\ngets deleted, `c4` is able to tell you through Slack. For example:\n\n## Usage\n\nInstall `v4`:\n\n```sh\ngo install github.com/maelvls/c4@latest\n```\n\nFirst, set a couple of variables (`direnv` is recommended):\n\n```sh\nexport GCP_JSON_KEY=...\nexport AWS_ACCESS_KEY_ID=...\nexport AWS_SECRET_ACCESS_KEY=...\nexport OS_USERNAME=...\nexport OS_PASSWORD=...\nexport SLACK_TOKEN=...\n```\n\nRun it to see the VMs that were found:\n\n```sh\nc4 --aws-regex=\"(test|example)\" --os-regex=\"(test|example)\" --gcp-regex=\"(test|example)\" --older-than=1h --slack-channel=test-channel\nRemoving anything older than 24h0m0s.\nNote: running in dry-mode. To actually delete things, add --do-it.\nfound aws instance test-aws-machine-crwvq (i-0d2efdf6b74578413), removing since age is 171h17m33.371533s\nfound aws instance test-machine-225dj (i-0e23754b0de933a8e), removing since age is 216h0m13.371556s\nfound aws instance test-aws-machine-2x48p (i-0d85e47e2e029b479), removing since age is 192h39m34.371561s\nfound aws instance test-aws-machine-72nmq (i-08c534433d1557efd), removing since age is 215h48m52.371565s\nfound aws instance test-aws-machine-mw7fr (i-0aa7b5c6805d22fd1), removing since age is 188h10m19.371569s\nfound openstack instance server-test-1 (39aa7efe-ff18-4144-9d62-d4cba84dbd47), keeping it since age is 2m37.175715s\n```\n\nThen, if you are OK with this list, you can add `--do-it` to the command to\nactually delete the VMs:\n\n```sh\nc4 --aws-regex=\"(test|example)\" --os-regex=\"(test|example)\" --gcp-regex=\"(test|example)\" --do-it --older-than=1h --slack-channel=test-channel\nRemoving anything older than 24h0m0s.\nfound aws instance test-aws-machine-crwvq (i-0d2efdf6b74578413), removing since age is 171h17m33.371533s\nfound aws instance test-machine-225dj (i-0e23754b0de933a8e), removing since age is 216h0m13.371556s\nfound aws instance test-aws-machine-2x48p (i-0d85e47e2e029b479), removing since age is 192h39m34.371561s\nfound aws instance test-aws-machine-72nmq (i-08c534433d1557efd), removing since age is 215h48m52.371565s\nfound aws instance test-aws-machine-mw7fr (i-0aa7b5c6805d22fd1), removing since age is 188h10m19.371569s\nfound openstack instance server-test-1 (39aa7efe-ff18-4144-9d62-d4cba84dbd47), keeping it since age is 2m37.175715s\n```\n\n## Help\n\n```sh\n% c4 --help\nUsage of c4:\n  -aws-regex string\n    \tSelects AWS instances where tag:Name contains this string. Example: (test|example) (default \".*\")\n  -do-it\n    \tBy default, nothing is deleted. This flag enable deletion.\n  -gcp-regex string\n    \tSelects OpenStack instances where the instance name contains this string. Example: (test|example) (default \".*\")\n  -older-than duration\n    \tOnly delete resources older than this specified value. Can be any valid Go duration, such as 10m or 8h. (default 24h0m0s)\n  -os-regex string\n    \tSelects OpenStack instances where the instance name contains this string. Example: (test|example) (default \".*\")\n  -slack-channel string\n    \tWith this argument, c4 sends a message to this channel whenever VMs are deleted (doesn't send anything when this flag isn't passed). Requires SLACK_TOKEN to be set.\n  -version\n    \tWatch out, returns 'n/a (commit none, built on unknown)' when built with 'go get'.\n\nEnvironment variables:\n  AWS_ACCESS_KEY_ID\n    \tThe AWS access key. (mandatory)\n  AWS_SECRET_ACCESS_KEY\n    \tThe AWS secret key. (mandatory)\n  AWS_REGION\n    \tThe AWS region. (mandatory)\n  OS_USERNAME\n    \t (mandatory)\n  OS_PASSWORD\n    \t (mandatory)\n  OS_AUTH_URL\n    \tOften looks like http://host/identity/v3. (mandatory)\n  OS_PROJECT_NAME\n    \tAlso called 'tenant name'. (mandatory)\n  OS_REGION\n    \tE.g., UK1 (for OVH). (mandatory)\n  OS_PROJECT_DOMAIN_NAME\n    \tThat's \"Default\" for most OpenStack instances. (mandatory)\n  GCP_JSON_KEY\n    \tThe content of the json key in plain text, not base-64 encoded. (mandatory)\n  SLACK_TOKEN\n    \tSlack OAuth token, create one at https://api.slack.com/apps.\n```\n\n## Slack\n\nOptionally, you can set `SLACK_TOKEN` and `--slack-channel` to send a Slack\nmessage that sums up all the VMs that were deleted:\n\n- create a [Slack App](https://api.slack.com/apps/) with the name `c4` (for\n  example),\n- add the Bot Token Scope `chat:write`,\n- Copy the OAuth Access Token and use it as `SLACK_TOKEN`,\n- Add `c4` to the channel you want the bot to be sending messages to.\n\n## Changelog\n\n## v1.1.0 - 04 July 2021\n\nYou can now use flags to set most values. For example, `GCP_JSON_KEY` can now be\nset using the flag `--gcp-json-key`.\n\nUnrelated but here are the commands you can use to create your GCP JSON key and\nstore it in [`lpass`](https://github.com/lastpass/lastpass-cli) (LastPass's\nCLI):\n\n```sh\ngcloud iam service-accounts create c4-cli --project $(gcloud config get-value project | tr ':' '/')\ngcloud projects add-iam-policy-binding jetstack-mael-valais --member=serviceAccount:c4-cli@$(gcloud config get-value project | tr ':' '/').iam.gserviceaccount.com --role=roles/compute.admin\nlpass edit c4-cli --non-interactive \u003c\u003cEOF\nPassword: $(gcloud iam service-accounts keys create /dev/stdout --iam-account c4-cli@$(gcloud config get-value project | tr ':' '/').iam.gserviceaccount.com | jq -c)\nEOF\n```\n\nThen you can run:\n\n```sh\nc4 --gcp-json-key=\"$(lpass show c4-cli -p)\"\n```\n\n## v1.0.2 - 11 March 2019\n\n- Bug: the Slack message was mixing up AWS and OpenStack, it now properly\n  shows what is what.\n\n## v1.0.1 - 9 March 2019\n\n- Bug: fix a bug where a Slack message was sent even when no VM had been\n  deleted.\n\n## v1.0.0 - 9 March 2019\n\n- Feature: use `--gcp-regex`, `--aws-regex` and `--os-regex` to filter\n  which VMs should be removed. You can test the regex using\n  \u003chttps://regex101.com\u003e (flavor: Golang).\n- Feature: to actually delete VMs, use `--do-it`. By default, it will run\n  in dry-run mode.\n- Feature: use `SLACK_TOKEN` and `--slack-channel` to let your team know\n  which VMs have been wiped.\n- Feature: credentials are passed through env variables. To list them, use\n  `--help`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaelvls%2Fc4","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaelvls%2Fc4","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaelvls%2Fc4/lists"}