├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── CI.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── IMPLEMENTATION_COVERAGE.md ├── LICENSE ├── README.md ├── examples ├── analytic │ ├── emr │ │ ├── main.tf │ │ └── versions.tf │ └── msk │ │ ├── main.tf │ │ └── versions.tf ├── compute │ ├── ami-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── autoscaling-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── dlm-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── ebs-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── ec2-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── ecr-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── eks-nuke │ │ ├── maint.tf │ │ └── versions.tf │ ├── loadbalancing-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── snapshot-nuke │ │ ├── main.tf │ │ └── versions.tf │ └── spot-nuke │ │ ├── main.tf │ │ └── versions.tf ├── database │ ├── dynamodb │ │ ├── main.tf │ │ └── versions.tf │ ├── elasticache │ │ ├── main.tf │ │ └── versions.tf │ ├── rds │ │ ├── main.tf │ │ └── versions.tf │ └── redshift │ │ ├── main.tf │ │ └── versions.tf ├── governance │ └── cloudwatch │ │ ├── main.tf │ │ └── versions.tf ├── network │ ├── eip │ │ ├── main.tf │ │ └── versions.tf │ ├── endpoint │ │ ├── main.tf │ │ └── versions.tf │ ├── network_acl │ │ ├── main.tf │ │ └── versions.tf │ └── security_group │ │ ├── main.tf │ │ └── versions.tf ├── storage │ ├── efs-nuke │ │ ├── main.tf │ │ └── versions.tf │ ├── glacier │ │ ├── main.tf │ │ └── versions.tf │ └── s3 │ │ ├── main.tf │ │ └── versions.tf └── test_fixture │ ├── main.tf │ └── versions.tf ├── main.tf ├── outputs.tf ├── package └── nuke │ ├── __init__.py │ ├── analytic │ ├── emr.py │ └── kafka.py │ ├── client_connections.py │ ├── compute │ ├── __init__.py │ ├── ami.py │ ├── autoscaling.py │ ├── dlm.py │ ├── ebs.py │ ├── ec2.py │ ├── ecr.py │ ├── eks.py │ ├── elasticbeanstalk.py │ ├── elb.py │ ├── key_pair.py │ ├── snapshot.py │ └── spot.py │ ├── database │ ├── __init__.py │ ├── dynamodb.py │ ├── elasticache.py │ ├── rds.py │ └── redshift.py │ ├── exceptions.py │ ├── governance │ ├── __init__.py │ └── cloudwatch.py │ ├── main.py │ ├── network │ ├── __init__.py │ ├── eip.py │ ├── endpoint.py │ ├── network_acl.py │ └── security_group.py │ ├── storage │ ├── __init__.py │ ├── efs.py │ ├── glacier.py │ └── s3.py │ └── timeparse.py ├── pytest.ini ├── requirements-dev.txt ├── tests ├── __init__.py ├── sanity │ ├── .bandit.yml │ ├── .pylintrc │ └── terraform_tests.sh └── unit │ ├── __init__.py │ ├── analytic │ ├── __init__.py │ ├── test_emr_nuke.py │ └── utils.py │ ├── compute │ ├── __init__.py │ ├── test_ami_nuke.py │ ├── test_autoscaling_nuke.py │ ├── test_ebs_nuke.py │ ├── test_ec2_nuke.py │ ├── test_elb_nuke.py │ ├── test_keypair_nuke.py │ ├── test_snapshot_nuke.py │ ├── test_spot_nuke.py │ └── utils.py │ ├── database │ ├── __init__.py │ ├── test_redshift_nuke.py │ └── utils.py │ ├── governance │ ├── __init__.py │ ├── test_cloudwatch_nuke.py │ └── utils.py │ ├── network │ ├── __init__.py │ ├── test_eip_nuke.py │ ├── test_network_acl_nuke.py │ ├── test_security_group_nuke.py │ └── utils.py │ └── storage │ ├── __init__.py │ ├── test_glacier_nuke.py │ ├── test_s3_nuke.py │ └── utils.py ├── tox.ini ├── variables.tf └── versions.tf /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 2 | 3 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 4 | && apt-get -y install --no-install-recommends \ 5 | acl \ 6 | ansible \ 7 | curl \ 8 | git \ 9 | gnupg \ 10 | iproute2 \ 11 | iputils-ping \ 12 | jq \ 13 | less \ 14 | libssl-dev \ 15 | lsb-release \ 16 | make \ 17 | nano \ 18 | openssh-client \ 19 | procps \ 20 | python3 \ 21 | python3-pip \ 22 | python3-venv \ 23 | sudo \ 24 | unzip \ 25 | vim \ 26 | wget \ 27 | zip \ 28 | zsh \ 29 | && apt-get clean \ 30 | && rm -rf /var/lib/apt/lists/* 31 | 32 | RUN ansible-galaxy role install diodonfrost.ohmyzsh && \ 33 | ansible-pull -U https://github.com/diodonfrost/ansible-role-ohmyzsh tests/test.yml -e "ohmyzsh_theme=powerlevel10k/powerlevel10k" -e '{"ohmyzsh_users": [vscode]}' 34 | 35 | RUN ansible-galaxy role install diodonfrost.p10k && \ 36 | ansible-pull -U https://github.com/diodonfrost/ansible-role-p10k tests/test.yml -e "zsh_plugin=ohmyzsh" -e '{"p10k_users": [vscode]}' 37 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { "dockerfile": "Dockerfile", "context": "../" }, 3 | "mounts": [ 4 | "source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind,consistency=cached" 5 | ], 6 | 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "hashicorp.terraform", 11 | "redhat.vscode-yaml", 12 | "vscode-icons-team.vscode-icons", 13 | "isudox.vscode-jetbrains-keybindings", 14 | "GitHub.vscode-github-actions" 15 | ], 16 | "settings": { 17 | // Fonts MesLGS NF should be install: https://github.com/romkatv/powerlevel10k-media/blob/master/MesloLGS%20NF%20Regular.ttf 18 | "terminal.integrated.fontFamily": "MesloLGS NF", 19 | "redhat.telemetry.enabled": false, 20 | "aws.telemetry": false, 21 | "workbench.iconTheme": "vscode-icons", 22 | "vsicons.dontShowNewVersionMessage": true, 23 | "editor.rulers": [88,120] 24 | } 25 | } 26 | }, 27 | "features": { 28 | "ghcr.io/devcontainers/features/terraform:1": { 29 | "installSentinel": true, 30 | "installTFsec": true, 31 | "installTerraformDocs": true 32 | }, 33 | "ghcr.io/devcontainers/features/aws-cli:1": {} 34 | }, 35 | "postStartCommand": "pip install -r requirements-dev.txt", 36 | "remoteEnv": { "PATH": "${containerEnv:PATH}:/home/vscode/.local/bin" }, 37 | "remoteUser": "vscode" 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | --- 5 | 6 | ##### SUMMARY 7 | 8 | 9 | ##### ISSUE TYPE 10 | - Bug Report 11 | 12 | ##### TERRAFORM VERSION 13 | 14 | ```paste below 15 | 16 | ``` 17 | 18 | ##### STEPS TO REPRODUCE 19 | 20 | 21 | 22 | ```paste below 23 | 24 | ``` 25 | 26 | 27 | 28 | ##### EXPECTED RESULTS 29 | 30 | 31 | 32 | ##### ACTUAL RESULTS 33 | 34 | 35 | 36 | ```paste below 37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature request" 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | ##### SUMMARY 7 | 8 | 9 | ##### ISSUE TYPE 10 | - Feature Idea 11 | 12 | ##### Additional context 13 | Add any other context or screenshots about the feature request here. 14 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '0 18 * * sun' 7 | 8 | jobs: 9 | tflint: 10 | name: Terraform ${{ matrix.terraform_version }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | terraform_version: 16 | - latest 17 | - 0.14.0 18 | - 0.13.0 19 | steps: 20 | - uses: actions/checkout@master 21 | - name: Terraform validate 22 | run: tests/sanity/terraform_tests.sh 23 | env: 24 | terraform_version: "${{ matrix.terraform_version }}" 25 | 26 | pythontest: 27 | name: ${{ matrix.config.toxenv }} 28 | runs-on: ubuntu-latest 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | config: 33 | - toxenv: py35 34 | python-version: 3.5 35 | - toxenv: py36 36 | python-version: 3.6 37 | - toxenv: py37 38 | python-version: 3.7 39 | - toxenv: py38 40 | python-version: 3.8 41 | - toxenv: flake8 42 | python-version: 3.7 43 | - toxenv: pylint 44 | python-version: 3.7 45 | - toxenv: black 46 | python-version: 3.7 47 | - toxenv: mypy 48 | python-version: 3.7 49 | - toxenv: pytest 50 | python-version: 3.7 51 | 52 | steps: 53 | - uses: actions/checkout@master 54 | - name: Set up Python 55 | uses: actions/setup-python@v1 56 | with: 57 | python-version: ${{ matrix.config.python-version }} 58 | - name: Install dependencies 59 | run: python -m pip install tox 60 | - name: Python test 61 | run: tox 62 | env: 63 | TOXENV: "${{ matrix.config.toxenv }}" 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .terraform.lock.hcl 3 | *.tfstate* 4 | *.zip 5 | terraform.tfstate.d 6 | .tox 7 | __pycache__ 8 | .pytest_cache 9 | .coverage 10 | .mypy_cache 11 | .venv 12 | .idea 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v3.4.0 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: end-of-file-fixer 8 | - id: check-added-large-files 9 | - id: check-case-conflict 10 | - id: check-merge-conflict 11 | - id: fix-encoding-pragma 12 | - id: check-builtin-literals 13 | - id: check-ast 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [2.12.1] - 2021-02-02 9 | ### Bug Fixes 10 | 11 | * **log:** grant lambda scheduler to write log ([197387a](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/197387a155cd9fef16a73e4189454a802db94001)) 12 | 13 | ## [2.12.0] - 2021-01-01 14 | ### Features 15 | 16 | * **terraform:** add tags variable ([19bb8d4](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/19bb8d44b58766e1dfdc46096dcc4c5018f9bbc5)) 17 | 18 | ## [2.11.0] - 2020-12-28 19 | ### Performance Improvements 20 | 21 | * **python:** optimize aws_regions loop ([204e58a](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/204e58a9165ccd89a1efc9b7e8f5d2ba19a93980)) 22 | * **python:** use singleton class to initialize connection to aws ([2395f08](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/2395f08197c53d5db70417a4c3ba5750f4cdc751)) 23 | 24 | ## [2.10.1] - 2020-12-20 25 | ### Bug fixes 26 | * **terraform:** apply terraform fmt ([e10eb9f](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/e10eb9f7717aa0f3a47f4cd7d85d067e6b3e8c9b)) 27 | 28 | ### CI 29 | * **tflint:** update terraform version ([cfc0b4c](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/cfc0b4c3ccf8833ff6a3563e57da6823211b838a)) 30 | * **travis-ci:** removing travis-ci pipeline ([706bc0c](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/706bc0c545d1c2369f82f91480e2acfcfbfa66bf)) 31 | 32 | ### Tests 33 | * **sanity:** stop sanity script when error is found ([5c7ed0f](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/5c7ed0f66ca1441d8f2713ff3f99e431dbca7287)) 34 | 35 | ## [2.10.0] - 2020-11-08 36 | ### Test 37 | 38 | * **poetry:** add python pyproject.toml file ([d59483f](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/d59483f119d410282490ddcfeb8292d3c5041f38)) 39 | * **pytest:** set python_path directly in pytest.ini ([70e7b98](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/70e7b98d59b4a976701b3c791d489f18906fbf03)) 40 | * **kitchen-ci:** removing kitchen-ci test ([8722edb](https://github.com/diodonfrost/terraform-aws-lambda-nuke/commit/8722edbc417875b5d7b3f4558de6ed89571601e9)) 41 | 42 | ## [2.9.0] - 2020-09-12 43 | ### Added 44 | - Python 3.8 support 45 | - Kafka nuke support 46 | 47 | ### Changed 48 | - Restrict iam log group policy 49 | - Improve security group exceptions 50 | - Restrict cloudwatch log group policy 51 | - Pytest: freeze moto version 52 | 53 | ## [2.8.0] - 2020-03-07 54 | ### Added 55 | - Nuke ec2 snapshot 56 | - Nuke ec2 ami 57 | 58 | ## [2.7.1] - 2020-02-28 59 | ### Added 60 | - Waiting for instances in terminated state 61 | 62 | ### Changed 63 | - Do not delete resource with no datetime if older_than not equal zero 64 | 65 | ## [2.7.0] - 2020-02-23 66 | ### Added 67 | - Nuke emr cluster 68 | - Nuke security group rule 69 | 70 | ## [2.6.0] - 2020-02-17 71 | ### Added 72 | - kms support 73 | 74 | ## [2.5.0] - 2020-02-15 75 | ### Added 76 | - Use Python type hint 77 | 78 | ### Changed 79 | - Move Python aws exception in dedicated function 80 | - Refactoring Python import 81 | 82 | ## [2.4.0] - 2020-02-01 83 | ### Added 84 | - Multi aws region support 85 | 86 | ## [2.3.0] - 2019-10-29 87 | ### Changed 88 | - Allow empty value for terraform variable exclude_resources 89 | 90 | ### Removed 91 | - Duplicated rds api call 92 | 93 | ## [2.2.0] - 2019-10-19 94 | ### Added 95 | - Nuke s3 object version 96 | - Python unit tests 97 | 98 | ## [2.1.2] - 2019-10-06 99 | ### Changed 100 | - Optimize python code with yield 101 | 102 | ## [2.1.1] - 2019-10-04 103 | ## Changed 104 | - Python poo style 105 | 106 | ## [2.1.0] - 2019-09-14 107 | ### Added 108 | - Custom IAM role 109 | 110 | ## [2.0.0] - 2019-09-13 111 | ### Added 112 | - Terraform 0.12 support 113 | - AWS cloudwatch dashboard and alarm deletion 114 | 115 | ### Changed 116 | - Reduce complexicy of main function 117 | 118 | ## [0.10.0] - 2019-08-11 119 | ### Added 120 | - Tox ci 121 | - Pylint convention 122 | - Flake8 convention 123 | - Black formating 124 | - Force UTF-8 125 | 126 | ### Changed 127 | - Reduce complexity of Python code 128 | - Refactoring whole Python code 129 | - Fix EFS nuke function 130 | 131 | ## [0.9.1] - 2019-07-05 132 | ### Added 133 | - Spot deletion support 134 | - Classic loadbalancer deletion support 135 | 136 | ### Changed 137 | - Delete s3 bucket with objects 138 | - Don't delete s3 bucket with policy 139 | - Refactoring python logging 140 | - Use local module in Terraform examples 141 | 142 | ### Removed 143 | - AWS nat gateway deletion 144 | - AWS internet gateway deletion 145 | - AWS route table deletion 146 | 147 | ## [0.9.0] - 2019-06-19 148 | ### Added 149 | - Paginator for Python launch_configuration resources 150 | 151 | ### Changed 152 | - Refactoring all Python exception handling 153 | - Fix Python dlm nuke function 154 | - Update aws update availability zones in Terraform examples 155 | - Fix Terraform CloudWatch policy 156 | 157 | ## [0.8.1] - 2019-06-17 158 | ### Added 159 | - Enable CloudWatch loggings 160 | 161 | ### Changed 162 | - Fix s3 objects deletion 163 | 164 | ## [0.8.0] - 2019-06-15 165 | ### Added 166 | - Sonarqube scan 167 | 168 | ### Changed 169 | - Improve order deletion 170 | - Refactoring python code 171 | 172 | ## [0.0.4] - 2019-05-09 173 | ### Changed 174 | - Improve awspec tests 175 | - Use inline policy instead of custom managed policy 176 | - Set default aws region to eu-west-1 177 | 178 | ## [v0.0.3] - 2019-04-17 179 | ### Added 180 | - AWS eip deletion support 181 | - AWS endpoint deletion support 182 | - AWS internet gateway deletion support 183 | - AWS nat gateway deletion support 184 | - AWS route table deletion support 185 | - AWS security group and acl deletion support 186 | 187 | ## [v0.0.2] - 2019-03-15 188 | ### Added 189 | - AWS dynamodb deletion support 190 | - AWS elasticache deletion support 191 | - AWS neptune deletion support 192 | - AWS rds deletion support 193 | - AWS redshift deletion support 194 | 195 | ## [v0.0.1] - 2019-03-07 196 | ### Added 197 | - AWS autoscaling group deletion support 198 | - AWS ebs deletion support 199 | - AWS ec2 instances deletion support 200 | - AWS ecr deletion support 201 | - AWS ecs deletion support 202 | - AWS eks deletion support 203 | - AWS elasticbeanstalk deletion support 204 | - AWS elbv2 deletion support 205 | - AWS keypair deletion support 206 | 207 | [Unreleased]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.12.1...HEAD 208 | [2.12.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.12.0...2.12.1 209 | [2.12.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.11.0...2.12.0 210 | [2.11.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.10.1...2.11.0 211 | [2.10.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.10.0...2.10.1 212 | [2.10.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.9.0...2.10.0 213 | [2.9.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.8.0...2.9.0 214 | [2.8.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.7.1...2.8.0 215 | [2.7.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.7.0...2.7.1 216 | [2.7.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.6.0...2.7.0 217 | [2.6.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.5.0...2.6.0 218 | [2.5.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.4.0...2.5.0 219 | [2.4.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.3.0...2.4.0 220 | [2.3.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.2.0...2.3.0 221 | [2.2.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.1.2...2.2.0 222 | [2.1.2]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.1.1...2.1.2 223 | [2.1.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.1.0...2.1.1 224 | [2.1.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/2.0.0...2.1.0 225 | [2.0.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.10.0...2.0.0 226 | [0.10.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.9.1...0.10.0 227 | [0.9.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.9.0...0.9.1 228 | [0.9.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.8.1...0.9.0 229 | [0.8.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.8.0...0.8.1 230 | [0.8.0]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/0.0.4...0.8.0 231 | [0.0.4]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/v0.0.3...0.0.4 232 | [v0.0.3]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/v0.0.2...v0.0.3 233 | [v0.0.2]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/compare/v0.0.1...v0.0.2 234 | [v0.0.1]: https://github.com/diodonfrost/terraform-aws-lambda-nuke/releases/tag/v0.0.1 235 | -------------------------------------------------------------------------------- /IMPLEMENTATION_COVERAGE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Compute 3 | 4 | - ami 5 | - autoscaling group 6 | - launch configuration 7 | - data lifecycle manager policy 8 | - elastic block store (ebs) 9 | - ec2 instance 10 | - launch template 11 | - placement group 12 | - ecr 13 | - eks 14 | - elasticbeanstalk app 15 | - elasticbeanstalk env 16 | - elb 17 | - elbv2 18 | - elbv2 target group 19 | - key pair 20 | - snapshot 21 | - spot request 22 | - spot fleet 23 | 24 | ## Database 25 | 26 | - dynamodb table 27 | - dyanmodb backup 28 | - elasticache cluster 29 | - elasticache snapshot 30 | - elasticache subnet 31 | - elasticache parameter group 32 | - rds cluster (aurora) 33 | - rds instance 34 | - redshift cluster 35 | - redshift snapshot 36 | - redshift subnet 37 | - redshift parameter group 38 | 39 | ## Governance 40 | 41 | - cloudwatch dashboard 42 | - cloudwatch alarm 43 | 44 | ## Network 45 | 46 | - eip 47 | - vpc endpoint 48 | - security group 49 | - network acl 50 | 51 | ## Storage 52 | 53 | - efs filesystem 54 | - glacier vault 55 | - s3 bucket 56 | 57 | ## Analytic 58 | 59 | - emr cluster 60 | - kafka cluster 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-aws-lambda-nuke 2 | 3 | [![CI](https://github.com/diodonfrost/terraform-aws-lambda-nuke/workflows/CI/badge.svg)](https://github.com/diodonfrost/terraform-aws-lambda-nuke/actions) 4 | 5 | Terraform module which create lambda which nuke all resources on aws account 6 | 7 | ## Requirements 8 | 9 | This role was developed using python lib boto3 1.13.34 Backwards compatibility is not guaranteed. 10 | 11 | ## Terraform versions 12 | 13 | For Terraform 0.15.* use version v2.* of this module. 14 | 15 | If you are using Terraform 0.11 you can use versions v1.*. 16 | 17 | ## Caveats 18 | This following resources are not supported because creation timestamp are not present: 19 | 20 | * Compute 21 | - ecs 22 | * Database: 23 | - dax 24 | 25 | ## Usage 26 | ```hcl 27 | module "nuke_everything_older_than_7d" { 28 | source = "diodonfrost/lambda-nuke/aws" 29 | name = "nuke_everything" 30 | cloudwatch_schedule_expression = "cron(0 00 ? * FRI *)" 31 | exclude_resources = "key_pairs,rds" 32 | older_than = "7d" 33 | } 34 | ``` 35 | 36 | ## Examples 37 | 38 | * [Compute-nuke](https://github.com/diodonfrost/terraform-aws-lambda-nuke/tree/master/examples/compute) Create lambda function to nuke compute resources on Friday at 23:00 Gmt 39 | 40 | * [Storage-nuke](https://github.com/diodonfrost/terraform-aws-lambda-nuke/tree/master/examples/storage) Create lambda function to nuke storage resources on Friday at 23:00 Gmt 41 | 42 | * [test fixture](https://github.com/diodonfrost/terraform-aws-lambda-lambda/tree/master/examples/test_fixture) - Deploy environment for testing module with kitchen-ci and awspec 43 | 44 | 45 | 46 | ## Inputs 47 | 48 | | Name | Description | Type | Default | Required | 49 | |------|-------------|------|---------|----------| 50 | | name | Define name to use for lambda function, cloudwatch event and iam role | string | n/a | yes | 51 | | custom_iam_role_arn | Custom IAM role arn for the scheduling lambda | string | null | no | 52 | | kms_key_arn | The ARN for the KMS encryption key. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key | string | null | no | 53 | | aws_regions | A list of one or more aws regions where the lambda will be apply, default use the current region | list | null | no | 54 | | cloudwatch_schedule_expression | The scheduling expression | string | `"cron(0 22 ? * MON-FRI *)"` | yes | 55 | | exclude_resources | Define the resources that will be not destroyed | string | null | no | 56 | | older_than | Only destroy resources that were created before a certain period | string | 0d | no | 57 | | tags | A map of tags to assign to the resources. | map(any) | null | no | 58 | 59 | ## Outputs 60 | 61 | | Name | Description | 62 | |------|-------------| 63 | | lambda_iam_role_arn | The ARN of the IAM role used by Lambda function | 64 | | lambda_iam_role_name | The name of the IAM role used by Lambda function | 65 | | nuke_lambda_arn | The ARN of the Lambda function | 66 | | nuke_function_name | The name of the Lambda function | 67 | | nuke_lambda_invoke_arn | The ARN to be used for invoking Lambda function from API Gateway | 68 | | nuke_lambda_function_last_modified | The date Lambda function was last modified | 69 | | nuke_lambda_function_version | Latest published version of your Lambda function | 70 | | scheduler_log_group_name | The name of the scheduler log group | 71 | | scheduler_log_group_arn | The Amazon Resource Name (ARN) specifying the log group | 72 | 73 | 74 | 75 | ## Authors 76 | 77 | Modules managed by [diodonfrost](https://github.com/diodonfrost) 78 | 79 | ## Licence 80 | 81 | Apache 2 Licensed. See LICENSE for full details. 82 | 83 | ## Resources 84 | 85 | * [cloudwatch schedule expressions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html) 86 | * [Python boto3 paginator](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/paginators.html) 87 | * [Python boto3 ec2](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html) 88 | * [Python boto3 rds](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rds.html) 89 | * [Python boto3 autoscaling](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html) 90 | -------------------------------------------------------------------------------- /examples/analytic/emr/main.tf: -------------------------------------------------------------------------------- 1 | # Create EMR cluster 2 | 3 | resource "aws_emr_cluster" "cluster" { 4 | name = "emr-nuke" 5 | release_label = "emr-4.6.0" 6 | applications = ["Spark"] 7 | 8 | ec2_attributes { 9 | subnet_id = aws_subnet.main.id 10 | emr_managed_master_security_group = aws_security_group.allow_access.id 11 | emr_managed_slave_security_group = aws_security_group.allow_access.id 12 | instance_profile = aws_iam_instance_profile.emr_profile.arn 13 | } 14 | 15 | master_instance_group { 16 | instance_type = "m4.large" 17 | instance_count = 1 18 | } 19 | 20 | core_instance_group { 21 | instance_type = "m4.large" 22 | instance_count = 3 23 | } 24 | 25 | tags = { 26 | role = "rolename" 27 | dns_zone = "env_zone" 28 | env = "env" 29 | name = "name-env" 30 | } 31 | 32 | bootstrap_action { 33 | path = "s3://elasticmapreduce/bootstrap-actions/run-if" 34 | name = "runif" 35 | args = ["instance.isMaster=true", "echo running on master node"] 36 | } 37 | 38 | configurations_json = < None: 17 | """Initialize emr nuke.""" 18 | self.emr = AwsClient().connect("emr", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Emr deleting function. 22 | 23 | Deleting all emr cluster resources with a timestamp 24 | greater than older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws resource 28 | will be deleted 29 | """ 30 | for cluster_id in self.list_emr(older_than_seconds): 31 | try: 32 | self.emr.terminate_job_flows(JobFlowIds=[cluster_id]) 33 | print("Nuke emr cluster {0}".format(cluster_id)) 34 | except ClientError as exc: 35 | nuke_exceptions("emr cluster", cluster_id, exc) 36 | 37 | def list_emr(self, time_delete: float) -> Iterator[str]: 38 | """Emr volume list function. 39 | 40 | List the IDs of all emr clusters with a timestamp 41 | lower than time_delete. 42 | 43 | :param int time_delete: 44 | Timestamp in seconds used for filter ebs volumes 45 | 46 | :yield Iterator[str]: 47 | Emr cluster IDs 48 | """ 49 | paginator = self.emr.get_paginator("list_clusters") 50 | 51 | for page in paginator.paginate(): 52 | for cluster in page["Clusters"]: 53 | timeline = cluster["Status"]["Timeline"] 54 | if timeline["CreationDateTime"].timestamp() < time_delete: 55 | yield cluster["Id"] 56 | -------------------------------------------------------------------------------- /package/nuke/analytic/kafka.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all kafka cluster.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeKafka: 14 | """Abstract kafka nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize kafka nuke.""" 18 | self.kafka = AwsClient().connect("kafka", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Kafka deleting function. 22 | 23 | Deleting all kafka cluster with a timestamp greater than 24 | older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws resource 28 | will be deleted 29 | """ 30 | for cluster_arn in self.list_cluster(older_than_seconds): 31 | try: 32 | self.kafka.delete_cluster(ClusterArn=cluster_arn) 33 | print("Nuke kafka cluster {0}".format(cluster_arn)) 34 | except ClientError as exc: 35 | nuke_exceptions("kafka cluster", cluster_arn, exc) 36 | 37 | def list_cluster(self, time_delete: float) -> Iterator[str]: 38 | """Kafka cluster list function. 39 | 40 | List the IDs of all kafka clusters with a timestamp 41 | lower than time_delete. 42 | 43 | :param int time_delete: 44 | Timestamp in seconds used for filter ebs volumes 45 | 46 | :yield Iterator[str]: 47 | Kafka cluster arm 48 | """ 49 | paginator = self.kafka.get_paginator("list_clusters") 50 | 51 | for page in paginator.paginate(): 52 | for cluster in page["ClusterInfoList"]: 53 | if cluster["CreationTime"].timestamp() < time_delete: 54 | yield cluster["ClusterArn"] 55 | -------------------------------------------------------------------------------- /package/nuke/client_connections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Boto3 client connection class.""" 4 | 5 | import boto3 6 | 7 | 8 | class MetaSingleton(type): 9 | """Singleton pattern for boto3 client connection.""" 10 | 11 | _instances = {} # type: ignore 12 | 13 | def __call__(cls, *args, **kwargs): 14 | """Singleton pattern method.""" 15 | if cls not in cls._instances: 16 | cls._instances[cls] = super(MetaSingleton, cls).__call__( 17 | *args, **kwargs 18 | ) 19 | return cls._instances[cls] 20 | 21 | 22 | class AwsClient(metaclass=MetaSingleton): 23 | """Abstract aws client connection in a class.""" 24 | 25 | connection = None 26 | aws_region = None 27 | aws_service = None 28 | 29 | def connect(self, service_name: str, region_name=None): 30 | """Initializes aws client connection to aws api only once. 31 | 32 | Connecting to aws api with low-level api client. This function 33 | uses a singleton pattern for initialize the connection only one 34 | time. 35 | 36 | :param str service_name: 37 | The AWS service used in instantiating the client. 38 | :param str region_name: 39 | The AWS Region used in instantiating the client. If used, 40 | this takes precedence over environment variable and 41 | configuration file values. 42 | """ 43 | if ( 44 | self.connection is None 45 | or self.aws_region != region_name 46 | or self.aws_service != service_name 47 | ): 48 | if region_name: 49 | self.connection = boto3.client(service_name, region_name) 50 | else: 51 | self.connection = boto3.client(service_name) 52 | self.aws_region = region_name 53 | self.aws_service = service_name 54 | return self.connection 55 | 56 | 57 | class AwsResource(metaclass=MetaSingleton): 58 | """Abstract aws resource connection in a class.""" 59 | 60 | connection = None 61 | aws_region = None 62 | aws_service = None 63 | 64 | def connect(self, service_name: str, region_name=None): 65 | """Initializes aws connection to aws api only once. 66 | 67 | Connecting to aws api with higher-level abstraction. 68 | This function uses a singleton pattern for initialize 69 | the connection only one time. 70 | 71 | :param str service_name: 72 | The AWS service used in instantiating the client. 73 | :param str region_name: 74 | The AWS Region used in instantiating the client. If used, 75 | this takes precedence over environment variable and 76 | configuration file values. 77 | """ 78 | if ( 79 | self.connection is None 80 | or self.aws_region != region_name 81 | or self.aws_service != service_name 82 | ): 83 | if region_name: 84 | self.connection = boto3.resource(service_name, region_name) 85 | else: 86 | self.connection = boto3.resource(service_name) 87 | self.aws_region = region_name 88 | self.aws_service = service_name 89 | return self.connection 90 | -------------------------------------------------------------------------------- /package/nuke/compute/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module containing the logic for the compute nuke entry-points.""" 3 | -------------------------------------------------------------------------------- /package/nuke/compute/ami.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all ec2 ami.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from dateutil.parser import parse 10 | 11 | from nuke.client_connections import AwsClient 12 | from nuke.exceptions import nuke_exceptions 13 | 14 | 15 | class NukeAmi: 16 | """Abstract ec2 ami in a class.""" 17 | 18 | def __init__(self, region_name=None) -> None: 19 | """Initialize ec2 ami.""" 20 | self.ec2 = AwsClient().connect("ec2", region_name) 21 | 22 | def nuke(self, older_than_seconds) -> None: 23 | """Ec2 ami deleting function. 24 | 25 | Deregister all ami present on the current aws account. 26 | 27 | :param int older_than_seconds: 28 | The timestamp in seconds used from which the aws resource 29 | will be deleted 30 | """ 31 | for ami_id in self.list_ami(older_than_seconds): 32 | try: 33 | self.ec2.deregister_image(ImageId=ami_id) 34 | print("Nuke ami {0}".format(ami_id)) 35 | except ClientError as exc: 36 | nuke_exceptions("ec2 ami", ami_id, exc) 37 | 38 | def list_ami(self, time_delete: float) -> Iterator[str]: 39 | """Ami volume list function. 40 | 41 | List the IDs of all AMI with a timestamp lower than time_delete. 42 | 43 | :param int time_delete: 44 | Timestamp in seconds used for filter AMI 45 | 46 | :yield Iterator[str]: 47 | AMI IDs 48 | """ 49 | amis_describe = self.ec2.describe_images(Owners=["self"]) 50 | 51 | for ami in amis_describe["Images"]: 52 | get_date_obj = parse(ami["CreationDate"]) 53 | date_obj = get_date_obj.replace(tzinfo=None).timestamp() 54 | if date_obj < time_delete: 55 | yield ami["ImageId"] 56 | -------------------------------------------------------------------------------- /package/nuke/compute/autoscaling.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws autoscaling resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeAutoscaling: 14 | """Abstract autoscaling nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize autoscaling nuke.""" 18 | self.asg = AwsClient().connect("autoscaling", region_name) 19 | 20 | try: 21 | self.asg.describe_auto_scaling_groups() 22 | except EndpointConnectionError: 23 | print("Autoscaling resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Autoscaling deleting function. 28 | 29 | Deleting all Autoscaling Groups and Launch Configurations 30 | resources with a timestamp greater than older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws resource 34 | will be deleted 35 | """ 36 | for scaling in self.list_asg(older_than_seconds): 37 | try: 38 | self.asg.delete_auto_scaling_group( 39 | AutoScalingGroupName=scaling, ForceDelete=True 40 | ) 41 | print("Nuke Autoscaling Group {0}".format(scaling)) 42 | except ClientError as exc: 43 | nuke_exceptions("autoscaling group", scaling, exc) 44 | 45 | for launch_conf in self.list_launch_confs(older_than_seconds): 46 | try: 47 | self.asg.delete_launch_configuration( 48 | LaunchConfigurationName=launch_conf 49 | ) 50 | print("Nuke Launch Configuration {0}".format(launch_conf)) 51 | except ClientError as exc: 52 | nuke_exceptions("launch configuration", launch_conf, exc) 53 | 54 | def list_asg(self, time_delete: float) -> Iterator[str]: 55 | """Autoscaling Group list function. 56 | 57 | List the names of all Autoscaling Groups with a timestamp lower 58 | than time_delete. 59 | 60 | :param int time_delete: 61 | Timestamp in seconds used for filter Autoscaling Groups 62 | 63 | :yield Iterator[str]: 64 | Autoscaling Groups names 65 | """ 66 | paginator = self.asg.get_paginator("describe_auto_scaling_groups") 67 | 68 | for page in paginator.paginate(): 69 | for asg in page["AutoScalingGroups"]: 70 | if asg["CreatedTime"].timestamp() < time_delete: 71 | yield asg["AutoScalingGroupName"] 72 | 73 | def list_launch_confs(self, time_delete: float) -> Iterator[str]: 74 | """Launch configuration list function. 75 | 76 | Returns the names of all Launch Configuration Groups with 77 | a timestamp lower than time_delete. 78 | 79 | :param int time_delete: 80 | Timestamp in seconds used for filter Launch Configuration Groups 81 | 82 | :yield Iterator[str]: 83 | Launch Configurations names 84 | """ 85 | paginator = self.asg.get_paginator("describe_launch_configurations") 86 | 87 | for page in paginator.paginate(): 88 | for launch_conf in page["LaunchConfigurations"]: 89 | if launch_conf["CreatedTime"].timestamp() < time_delete: 90 | yield launch_conf["LaunchConfigurationName"] 91 | -------------------------------------------------------------------------------- /package/nuke/compute/dlm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws dlm policie resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeDlm: 14 | """Abstract dlm nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize dlm nuke.""" 18 | self.dlm = AwsClient().connect("dlm", region_name) 19 | 20 | try: 21 | self.dlm.get_lifecycle_policies() 22 | except EndpointConnectionError: 23 | print("Dlm resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Dlm policies deleting function. 28 | 29 | Deleting all dlm policy resources with a timestamp greater 30 | than older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws resource 34 | will be deleted 35 | """ 36 | for policy in self.list_policy(older_than_seconds): 37 | try: 38 | self.dlm.delete_lifecycle_policy(PolicyId=policy) 39 | print("Nuke dlm Lifecycle Policy {0}".format(policy)) 40 | except ClientError as exc: 41 | nuke_exceptions("dlm policy", policy, exc) 42 | 43 | def list_policy(self, time_delete: float) -> Iterator[str]: 44 | """Data Lifecycle Policies list function. 45 | 46 | Returns the IDs of all Data Lifecycle Policies with 47 | a timestamp lower than time_delete. 48 | 49 | :param int time_delete: 50 | Timestamp in seconds used for filter Data Lifecycle policies 51 | 52 | :yield Iterator[str]: 53 | Data Lifecycle policies IDs 54 | """ 55 | response = self.dlm.get_lifecycle_policies() 56 | 57 | for policy in response["Policies"]: 58 | detailed = self.dlm.get_lifecycle_policy( 59 | PolicyId=policy["PolicyId"] 60 | ) 61 | if detailed["Policy"]["DateCreated"].timestamp() < time_delete: 62 | yield policy["PolicyId"] 63 | -------------------------------------------------------------------------------- /package/nuke/compute/ebs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws ebs volume.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEbs: 14 | """Abstract ebs nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize ebs nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Ebs deleting function. 22 | 23 | Deleting all ebs volumes resources with a timestamp 24 | greater than older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws resource 28 | will be deleted 29 | """ 30 | for volume in self.list_ebs(older_than_seconds): 31 | try: 32 | self.ec2.delete_volume(VolumeId=volume) 33 | print("Nuke EBS Volume {0}".format(volume)) 34 | except ClientError as exc: 35 | nuke_exceptions("ebs volume", volume, exc) 36 | 37 | def list_ebs(self, time_delete: float) -> Iterator[str]: 38 | """Ebs volume list function. 39 | 40 | List the IDs of all ebs volumes with a timestamp 41 | lower than time_delete. 42 | 43 | :param int time_delete: 44 | Timestamp in seconds used for filter ebs volumes 45 | 46 | :yield Iterator[str]: 47 | Ebs volumes IDs 48 | """ 49 | paginator = self.ec2.get_paginator("describe_volumes") 50 | 51 | for page in paginator.paginate(): 52 | for volume in page["Volumes"]: 53 | if volume["CreateTime"].timestamp() < time_delete: 54 | yield volume["VolumeId"] 55 | -------------------------------------------------------------------------------- /package/nuke/compute/ec2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all ec2 instances, placement groups and launch templates.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, WaiterError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEc2: 14 | """Abstract ec2 nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize ec2 nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self, older_than_seconds) -> None: 21 | """Ec2 instance, placement group and template deleting function. 22 | 23 | Deleting all ec2 instances, placement groups and launch 24 | templates with a timestamp greater than older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws resource 28 | will be deleted 29 | """ 30 | self.nuke_instances(older_than_seconds) 31 | self.nuke_launch_templates(older_than_seconds) 32 | self.nuke_placement_groups() 33 | 34 | def nuke_instances(self, time_delete: float) -> None: 35 | """Ec2 instance delete function. 36 | 37 | Delete ec2 instances with a timestamp greater than time_delete 38 | 39 | :param int time_delete: 40 | The timestamp in seconds used from which the aws resource 41 | will be deleted 42 | """ 43 | instance_terminating = [] 44 | ec2_waiter = self.ec2.get_waiter("instance_terminated") 45 | 46 | for instance in self.list_instances(time_delete): 47 | try: 48 | self.ec2.terminate_instances(InstanceIds=[instance]) 49 | print("Terminate instances {0}".format(instance)) 50 | except ClientError as exc: 51 | nuke_exceptions("instance", instance, exc) 52 | else: 53 | instance_terminating.append(instance) 54 | 55 | if instance_terminating: 56 | try: 57 | ec2_waiter.wait( 58 | InstanceIds=instance_terminating, 59 | WaiterConfig={"Delay": 10, "MaxAttempts": 30}, 60 | ) 61 | except WaiterError as exc: 62 | nuke_exceptions("instance waiter", instance_terminating, exc) 63 | 64 | def nuke_launch_templates(self, time_delete: float) -> None: 65 | """Ec2 launche template delete function. 66 | 67 | Delete ec2 instances with a timestamp greater than time_delete 68 | 69 | :param int time_delete: 70 | The timestamp in seconds used from which the aws resource 71 | will be deleted 72 | """ 73 | for template in self.list_templates(time_delete): 74 | try: 75 | self.ec2.delete_launch_template(LaunchTemplateId=template) 76 | print("Nuke Launch Template{0}".format(template)) 77 | except ClientError as exc: 78 | nuke_exceptions("ec2 template", template, exc) 79 | 80 | def nuke_placement_groups(self) -> None: 81 | """Ec2 placement group delete function.""" 82 | for placement_group in self.list_placement_groups(): 83 | try: 84 | self.ec2.delete_placement_group(GroupName=placement_group) 85 | print("Nuke Placement Group {0}".format(placement_group)) 86 | except ClientError as exc: 87 | nuke_exceptions("placement group", placement_group, exc) 88 | 89 | def list_instances(self, time_delete: float) -> Iterator[str]: 90 | """Ec2 instance list function. 91 | 92 | List IDs of all ec2 instances with a timestamp lower than 93 | time_delete. 94 | 95 | :param int time_delete: 96 | Timestamp in seconds used for filter ec2 instances 97 | 98 | :yield Iterator[str]: 99 | Ec2 instances IDs 100 | """ 101 | paginator = self.ec2.get_paginator("describe_instances") 102 | page_iterator = paginator.paginate( 103 | Filters=[ 104 | { 105 | "Name": "instance-state-name", 106 | "Values": ["pending", "running", "stopping", "stopped"], 107 | } 108 | ] 109 | ) 110 | 111 | for page in page_iterator: 112 | for reservation in page["Reservations"]: 113 | for instance in reservation["Instances"]: 114 | if instance["LaunchTime"].timestamp() < time_delete: 115 | yield instance["InstanceId"] 116 | 117 | def list_templates(self, time_delete: float) -> Iterator[str]: 118 | """Launch Template list function. 119 | 120 | List Ids of all Launch Templates with a timestamp lower than 121 | time_delete. 122 | 123 | :param int time_delete: 124 | Timestamp in seconds used for filter Launch Templates 125 | 126 | :yield Iterator[str]: 127 | Launch Templates Ids 128 | """ 129 | response = self.ec2.describe_launch_templates() 130 | 131 | for template in response["LaunchTemplates"]: 132 | if template["CreateTime"].timestamp() < time_delete: 133 | yield template["LaunchTemplateId"] 134 | 135 | def list_placement_groups(self) -> Iterator[str]: 136 | """Placement Group list function. 137 | 138 | List name of all placement group. 139 | 140 | :yield Iterator[str]: 141 | Placement groups names 142 | """ 143 | response = self.ec2.describe_placement_groups() 144 | 145 | for placementgroup in response["PlacementGroups"]: 146 | yield placementgroup["GroupName"] 147 | -------------------------------------------------------------------------------- /package/nuke/compute/ecr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws Elastic Container Registry resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEcr: 14 | """Abstract ecr nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize ecr nuke.""" 18 | self.ecr = AwsClient().connect("ecr", region_name) 19 | 20 | try: 21 | self.ecr.describe_repositories() 22 | except EndpointConnectionError: 23 | print("Ecr resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Elastic Container Registry deleting function. 28 | 29 | Deleting all Elastic Container Registry with a timestamp greater 30 | than older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws 34 | resource will be deleted 35 | """ 36 | for registry in self.list_registry(older_than_seconds): 37 | try: 38 | self.ecr.delete_repository(repositoryName=registry, force=True) 39 | print("Nuke ECR Registry{0}".format(registry)) 40 | except ClientError as exc: 41 | nuke_exceptions("ecr registry", registry, exc) 42 | 43 | def list_registry(self, time_delete: float) -> Iterator[str]: 44 | """Elastic Container Registry list function. 45 | 46 | List the names of all Elastic Container Registry with 47 | a timestamp lower than time_delete. 48 | 49 | :param int time_delete: 50 | Timestamp in seconds used for filter ECR 51 | 52 | :yield Iterator[str]: 53 | Elastic Container Registry names 54 | """ 55 | paginator = self.ecr.get_paginator("describe_repositories") 56 | 57 | for page in paginator.paginate(): 58 | for registry in page["repositories"]: 59 | if registry["createdAt"].timestamp() < time_delete: 60 | yield registry["repositoryName"] 61 | -------------------------------------------------------------------------------- /package/nuke/compute/eks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws EKS cluster resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEks: 14 | """Abstract eks nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize eks nuke.""" 18 | self.eks = AwsClient().connect("eks", region_name) 19 | 20 | try: 21 | self.eks.list_clusters() 22 | except EndpointConnectionError: 23 | print("eks resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """EKS cluster deleting function. 28 | 29 | Deleting all EKS clusters with a timestamp greater than 30 | older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws 34 | resource will be deleted 35 | """ 36 | for cluster in self.list_clusters(older_than_seconds): 37 | try: 38 | self.eks.delete_cluster(name=cluster) 39 | print("Nuke EKS Cluster{0}".format(cluster)) 40 | except ClientError as exc: 41 | nuke_exceptions("eks cluster", cluster, exc) 42 | 43 | def list_clusters(self, time_delete: float) -> Iterator[str]: 44 | """EKS cluster list function. 45 | 46 | List the names of all EKS clusters with a timestamp lower than 47 | time_delete. 48 | 49 | :param int time_delete: 50 | Timestamp in seconds used for filter EKS clusters 51 | 52 | :yield Iterator[str]: 53 | EKS cluster names 54 | """ 55 | response = self.eks.list_clusters() 56 | 57 | for kube in response["clusters"]: 58 | k8s = self.eks.describe_cluster(name=kube) 59 | if k8s["cluster"]["createdAt"].timestamp() < time_delete: 60 | yield kube 61 | -------------------------------------------------------------------------------- /package/nuke/compute/elasticbeanstalk.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws Elastic Beanstalk resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeElasticbeanstalk: 14 | """Initialize elasticbeanstalk nuke.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize elasticbeanstalk nuke.""" 18 | self.elasticbeanstalk = AwsClient().connect( 19 | "elasticbeanstalk", region_name 20 | ) 21 | 22 | try: 23 | self.elasticbeanstalk.describe_applications() 24 | except EndpointConnectionError: 25 | print( 26 | "elasticbeanstalk resource is not available in this aws region" 27 | ) 28 | return 29 | 30 | def nuke(self, older_than_seconds: float) -> None: 31 | """Elastic Beanstalk deleting function. 32 | 33 | Deleting all Elastic Beanstalk with a timestamp greater than 34 | older_than_seconds. 35 | 36 | :param int older_than_seconds: 37 | The timestamp in seconds used from which the aws resource 38 | will be deleted 39 | """ 40 | for app in self.list_apps(older_than_seconds): 41 | try: 42 | self.elasticbeanstalk.delete_application( 43 | ApplicationName=app, TerminateEnvByForce=True 44 | ) 45 | print("Nuke elasticbeanstalk application{0}".format(app)) 46 | except ClientError as exc: 47 | nuke_exceptions("elasticbenstalk app", app, exc) 48 | 49 | for env in self.list_envs(older_than_seconds): 50 | try: 51 | self.elasticbeanstalk.terminate_environment( 52 | EnvironmentId=env, ForceTerminate=True 53 | ) 54 | print("Nuke elasticbeanstalk environment {0}".format(env)) 55 | except ClientError as exc: 56 | nuke_exceptions("elasticbenstalk env", env, exc) 57 | 58 | def list_apps(self, time_delete: float) -> Iterator[str]: 59 | """Elastic Beanstalk Application list function. 60 | 61 | List the names of all Elastic Beanstalk Applications. 62 | 63 | :yield Iterator[str]: 64 | Elastic Beanstalk Application names 65 | """ 66 | response = self.elasticbeanstalk.describe_applications() 67 | 68 | for app in response["Applications"]: 69 | if app["DateCreated"].timestamp() < time_delete: 70 | yield app["ApplicationName"] 71 | 72 | def list_envs(self, time_delete: float) -> Iterator[str]: 73 | """Elastic Beanstalk Environment list function. 74 | 75 | List the IDs of all Elastic Beanstalk Environments. 76 | 77 | :yield Iterator[str]: 78 | Elastic Beanstalk Environment IDs 79 | """ 80 | response = self.elasticbeanstalk.describe_environments() 81 | 82 | for env in response["Environments"]: 83 | if env["DateCreated"].timestamp() < time_delete: 84 | yield env["EnvironmentId"] 85 | -------------------------------------------------------------------------------- /package/nuke/compute/elb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws Classic Load Balancer resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeElb: 14 | """Abstract elb nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize elb nuke.""" 18 | self.elb = AwsClient().connect("elb", region_name) 19 | self.elbv2 = AwsClient().connect("elbv2", region_name) 20 | 21 | try: 22 | self.elb.describe_load_balancers() 23 | self.elbv2.describe_load_balancers() 24 | except EndpointConnectionError: 25 | print("elb resource is not available in this aws region") 26 | return 27 | 28 | def nuke(self, older_than_seconds) -> None: 29 | """Main nuke entrypoint function for elb and elbv2. 30 | 31 | Entrypoint function 32 | 33 | :param int older_than_seconds: 34 | The timestamp in seconds used from which the aws resource 35 | will be deleted 36 | """ 37 | self.nuke_loadbalancers(older_than_seconds) 38 | self.nuke_target_groups() 39 | 40 | def nuke_loadbalancers(self, time_delete: float) -> None: 41 | """Loadbalancer delete function. 42 | 43 | Deleting all elbv and elbv2 with a timestamp greater than 44 | older_than_seconds. 45 | 46 | :param int older_than_seconds: 47 | The timestamp in seconds used from which the aws resource 48 | will be deleted 49 | """ 50 | for elb in self.list_elb(time_delete): 51 | try: 52 | self.elb.delete_load_balancer(LoadBalancerName=elb) 53 | print("Nuke Load Balancer {0}".format(elb)) 54 | except ClientError as exc: 55 | nuke_exceptions("elb", elb, exc) 56 | 57 | for elbv2 in self.list_elbv2(time_delete): 58 | try: 59 | self.elbv2.delete_load_balancer(LoadBalancerArn=elbv2) 60 | print("Nuke Load Balancer {0}".format(elbv2)) 61 | except ClientError as exc: 62 | nuke_exceptions("elbv2", elbv2, exc) 63 | 64 | def nuke_target_groups(self) -> None: 65 | """Elbv2 Target group delete function. 66 | 67 | Deleting all elbv2 target groups 68 | """ 69 | for target_group in self.list_target_groups(): 70 | try: 71 | self.elbv2.delete_target_group(TargetGroupArn=target_group) 72 | print("Nuke Target Group {0}".format(target_group)) 73 | except ClientError as exc: 74 | nuke_exceptions("lb target group", target_group, exc) 75 | 76 | def list_elb(self, time_delete: float) -> Iterator[str]: 77 | """Elastic Load Balancer list function. 78 | 79 | List the names of all Elastic Load Balancer with 80 | a timestamp lower than time_delete. 81 | 82 | :param int time_delete: 83 | Timestamp in seconds used for filter Elastic Load Balancer 84 | 85 | :yield Iterator[str]: 86 | Load Balancer names 87 | """ 88 | paginator = self.elb.get_paginator("describe_load_balancers") 89 | 90 | for page in paginator.paginate(): 91 | for loadbalancer in page["LoadBalancerDescriptions"]: 92 | if loadbalancer["CreatedTime"].timestamp() < time_delete: 93 | yield loadbalancer["LoadBalancerName"] 94 | 95 | def list_elbv2(self, time_delete: float) -> Iterator[str]: 96 | """Elastic Load Balancer v2 list function. 97 | 98 | List ARN of all Elastic Load Balancer v2 with 99 | a timestamp lower than time_delete. 100 | 101 | :param int time_delete: 102 | Timestamp in seconds used for filter elbv2 103 | 104 | :yield Iterator[str]: 105 | Elbv2 ARN 106 | """ 107 | paginator = self.elbv2.get_paginator("describe_load_balancers") 108 | 109 | for page in paginator.paginate(): 110 | for lb in page["LoadBalancers"]: 111 | if lb["CreatedTime"].timestamp() < time_delete: 112 | yield lb["LoadBalancerArn"] 113 | 114 | def list_target_groups(self) -> Iterator[str]: 115 | """Elastic Load Balancer Target Group list function. 116 | 117 | List ARN of all elbv2 Target Group with 118 | a timestamp lower than time_delete. 119 | 120 | :param int time_delete: 121 | Timestamp in seconds used for filter elbv2 Target Groups 122 | 123 | :yield Iterator[str]: 124 | Elbv2 ARN 125 | """ 126 | paginator = self.elbv2.get_paginator("describe_target_groups") 127 | 128 | for page in paginator.paginate(): 129 | for targetgroup in page["TargetGroups"]: 130 | yield targetgroup["TargetGroupArn"] 131 | -------------------------------------------------------------------------------- /package/nuke/compute/key_pair.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all keypairs.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeKeypair: 14 | """Abstract key pair nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize key pair nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self) -> None: 21 | """Keypair deleting function. 22 | 23 | Deleting all Keypair 24 | """ 25 | for keypair in self.list_keypair(): 26 | try: 27 | self.ec2.delete_key_pair(KeyName=keypair) 28 | print("Nuke Key Pair {0}".format(keypair)) 29 | except ClientError as exc: 30 | nuke_exceptions("keypair", keypair, exc) 31 | 32 | def list_keypair(self) -> Iterator[str]: 33 | """Keypair list function. 34 | 35 | List all keypair names 36 | 37 | :yield Iterator[str]: 38 | Key pair name 39 | """ 40 | response = self.ec2.describe_key_pairs() 41 | 42 | for keypair in response["KeyPairs"]: 43 | yield keypair["KeyName"] 44 | -------------------------------------------------------------------------------- /package/nuke/compute/snapshot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all ec2 snpashot.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeSnapshot: 14 | """Abstract ec2 snpashot in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize snpashot nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Ec2 snapshot deleting function. 22 | 23 | Delete all snapshot present on the current aws account. 24 | 25 | :param int older_than_seconds: 26 | The timestamp in seconds used from which the aws resource 27 | will be deleted 28 | """ 29 | for snapshot_id in self.list_snapshot(older_than_seconds): 30 | try: 31 | self.ec2.delete_snapshot(SnapshotId=snapshot_id) 32 | print("Nuke snapshot {0}".format(snapshot_id)) 33 | except ClientError as exc: 34 | nuke_exceptions("ec2 snapshot", snapshot_id, exc) 35 | 36 | def list_snapshot(self, time_delete: float) -> Iterator[str]: 37 | """Snapshot list function. 38 | 39 | List the IDs of all snapshot owner with a timestamp lower 40 | than time_delete. 41 | 42 | :param int time_delete: 43 | Timestamp in seconds used for filter AMI 44 | 45 | :yield Iterator[str]: 46 | AMI IDs 47 | """ 48 | paginator = self.ec2.get_paginator("describe_snapshots") 49 | 50 | for page in paginator.paginate(OwnerIds=["self"]): 51 | for snapshot in page["Snapshots"]: 52 | if snapshot["StartTime"].timestamp() < time_delete: 53 | yield snapshot["SnapshotId"] 54 | -------------------------------------------------------------------------------- /package/nuke/compute/spot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all spot requests and spot fleets.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeSpot: 14 | """Abstract spot nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize spot nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Spot request and spot fleet deleting function. 22 | 23 | Deleting all Spot request and spot fleet deleting function with 24 | a timestamp greater than older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws 28 | resource will be deleted 29 | """ 30 | for spot_request in self.list_requests(older_than_seconds): 31 | try: 32 | self.ec2.cancel_spot_instance_requests( 33 | SpotInstanceRequestIds=[spot_request] 34 | ) 35 | print("Cancel spot instance request {0}".format(spot_request)) 36 | except ClientError as exc: 37 | nuke_exceptions("spot request", spot_request, exc) 38 | 39 | for spot_fleet in self.list_fleet(older_than_seconds): 40 | try: 41 | self.ec2.cancel_spot_fleet_requests( 42 | SpotFleetRequestIds=[spot_fleet] 43 | ) 44 | print("Nuke spot fleet request {0}".format(spot_fleet)) 45 | except ClientError as exc: 46 | nuke_exceptions("spot fleet", spot_fleet, exc) 47 | 48 | def list_requests(self, time_delete: float) -> Iterator[str]: 49 | """Spot Request list function. 50 | 51 | List IDs of all Spot Requests with a timestamp 52 | lower than time_delete. 53 | 54 | :param int time_delete: 55 | Timestamp in seconds used for filter spot requests 56 | 57 | :yield Iterator[str]: 58 | Spot requests IDs 59 | """ 60 | response = self.ec2.describe_spot_instance_requests( 61 | Filters=[{"Name": "state", "Values": ["active", "open"]}] 62 | ) 63 | 64 | for spot_request in response["SpotInstanceRequests"]: 65 | if spot_request["CreateTime"].timestamp() < time_delete: 66 | yield spot_request["SpotInstanceRequestId"] 67 | 68 | def list_fleet(self, time_delete: float) -> Iterator[str]: 69 | """Spot Fleet list function. 70 | 71 | List IDs of all Spot Fleets with a timestamp 72 | lower than time_delete. 73 | 74 | :param int time_delete: 75 | Timestamp in seconds used for filter Spot Fleet 76 | 77 | :yield Iterator[str]: 78 | Spot Fleet IDs 79 | """ 80 | paginator = self.ec2.get_paginator("describe_spot_fleet_requests") 81 | 82 | for page in paginator.paginate(): 83 | for fleet in page["SpotFleetRequestConfigs"]: 84 | if fleet["CreateTime"].timestamp() < time_delete: 85 | yield fleet["SpotFleetRequestId"] 86 | -------------------------------------------------------------------------------- /package/nuke/database/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module containing the logic for the lambda nuke entry-points.""" 3 | -------------------------------------------------------------------------------- /package/nuke/database/dynamodb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all dynamodb tables and backups.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeDynamodb: 14 | """Abstract dynamodb nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize dynamodb nuke.""" 18 | self.dynamodb = AwsClient().connect("dynamodb", region_name) 19 | 20 | try: 21 | self.dynamodb.list_tables() 22 | except EndpointConnectionError: 23 | print("Dynamodb resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Dynamodb table and backup deleting function. 28 | 29 | Deleting all dynamodb table and backup with a timestamp greater 30 | than older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws 34 | resource will be deleted 35 | """ 36 | for table in self.list_tables(older_than_seconds): 37 | try: 38 | self.dynamodb.delete_table(TableName=table) 39 | print("Nuke dynamodb table{0}".format(table)) 40 | except ClientError as exc: 41 | nuke_exceptions("dynamodb table", table, exc) 42 | 43 | for backup in self.list_backups(older_than_seconds): 44 | try: 45 | self.dynamodb.delete_backup(BackupArn=backup) 46 | print("Nuke dynamodb backup {0}".format(backup)) 47 | except ClientError as exc: 48 | nuke_exceptions("dynamodb backup", backup, exc) 49 | 50 | def list_tables(self, time_delete: float) -> Iterator[str]: 51 | """Dynamodb table list function. 52 | 53 | List names of all dynamodb tables with a timestamp lower than 54 | time_delete. 55 | 56 | :param int time_delete: 57 | Timestamp in seconds used for filter dynamodb tables 58 | 59 | :yield Iterator[str]: 60 | Dynamodb tables names 61 | """ 62 | paginator = self.dynamodb.get_paginator("list_tables") 63 | 64 | for page in paginator.paginate(): 65 | for table in page["TableNames"]: 66 | table_desc = self.dynamodb.describe_table(TableName=table) 67 | date_table = table_desc["Table"]["CreationDateTime"] 68 | if date_table.timestamp() < time_delete: 69 | yield table 70 | 71 | def list_backups(self, time_delete: float) -> Iterator[str]: 72 | """Dynamodb backup list function. 73 | 74 | List arn of all dynamodb backup with a timestamp lower than 75 | time_delete. 76 | 77 | :param int time_delete: 78 | Timestamp in seconds used for filter dynamodb backup 79 | 80 | :yield Iterator[str]: 81 | Dynamodb backup arn 82 | """ 83 | paginator = self.dynamodb.get_paginator("list_backups") 84 | 85 | for page in paginator.paginate(): 86 | for backup in page["BackupSummaries"]: 87 | backup_desc = self.dynamodb.describe_backup( 88 | BackupArn=backup["BackupArn"]["BackupDescription"] 89 | ) 90 | desc = backup_desc["BackupDetails"] 91 | if desc["BackupCreationDateTime"].timestamp() < time_delete: 92 | yield backup["BackupArn"] 93 | -------------------------------------------------------------------------------- /package/nuke/database/elasticache.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all elasticache resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeElasticache: 14 | """Abstract elasticache nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize elasticache nuke.""" 18 | self.elasticache = AwsClient().connect("elasticache", region_name) 19 | 20 | try: 21 | self.elasticache.describe_cache_clusters() 22 | except EndpointConnectionError: 23 | print("Elasticache resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Elasticache resources deleting function. 28 | 29 | Deleting all elasticache resources with 30 | a timestamp greater than older_than_seconds. 31 | That include: 32 | - clusters 33 | - snapshots 34 | - subnets 35 | - param groups 36 | 37 | :param int older_than_seconds: 38 | The timestamp in seconds used from which the aws 39 | resource will be deleted 40 | """ 41 | self.nuke_clusters(older_than_seconds) 42 | self.nuke_snapshots(older_than_seconds) 43 | self.nuke_subnets() 44 | self.nuke_param_groups() 45 | 46 | def nuke_clusters(self, time_delete: float) -> None: 47 | """Elasticache cluster deleting function. 48 | 49 | Deleting elasticache cluster with a timestamp lower than 50 | time_delete. 51 | 52 | :param int older_than_seconds: 53 | The timestamp in seconds used from which the aws resource 54 | will be deleted 55 | """ 56 | for cluster in self.list_clusters(time_delete): 57 | try: 58 | self.elasticache.delete_cache_cluster(CacheClusterId=cluster) 59 | print("Nuke elasticache cluster {0}".format(cluster)) 60 | except ClientError as exc: 61 | nuke_exceptions("elasticache cluster", cluster, exc) 62 | 63 | def nuke_snapshots(self, time_delete: float) -> None: 64 | """Elasticache snapshot deleting function. 65 | 66 | Deleting elasticache snapshot with a timestamp lower than 67 | time_delete. 68 | 69 | :param int older_than_seconds: 70 | The timestamp in seconds used from which the aws resource 71 | will be deleted 72 | """ 73 | for snapshot in self.list_snapshots(time_delete): 74 | try: 75 | self.elasticache.delete_snapshot(SnapshotName=snapshot) 76 | print("Nuke elasticache snapshot {0}".format(snapshot)) 77 | except ClientError as exc: 78 | nuke_exceptions("elasticache snapshot", snapshot, exc) 79 | 80 | def nuke_subnets(self) -> None: 81 | """Elasticache subnet deleting function. 82 | 83 | Deleting elasticache subnets 84 | """ 85 | for subnet in self.list_subnets(): 86 | 87 | try: 88 | self.elasticache.delete_cache_subnet_group( 89 | CacheSubnetGroupName=subnet 90 | ) 91 | print("Nuke elasticache subnet{0}".format(subnet)) 92 | except ClientError as exc: 93 | nuke_exceptions("elasticache subnet", subnet, exc) 94 | 95 | def nuke_param_groups(self) -> None: 96 | """Elasticache param group deleting function. 97 | 98 | Deleting elasticache parameter groups 99 | """ 100 | for param in self.list_param_groups(): 101 | try: 102 | self.elasticache.delete_cache_parameter_group( 103 | CacheParameterGroupName=param 104 | ) 105 | print("Nuke elasticache param {0}".format(param)) 106 | except ClientError as exc: 107 | nuke_exceptions("elasticache param", param, exc) 108 | 109 | def list_clusters(self, time_delete: float) -> Iterator[str]: 110 | """Elasticache cluster list function. 111 | 112 | List IDs of all elasticache clusters with a timestamp 113 | lower than time_delete. 114 | 115 | :param int time_delete: 116 | Timestamp in seconds used for filter elasticache clusters 117 | 118 | :yield Iterator[str]: 119 | Elasticache clusters IDs 120 | """ 121 | paginator = self.elasticache.get_paginator("describe_cache_clusters") 122 | 123 | for page in paginator.paginate(): 124 | for cluster in page["CacheClusters"]: 125 | if cluster["CacheClusterCreateTime"].timestamp() < time_delete: 126 | yield cluster["CacheClusterId"] 127 | 128 | def list_snapshots(self, time_delete: float) -> Iterator[str]: 129 | """Elasticache snapshots list function. 130 | 131 | List names of all elasticache snapshots with a timestamp 132 | lower than time_delete. 133 | 134 | :param int time_delete: 135 | Timestamp in seconds used for filter elasticache snapshots 136 | 137 | :yield Iterator[str]: 138 | Elasticache snpashots names 139 | """ 140 | paginator = self.elasticache.get_paginator("describe_snapshots") 141 | 142 | for page in paginator.paginate(): 143 | for snapshot in page["Snapshots"]: 144 | date_snap = snapshot["NodeSnapshots"][0]["SnapshotCreateTime"] 145 | if date_snap.timestamp() < time_delete: 146 | yield snapshot["SnapshotName"] 147 | 148 | def list_subnets(self) -> Iterator[str]: 149 | """Elasticache subnet list function. 150 | 151 | List elasticache subnet group names 152 | 153 | :yield Iterator[str]: 154 | Elasticache subnet group names 155 | """ 156 | paginator = self.elasticache.get_paginator( 157 | "describe_cache_subnet_groups" 158 | ) 159 | 160 | for page in paginator.paginate(): 161 | for subnet in page["CacheSubnetGroups"]: 162 | yield subnet["CacheSubnetGroupName"] 163 | 164 | def list_param_groups(self) -> Iterator[str]: 165 | """Elasticache parameters group list function. 166 | 167 | List elasticache param group names 168 | 169 | :yield Iterator[str]: 170 | Elasticache param group names 171 | """ 172 | paginator = self.elasticache.get_paginator( 173 | "describe_cache_parameter_groups" 174 | ) 175 | 176 | for page in paginator.paginate(): 177 | for param_group in page["CacheParameterGroups"]: 178 | yield param_group["CacheParameterGroupName"] 179 | -------------------------------------------------------------------------------- /package/nuke/database/rds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all rds resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeRds: 14 | """Abstract rds nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize rds nuke.""" 18 | self.rds = AwsClient().connect("rds", region_name) 19 | 20 | try: 21 | self.rds.describe_db_clusters() 22 | except EndpointConnectionError: 23 | print("Rds resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Rds resources deleting function. 28 | 29 | Deleting all rds resources with 30 | a timestamp greater than older_than_seconds. 31 | That include: 32 | - Aurora clusters 33 | - rds instances 34 | - snapshots 35 | - subnets 36 | - param groups 37 | 38 | :param int older_than_seconds: 39 | The timestamp in seconds used from which the aws 40 | resource will be deleted 41 | """ 42 | for instance in self.list_instances(older_than_seconds): 43 | try: 44 | self.rds.delete_db_instance( 45 | DBInstanceIdentifier=instance, SkipFinalSnapshot=True 46 | ) 47 | print("Stop rds instance {0}".format(instance)) 48 | except ClientError as exc: 49 | nuke_exceptions("rds instance", instance, exc) 50 | 51 | for cluster in self.list_clusters(older_than_seconds): 52 | try: 53 | self.rds.delete_db_cluster( 54 | DBClusterIdentifier=cluster, SkipFinalSnapshot=True 55 | ) 56 | print("Nuke rds cluster {0}".format(cluster)) 57 | except ClientError as exc: 58 | nuke_exceptions("rds cluster", cluster, exc) 59 | 60 | def list_instances(self, time_delete: float) -> Iterator[str]: 61 | """Rds instance list function. 62 | 63 | List IDs of all rds instances with a timestamp 64 | lower than time_delete. 65 | 66 | :param int time_delete: 67 | Timestamp in seconds used for filter rds instances 68 | 69 | :yield Iterator[str]: 70 | Rds instances IDs 71 | """ 72 | paginator = self.rds.get_paginator("describe_db_instances") 73 | 74 | for page in paginator.paginate(): 75 | for instance in page["DBInstances"]: 76 | if instance["InstanceCreateTime"].timestamp() < time_delete: 77 | yield instance["DBInstanceIdentifier"] 78 | 79 | def list_clusters(self, time_delete: float) -> Iterator[str]: 80 | """Aurora cluster list function. 81 | 82 | List IDs of all aurora clusters with a timestamp 83 | lower than time_delete. 84 | 85 | :param int time_delete: 86 | Timestamp in seconds used for filter aurora clusters 87 | 88 | :yield Iterator[str]: 89 | Aurora clusters IDs 90 | """ 91 | paginator = self.rds.get_paginator("describe_db_clusters") 92 | 93 | for page in paginator.paginate(): 94 | for cluster in page["DBClusters"]: 95 | if cluster["ClusterCreateTime"].timestamp() < time_delete: 96 | yield cluster["DBClusterIdentifier"] 97 | -------------------------------------------------------------------------------- /package/nuke/database/redshift.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all redshift resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeRedshift: 14 | """Abstract redshift nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize redshift nuke.""" 18 | self.redshift = AwsClient().connect("redshift", region_name) 19 | 20 | try: 21 | self.redshift.describe_clusters() 22 | except EndpointConnectionError: 23 | print("Redshift resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds) -> None: 27 | """Redshift resources deleting function. 28 | 29 | Deleting all redshift resources with 30 | a timestamp greater than older_than_seconds. 31 | That include: 32 | - clusters 33 | - snapshots 34 | - subnets 35 | - param groups 36 | 37 | :param int older_than_seconds: 38 | The timestamp in seconds used from which the aws 39 | resource will be deleted 40 | """ 41 | self.nuke_clusters(older_than_seconds) 42 | self.nuke_snapshots(older_than_seconds) 43 | self.nuke_subnets() 44 | self.nuke_param_groups() 45 | 46 | def nuke_clusters(self, time_delete) -> None: 47 | """Redshift cluster deleting function. 48 | 49 | Deleting redshift clusters with a timestamp lower than 50 | time_delete. 51 | 52 | :param int older_than_seconds: 53 | The timestamp in seconds used from which the aws resource 54 | will be deleted 55 | """ 56 | for cluster in self.list_clusters(time_delete): 57 | try: 58 | self.redshift.delete_cluster( 59 | ClusterIdentifier=cluster, SkipFinalClusterSnapshot=True 60 | ) 61 | print("Nuke redshift cluster{0}".format(cluster)) 62 | except ClientError as exc: 63 | nuke_exceptions("redshift cluster", cluster, exc) 64 | 65 | def nuke_snapshots(self, time_delete) -> None: 66 | """Redshift snapshot deleting function. 67 | 68 | Deleting redshift snapshots with a timestamp lower than 69 | time_delete. 70 | 71 | :param int older_than_seconds: 72 | The timestamp in seconds used from which the aws resource 73 | will be deleted 74 | """ 75 | for snapshot in self.list_snapshots(time_delete): 76 | try: 77 | self.redshift.delete_cluster_snapshot( 78 | SnapshotIdentifier=snapshot 79 | ) 80 | print("Nuke redshift snapshot {0}".format(snapshot)) 81 | except ClientError as exc: 82 | nuke_exceptions("redshift snapshot", snapshot, exc) 83 | 84 | def nuke_subnets(self) -> None: 85 | """Redshift subnet deleting function. 86 | 87 | Deleting redshift subnets with a timestamp lower than 88 | time_delete. 89 | 90 | :param int older_than_seconds: 91 | The timestamp in seconds used from which the aws resource 92 | will be deleted 93 | """ 94 | for subnet in self.list_subnet(): 95 | try: 96 | self.redshift.delete_cluster_subnet_group( 97 | ClusterSubnetGroupName=subnet 98 | ) 99 | print("Nuke redshift subnet {0}".format(subnet)) 100 | except ClientError as exc: 101 | nuke_exceptions("redshift subnet", subnet, exc) 102 | 103 | def nuke_param_groups(self) -> None: 104 | """Redshift parameter group deleting function. 105 | 106 | Deleting redshift parameter groups with a timestamp lower than 107 | time_delete. 108 | 109 | :param int older_than_seconds: 110 | The timestamp in seconds used from which the aws resource 111 | will be deleted 112 | """ 113 | for param in self.list_cluster_params(): 114 | try: 115 | self.redshift.delete_cluster_parameter_group( 116 | ParameterGroupName=param 117 | ) 118 | print("Nuke redshift param {0}".format(param)) 119 | except ClientError as exc: 120 | nuke_exceptions("redshift param", param, exc) 121 | 122 | def list_clusters(self, time_delete: float) -> Iterator[str]: 123 | """Redshift cluster list function. 124 | 125 | List IDs of all redshift cluster with a timestamp lower than 126 | time_delete. 127 | 128 | :param int time_delete: 129 | Timestamp in seconds used for filter redshift cluster 130 | 131 | :yield Iterator[str]: 132 | Redshift cluster IDs 133 | """ 134 | paginator = self.redshift.get_paginator("describe_clusters") 135 | 136 | for page in paginator.paginate(): 137 | for cluster in page["Clusters"]: 138 | if cluster["ClusterCreateTime"].timestamp() < time_delete: 139 | yield cluster["ClusterIdentifier"] 140 | 141 | def list_snapshots(self, time_delete: float) -> Iterator[str]: 142 | """Redshift snapshot list function. 143 | 144 | List IDs of all redshift snapshots with a timestamp 145 | lower than time_delete. 146 | 147 | :param int time_delete: 148 | Timestamp in seconds used for filter redshift snapshots 149 | 150 | :yield Iterator[str]: 151 | Redshift snapshots IDs 152 | """ 153 | paginator = self.redshift.get_paginator("describe_cluster_snapshots") 154 | 155 | for page in paginator.paginate(): 156 | for snapshot in page["Snapshots"]: 157 | if snapshot["SnapshotCreateTime"].timestamp() < time_delete: 158 | yield snapshot["SnapshotIdentifier"] 159 | 160 | def list_subnet(self) -> Iterator[str]: 161 | """Redshift subnet list function. 162 | 163 | :yield Iterator[str]: 164 | Redshift subnet names 165 | """ 166 | paginator = self.redshift.get_paginator( 167 | "describe_cluster_subnet_groups" 168 | ) 169 | 170 | for page in paginator.paginate(): 171 | for subnet in page["ClusterSubnetGroups"]: 172 | yield subnet["ClusterSubnetGroupName"] 173 | 174 | def list_cluster_params(self) -> Iterator[str]: 175 | """Redshift cluster parameter list function. 176 | 177 | :yield Iterator[str]: 178 | Redshift cluster parameter names 179 | """ 180 | paginator = self.redshift.get_paginator( 181 | "describe_cluster_parameter_groups" 182 | ) 183 | 184 | for page in paginator.paginate(): 185 | for param in page["ParameterGroups"]: 186 | yield param["ParameterGroupName"] 187 | -------------------------------------------------------------------------------- /package/nuke/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Exception function for aws nuke.""" 4 | 5 | import logging 6 | 7 | 8 | def nuke_exceptions(resource_name, resource_id, exception): 9 | """Exception raised during execution of compute nuke. 10 | 11 | Log aws nuke compute exception on the specific aws resources. 12 | 13 | :param str resource_name: 14 | Aws resource name 15 | :param str resource_id: 16 | Aws resource id 17 | :param str exception: 18 | Human readable string describing the exception 19 | """ 20 | info_error_codes = [ 21 | "UnauthorizedOperation", 22 | "CannotDelete", 23 | "DependencyViolation", 24 | "InvalidVolume", 25 | "InvalidCacheClusterState", 26 | "InvalidSnapshotState", 27 | "InvalidCacheSubnetState", 28 | "InvalidParameterValue", 29 | "InvalidCacheParameterGroupState", 30 | "InvalidPermission.NotFound", 31 | "RequestLimitExceeded", 32 | "ServiceLinkedRoleNotFoundFault", 33 | "UnauthorizedOperation", 34 | "VolumeInUse", 35 | ] 36 | warning_error_codes = [ 37 | "OperationNotPermitted", 38 | "ResourceInUse", 39 | "InvalidDBInstanceState", 40 | "InvalidDBClusterStateFault", 41 | "InvalidClusterStateFault", 42 | "InvalidClusterSnapshotStateFault", 43 | "InvalidClusterSubnetGroupStateFault", 44 | "InvalidClusterParameterGroupStateFault", 45 | "AccessDenied", 46 | ] 47 | 48 | if exception.response["Error"]["Code"] in info_error_codes: 49 | logging.info( 50 | "%s %s: %s", 51 | resource_name, 52 | resource_id, 53 | exception, 54 | ) 55 | elif exception.response["Error"]["Code"] in warning_error_codes: 56 | logging.warning( 57 | "%s %s: %s", 58 | resource_name, 59 | resource_id, 60 | exception, 61 | ) 62 | else: 63 | logging.error( 64 | "Unexpected error on %s %s: %s", 65 | resource_name, 66 | resource_id, 67 | exception, 68 | ) 69 | -------------------------------------------------------------------------------- /package/nuke/governance/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module containing the logic for the governance nuke entry-points.""" 3 | -------------------------------------------------------------------------------- /package/nuke/governance/cloudwatch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws cloudwatch dashboards and alarms.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeCloudwatch: 14 | """Abstract cloudwatch nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize cloudwatch nuke.""" 18 | self.cloudwatch = AwsClient().connect("cloudwatch", region_name) 19 | 20 | def nuke(self, older_than_seconds: float) -> None: 21 | """Cloudwatch and dlm policies deleting function. 22 | 23 | Deleting all cloudwatch dashboards and alarms resources with 24 | a timestamp greater than older_than_seconds. 25 | 26 | :param int older_than_seconds: 27 | The timestamp in seconds used from which the aws 28 | resource will be deleted 29 | """ 30 | for dashboard in self.list_dashboards(older_than_seconds): 31 | try: 32 | self.cloudwatch.delete_dashboards(DashboardNames=[dashboard]) 33 | print("Nuke cloudwatch dashboard {0}".format(dashboard)) 34 | except ClientError as exc: 35 | nuke_exceptions("cloudwatch dashboard", dashboard, exc) 36 | 37 | for alarm in self.list_alarms(older_than_seconds): 38 | try: 39 | self.cloudwatch.delete_alarms(AlarmNames=[alarm]) 40 | print("Nuke cloudwatch alarm {0}".format(alarm)) 41 | except ClientError as exc: 42 | nuke_exceptions("cloudwatch alarm", alarm, exc) 43 | 44 | def list_dashboards(self, time_delete: float) -> Iterator[str]: 45 | """Cloudwatch dashboard list function. 46 | 47 | List the names of all cloudwatch dashboards with a timestamp 48 | lower than time_delete. 49 | 50 | :param int time_delete: 51 | Timestamp in seconds used for filter cloudwatch dashboards 52 | 53 | :yield Iterator[str]: 54 | Cloudwatch dashboard names 55 | """ 56 | paginator = self.cloudwatch.get_paginator("list_dashboards") 57 | 58 | for page in paginator.paginate(): 59 | for dashboard in page["DashboardEntries"]: 60 | if dashboard["LastModified"].timestamp() < time_delete: 61 | yield dashboard["DashboardName"] 62 | 63 | def list_alarms(self, time_delete: float) -> Iterator[str]: 64 | """Cloudwatch alarm list function. 65 | 66 | List the IDs of all cloudwatch alarms with a timestamp 67 | lower than time_delete. 68 | 69 | :param int time_delete: 70 | Timestamp in seconds used for filter cloudwatch alarms 71 | 72 | :yield Iterator[str]: 73 | Cloudwatch alarm IDs 74 | """ 75 | paginator = self.cloudwatch.get_paginator("describe_alarms") 76 | 77 | for page in paginator.paginate(): 78 | for alarm in page["MetricAlarms"]: 79 | if alarm["StateUpdatedTimestamp"].timestamp() < time_delete: 80 | yield alarm["AlarmName"] 81 | -------------------------------------------------------------------------------- /package/nuke/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Main entrypoint function for destroy all aws resources.""" 4 | import os 5 | import time 6 | 7 | from nuke.analytic.emr import NukeEmr 8 | from nuke.analytic.kafka import NukeKafka 9 | from nuke.compute.ami import NukeAmi 10 | from nuke.compute.autoscaling import NukeAutoscaling 11 | from nuke.compute.dlm import NukeDlm 12 | from nuke.compute.ebs import NukeEbs 13 | from nuke.compute.ec2 import NukeEc2 14 | from nuke.compute.ecr import NukeEcr 15 | from nuke.compute.eks import NukeEks 16 | from nuke.compute.elasticbeanstalk import NukeElasticbeanstalk 17 | from nuke.compute.elb import NukeElb 18 | from nuke.compute.key_pair import NukeKeypair 19 | from nuke.compute.snapshot import NukeSnapshot 20 | from nuke.compute.spot import NukeSpot 21 | from nuke.database.dynamodb import NukeDynamodb 22 | from nuke.database.elasticache import NukeElasticache 23 | from nuke.database.rds import NukeRds 24 | from nuke.database.redshift import NukeRedshift 25 | from nuke.governance.cloudwatch import NukeCloudwatch 26 | from nuke.network.eip import NukeEip 27 | from nuke.network.endpoint import NukeEndpoint 28 | from nuke.network.network_acl import NukeNetworkAcl 29 | from nuke.network.security_group import NukeSecurityGroup 30 | from nuke.storage.efs import NukeEfs 31 | from nuke.storage.glacier import NukeGlacier 32 | from nuke.storage.s3 import NukeS3 33 | from nuke.timeparse import timeparse 34 | 35 | 36 | def lambda_handler(event, context): 37 | """Main function entrypoint for lambda.""" 38 | exclude_resources = os.getenv("EXCLUDE_RESOURCES", "no_value") 39 | # Older than date 40 | older_than = os.getenv("OLDER_THAN") 41 | # Convert older_than date to seconds 42 | older_than_seconds = time.time() - timeparse(older_than) 43 | 44 | aws_regions = os.getenv("AWS_REGIONS").replace(" ", "").split(",") 45 | 46 | _strategy = { 47 | "ami": NukeAmi, 48 | "ebs": NukeEbs, 49 | "snapshot": NukeSnapshot, 50 | "ec2": NukeEc2, 51 | "spot": NukeSpot, 52 | "endpoint": NukeEndpoint, 53 | "ecr": NukeEcr, 54 | "emr": NukeEmr, 55 | "kafka": NukeKafka, 56 | "autoscaling": NukeAutoscaling, 57 | "dlm": NukeDlm, 58 | "eks": NukeEks, 59 | "elasticbeanstalk": NukeElasticbeanstalk, 60 | "elb": NukeElb, 61 | "dynamodb": NukeDynamodb, 62 | "elasticache": NukeElasticache, 63 | "rds": NukeRds, 64 | "redshift": NukeRedshift, 65 | "cloudwatch": NukeCloudwatch, 66 | "efs": NukeEfs, 67 | "glacier": NukeGlacier, 68 | "s3": NukeS3, 69 | } 70 | 71 | _strategy_with_no_date = { 72 | "eip": NukeEip, 73 | "key_pair": NukeKeypair, 74 | "security_group": NukeSecurityGroup, 75 | "network_acl": NukeNetworkAcl, 76 | } 77 | 78 | for aws_region in aws_regions: 79 | for key, value in _strategy.items(): 80 | if key not in exclude_resources: 81 | strategy = value(region_name=aws_region) 82 | strategy.nuke(older_than_seconds) 83 | 84 | no_older_than = [int(s) for s in older_than if s.isdigit() and s == "0"] 85 | for aws_region in aws_regions: 86 | for key, value in _strategy_with_no_date.items(): 87 | if key not in exclude_resources and no_older_than == [0]: 88 | strategy = value(region_name=aws_region) 89 | strategy.nuke() 90 | -------------------------------------------------------------------------------- /package/nuke/network/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module containing the logic for the network nuke entry-points.""" 3 | -------------------------------------------------------------------------------- /package/nuke/network/eip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws eip.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEip: 14 | """Abstract eip nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize eip nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | try: 21 | self.ec2.describe_addresses() 22 | except EndpointConnectionError: 23 | print("Eip resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self) -> None: 27 | """Eip deleting function. 28 | 29 | Delete all eip 30 | """ 31 | for eip in self.list_eips(): 32 | try: 33 | self.ec2.release_address(AllocationId=eip) 34 | print("Nuke elastic ip {0}".format(eip)) 35 | except ClientError as exc: 36 | nuke_exceptions("eip", eip, exc) 37 | 38 | def list_eips(self) -> Iterator[str]: 39 | """Eip list function. 40 | 41 | List all eip Id 42 | 43 | :yield Iterator[str]: 44 | Eip Id 45 | """ 46 | response = self.ec2.describe_addresses() 47 | 48 | for eip in response["Addresses"]: 49 | yield eip["AllocationId"] 50 | -------------------------------------------------------------------------------- /package/nuke/network/endpoint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws endpoints.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEndpoint: 14 | """Abstract endpoint nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize endpoint nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | try: 21 | self.ec2.describe_vpc_endpoints() 22 | except EndpointConnectionError: 23 | print("Ec2 endpoint resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """Endpoint deleting function. 28 | 29 | Deleting all aws endpoint with a timestamp greater than 30 | older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws resource 34 | will be deleted 35 | """ 36 | for endpoint in self.list_endpoints(older_than_seconds): 37 | try: 38 | self.ec2.delete_vpc_endpoints(VpcEndpointIds=[endpoint]) 39 | print("Nuke ec2 endpoint {0}".format(endpoint)) 40 | except ClientError as exc: 41 | nuke_exceptions("vpc endpoint", endpoint, exc) 42 | 43 | def list_endpoints(self, time_delete: float) -> Iterator[str]: 44 | """Aws enpoint list function. 45 | 46 | List IDs of all aws endpoints with a timestamp lower than 47 | time_delete. 48 | 49 | :param int time_delete: 50 | Timestamp in seconds used for filter aws endpoint 51 | 52 | :yield Iterator[str]: 53 | Elastic aws endpoint IDs 54 | """ 55 | response = self.ec2.describe_vpc_endpoints() 56 | 57 | for endpoint in response["VpcEndpoints"]: 58 | if endpoint["CreationTimestamp"].timestamp() < time_delete: 59 | yield endpoint["VpcEndpointId"] 60 | -------------------------------------------------------------------------------- /package/nuke/network/network_acl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all network acl.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeNetworkAcl: 14 | """Abstract network acl nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize endpoint nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self) -> None: 21 | """Network acl delete function.""" 22 | for net_acl in self.list_network_acls(): 23 | try: 24 | self.ec2.delete_network_acl(NetworkAclId=net_acl) 25 | print("Nuke ec2 network acl {0}".format(net_acl)) 26 | except ClientError as exc: 27 | nuke_exceptions("network acl", net_acl, exc) 28 | 29 | def list_network_acls(self) -> Iterator[str]: 30 | """Network acl list function. 31 | 32 | :yield Iterator[str]: 33 | Network acl Id 34 | """ 35 | response = self.ec2.describe_network_acls() 36 | 37 | for network_acl in response["NetworkAcls"]: 38 | yield network_acl["NetworkAclId"] 39 | -------------------------------------------------------------------------------- /package/nuke/network/security_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all security groups.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeSecurityGroup: 14 | """Abstract security group nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize endpoint nuke.""" 18 | self.ec2 = AwsClient().connect("ec2", region_name) 19 | 20 | def nuke(self) -> None: 21 | """Security groups delete function.""" 22 | for sec_grp in self.list_security_groups(): 23 | try: 24 | self.revoke_all_rules_in_security_group(sec_grp) 25 | except ClientError as exc: 26 | nuke_exceptions("security group rule", sec_grp, exc) 27 | 28 | for sec_grp in self.list_security_groups(): 29 | try: 30 | self.ec2.delete_security_group(GroupId=sec_grp) 31 | print("Nuke ec2 security group {0}".format(sec_grp)) 32 | except ClientError as exc: 33 | nuke_exceptions("security group", sec_grp, exc) 34 | 35 | def revoke_all_rules_in_security_group(self, security_group_id) -> None: 36 | """Revoke all rules in specific security group. 37 | 38 | :param str security_group_id: 39 | The security group to apply this rule to. 40 | """ 41 | sg_desc = self.ec2.describe_security_groups( 42 | GroupIds=[security_group_id] 43 | ) 44 | try: 45 | self.ec2.revoke_security_group_egress( 46 | GroupId=security_group_id, 47 | IpPermissions=sg_desc["SecurityGroups"][0]["IpPermissions"], 48 | ) 49 | self.ec2.revoke_security_group_ingress( 50 | GroupId=security_group_id, 51 | IpPermissions=sg_desc["SecurityGroups"][0][ 52 | "IpPermissionsEgress" 53 | ], 54 | ) 55 | except ClientError as exc: 56 | nuke_exceptions("security group rule", security_group_id, exc) 57 | 58 | def list_security_groups(self) -> Iterator[str]: 59 | """Security groups list function. 60 | 61 | :yield Iterator[str]: 62 | Security group Id 63 | """ 64 | paginator = self.ec2.get_paginator("describe_security_groups") 65 | 66 | for page in paginator.paginate(): 67 | for security_group in page["SecurityGroups"]: 68 | yield security_group["GroupId"] 69 | -------------------------------------------------------------------------------- /package/nuke/storage/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module containing the logic for the storage nuke entry-points.""" 3 | -------------------------------------------------------------------------------- /package/nuke/storage/efs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws efs resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.exceptions import nuke_exceptions 11 | 12 | 13 | class NukeEfs: 14 | """Abstract efs nuke in a class.""" 15 | 16 | def __init__(self, region_name=None) -> None: 17 | """Initialize efs nuke.""" 18 | self.efs = AwsClient().connect("efs", region_name) 19 | 20 | try: 21 | self.efs.describe_file_systems() 22 | except EndpointConnectionError: 23 | print("EFS resource is not available in this aws region") 24 | return 25 | 26 | def nuke(self, older_than_seconds: float) -> None: 27 | """EFS deleting function. 28 | 29 | Deleting all efs with a timestamp greater than older_than_seconds. 30 | 31 | :param int older_than_seconds: 32 | The timestamp in seconds used from which the aws 33 | resource will be deleted 34 | """ 35 | for efs_file_system in self.list_file_systems(older_than_seconds): 36 | try: 37 | self.efs.delete_file_system(FileSystemId=efs_file_system) 38 | print("Nuke EFS share {0}".format(efs_file_system)) 39 | except ClientError as exc: 40 | nuke_exceptions("efs filesystem", efs_file_system, exc) 41 | 42 | def list_file_systems(self, time_delete: float) -> Iterator[str]: 43 | """EFS list function. 44 | 45 | List IDS of all efs with a timestamp lower than time_delete. 46 | 47 | :param int time_delete: 48 | Timestamp in seconds used for filter efs 49 | 50 | :yield Iterator[str]: 51 | Rfs IDs 52 | """ 53 | paginator = self.efs.get_paginator("describe_file_systems") 54 | 55 | for page in paginator.paginate(): 56 | for filesystem in page["FileSystems"]: 57 | if filesystem["CreationTime"].timestamp() < time_delete: 58 | yield filesystem["FileSystemId"] 59 | -------------------------------------------------------------------------------- /package/nuke/storage/glacier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws Glacier resources.""" 4 | 5 | import datetime 6 | from typing import Iterator 7 | 8 | from botocore.exceptions import ClientError, EndpointConnectionError 9 | 10 | from nuke.client_connections import AwsClient 11 | from nuke.exceptions import nuke_exceptions 12 | 13 | 14 | class NukeGlacier: 15 | """Abstract glacier nuke in a class.""" 16 | 17 | def __init__(self, region_name=None) -> None: 18 | """Initialize glacier nuke.""" 19 | self.glacier = AwsClient().connect("glacier", region_name) 20 | 21 | try: 22 | self.glacier.list_vaults() 23 | except EndpointConnectionError: 24 | print("Glacier resource is not available in this aws region") 25 | return 26 | 27 | def nuke(self, older_than_seconds) -> None: 28 | """Glacier deleting function. 29 | 30 | Deleting all Glacier with a timestamp greater than older_than_seconds. 31 | 32 | :param int older_than_seconds: 33 | The timestamp in seconds used from which the aws 34 | resource will be deleted 35 | """ 36 | for vault in self.list_vaults(older_than_seconds): 37 | try: 38 | self.glacier.delete_vault(vaultName=vault) 39 | print("Nuke glacier vault {0}".format(vault)) 40 | except ClientError as exc: 41 | nuke_exceptions("glacier vault", vault, exc) 42 | 43 | def list_vaults(self, time_delete: float) -> Iterator[str]: 44 | """Glacier vault list function. 45 | 46 | List the names of all Glacier vaults with a timestamp lower 47 | than time_delete. 48 | 49 | :param int time_delete: 50 | Timestamp in seconds used for filter Glacier vaults 51 | 52 | :yield Iterator[str]: 53 | Glacier vaults names 54 | """ 55 | paginator = self.glacier.get_paginator("list_vaults") 56 | 57 | for page in paginator.paginate(): 58 | for vault in page["VaultList"]: 59 | creation_date = datetime.datetime.strptime( 60 | vault["CreationDate"], "%Y-%m-%dT%H:%M:%S.%fZ" 61 | ) 62 | if creation_date.timestamp() < time_delete: 63 | yield vault["VaultName"] 64 | -------------------------------------------------------------------------------- /package/nuke/storage/s3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Module deleting all aws s3 bucket resources.""" 4 | 5 | from typing import Iterator 6 | 7 | from botocore.exceptions import ClientError, EndpointConnectionError 8 | 9 | from nuke.client_connections import AwsClient 10 | from nuke.client_connections import AwsResource 11 | from nuke.exceptions import nuke_exceptions 12 | 13 | 14 | class NukeS3: 15 | """Abstract s3 nuke in a class.""" 16 | 17 | def __init__(self, region_name=None) -> None: 18 | """Initialize s3 nuke.""" 19 | self.s3 = AwsClient().connect("s3", region_name) 20 | self.s3_resource = AwsResource().connect("s3", region_name) 21 | 22 | try: 23 | self.s3.list_buckets() 24 | except EndpointConnectionError: 25 | print("s3 resource is not available in this aws region") 26 | return 27 | 28 | def nuke(self, older_than_seconds: float) -> None: 29 | """S3 bucket deleting function. 30 | 31 | Deleting all s3 buckets with a timestamp greater than 32 | older_than_seconds. 33 | 34 | :param int older_than_seconds: 35 | The timestamp in seconds used from which the aws 36 | resource will be deleted 37 | """ 38 | for s3_bucket in self.list_buckets(older_than_seconds): 39 | try: 40 | # Delete all objects in bucket 41 | bucket = self.s3_resource.Bucket(s3_bucket) 42 | bucket.object_versions.delete() 43 | bucket.objects.delete() 44 | except ClientError as exc: 45 | nuke_exceptions("s3 object", s3_bucket, exc) 46 | 47 | try: 48 | # Delete bucket 49 | self.s3.delete_bucket(Bucket=s3_bucket) 50 | print("Nuke s3 bucket {0}".format(s3_bucket)) 51 | except ClientError as exc: 52 | nuke_exceptions("s3 bucket", s3_bucket, exc) 53 | 54 | def list_buckets(self, time_delete: float) -> Iterator[str]: 55 | """S3 bucket list function. 56 | 57 | List the names of all S3 buckets with 58 | a timestamp lower than time_delete. 59 | 60 | :param int time_delete: 61 | Timestamp in seconds used for filter S3 buckets 62 | 63 | :yield Iterator[str]: 64 | S3 buckets names 65 | """ 66 | response = self.s3.list_buckets() 67 | 68 | for bucket in response["Buckets"]: 69 | if bucket["CreationDate"].timestamp() < time_delete: 70 | yield bucket["Name"] 71 | -------------------------------------------------------------------------------- /package/nuke/timeparse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | timeparse.py 5 | (c) Will Roberts 1 February, 2014 6 | 7 | Implements a single function, `timeparse`, which can parse various 8 | kinds of time expressions. 9 | """ 10 | 11 | # MIT LICENSE 12 | # 13 | # Permission is hereby granted, free of charge, to any person 14 | # obtaining a copy of this software and associated documentation files 15 | # (the "Software"), to deal in the Software without restriction, 16 | # including without limitation the rights to use, copy, modify, merge, 17 | # publish, distribute, sublicense, and/or sell copies of the Software, 18 | # and to permit persons to whom the Software is furnished to do so, 19 | # subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be 22 | # included in all copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 28 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 29 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | # SOFTWARE. 32 | 33 | import re 34 | 35 | SIGN = r"(?P[+|-])?" 36 | WEEKS = r"(?P[\d.]+)\s*(?:w|wks?|weeks?)" 37 | DAYS = r"(?P[\d.]+)\s*(?:d|dys?|days?)" 38 | HOURS = r"(?P[\d.]+)\s*(?:h|hrs?|hours?)" 39 | MINS = r"(?P[\d.]+)\s*(?:m|(mins?)|(minutes?))" 40 | SECS = r"(?P[\d.]+)\s*(?:s|secs?|seconds?)" 41 | SEPARATORS = r"[,/]" 42 | SECCLOCK = r":(?P\d{2}(?:\.\d+)?)" 43 | MINCLOCK = r"(?P\d{1,2}):(?P\d{2}(?:\.\d+)?)" 44 | HOURCLOCK = r"(?P\d+):(?P\d{2}):(?P\d{2}(?:\.\d+)?)" 45 | DAYCLOCK = ( 46 | r"(?P\d+):(?P\d{2}):" 47 | r"(?P\d{2}):(?P\d{2}(?:\.\d+)?)" 48 | ) 49 | 50 | OPT = lambda x: r"(?:{x})?".format(x=x, SEPARATORS=SEPARATORS) # type: ignore 51 | OPTSEP = lambda x: r"(?:{x}\s*(?:{SEPARATORS}\s*)?)?".format( 52 | x=x, SEPARATORS=SEPARATORS 53 | ) 54 | 55 | TIMEFORMATS = [ 56 | r"{WEEKS}\s*{DAYS}\s*{HOURS}\s*{MINS}\s*{SECS}".format( 57 | WEEKS=OPTSEP(WEEKS), 58 | DAYS=OPTSEP(DAYS), 59 | HOURS=OPTSEP(HOURS), 60 | MINS=OPTSEP(MINS), 61 | SECS=OPT(SECS), 62 | ), 63 | r"{MINCLOCK}".format(MINCLOCK=MINCLOCK), 64 | r"{WEEKS}\s*{DAYS}\s*{HOURCLOCK}".format( 65 | WEEKS=OPTSEP(WEEKS), DAYS=OPTSEP(DAYS), HOURCLOCK=HOURCLOCK 66 | ), 67 | r"{DAYCLOCK}".format(DAYCLOCK=DAYCLOCK), 68 | r"{SECCLOCK}".format(SECCLOCK=SECCLOCK), 69 | ] 70 | 71 | COMPILED_SIGN = re.compile(r"\s*" + SIGN + r"\s*(?P.*)$") 72 | COMPILED_TIMEFORMATS = [ 73 | re.compile(r"\s*" + timefmt + r"\s*$", re.I) for timefmt in TIMEFORMATS 74 | ] 75 | 76 | MULTIPLIERS = dict( 77 | [ 78 | ("weeks", 60 * 60 * 24 * 7), 79 | ("days", 60 * 60 * 24), 80 | ("hours", 60 * 60), 81 | ("mins", 60), 82 | ("secs", 1), 83 | ] 84 | ) 85 | 86 | 87 | def _interpret_as_minutes(sval, mdict): 88 | """ 89 | Times like "1:22" are ambiguous; do they represent minutes and seconds 90 | or hours and minutes? By default, timeparse assumes the latter. Call 91 | this function after parsing out a dictionary to change that assumption. 92 | 93 | >>> import pprint 94 | >>> pprint.pprint(_interpret_as_minutes('1:24', {'secs': '24', 'mins': '1'})) 95 | {'hours': '1', 'mins': '24'} 96 | """ 97 | if ( 98 | sval.count(":") == 1 99 | and "." not in sval 100 | and (("hours" not in mdict) or (mdict["hours"] is None)) 101 | and (("days" not in mdict) or (mdict["days"] is None)) 102 | and (("weeks" not in mdict) or (mdict["weeks"] is None)) 103 | ): 104 | mdict["hours"] = mdict["mins"] 105 | mdict["mins"] = mdict["secs"] 106 | mdict.pop("secs") 107 | pass 108 | return mdict 109 | 110 | 111 | def timeparse(sval, granularity="seconds"): 112 | """ 113 | Parse a time expression, returning it as a number of seconds. If 114 | possible, the return value will be an `int`; if this is not 115 | possible, the return will be a `float`. Returns `None` if a time 116 | expression cannot be parsed from the given string. 117 | 118 | Arguments: 119 | - `sval`: the string value to parse 120 | 121 | >>> timeparse('1:24') 122 | 84 123 | >>> timeparse(':22') 124 | 22 125 | >>> timeparse('1 minute, 24 secs') 126 | 84 127 | >>> timeparse('1m24s') 128 | 84 129 | >>> timeparse('1.2 minutes') 130 | 72 131 | >>> timeparse('1.2 seconds') 132 | 1.2 133 | 134 | Time expressions can be signed. 135 | 136 | >>> timeparse('- 1 minute') 137 | -60 138 | >>> timeparse('+ 1 minute') 139 | 60 140 | 141 | If granularity is specified as ``minutes``, then ambiguous digits following 142 | a colon will be interpreted as minutes; otherwise they are considered seconds. 143 | 144 | >>> timeparse('1:30') 145 | 90 146 | >>> timeparse('1:30', granularity='minutes') 147 | 5400 148 | """ 149 | match = COMPILED_SIGN.match(sval) 150 | sign = -1 if match.groupdict()["sign"] == "-" else 1 151 | sval = match.groupdict()["unsigned"] 152 | for timefmt in COMPILED_TIMEFORMATS: 153 | match = timefmt.match(sval) 154 | if match and match.group(0).strip(): 155 | mdict = match.groupdict() 156 | if granularity == "minutes": 157 | mdict = _interpret_as_minutes(sval, mdict) 158 | # if all of the fields are integer numbers 159 | if all(v.isdigit() for v in list(mdict.values()) if v): 160 | return sign * sum( 161 | [ 162 | MULTIPLIERS[k] * int(v, 10) 163 | for (k, v) in list(mdict.items()) 164 | if v is not None 165 | ] 166 | ) 167 | # if SECS is an integer number 168 | elif ( 169 | "secs" not in mdict 170 | or mdict["secs"] is None 171 | or mdict["secs"].isdigit() 172 | ): 173 | # we will return an integer 174 | return sign * int( 175 | sum( 176 | [ 177 | MULTIPLIERS[k] * float(v) 178 | for (k, v) in list(mdict.items()) 179 | if k != "secs" and v is not None 180 | ] 181 | ) 182 | ) + (int(mdict["secs"], 10) if mdict["secs"] else 0) 183 | else: 184 | # SECS is a float, we will return a float 185 | return sign * sum( 186 | [ 187 | MULTIPLIERS[k] * float(v) 188 | for (k, v) in list(mdict.items()) 189 | if v is not None 190 | ] 191 | ) 192 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = .git .* *.egg* old docs dist build 3 | addopts = -p no:warnings 4 | python_paths = package/ 5 | filterwarnings = 6 | # python3.4 raises this when importing setuptools 7 | ignore:The value of convert_charrefs will become True in 3.5.*:DeprecationWarning 8 | # python3 raises this when importing setuptools 9 | ignore:the imp module is deprecated in favour of importlib.*:PendingDeprecationWarning 10 | ignore:the imp module is deprecated in favour of importlib.*:DeprecationWarning 11 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | ansible-base==2.* 2 | Babel==2.* 3 | black==25.* 4 | boto3==1.* 5 | botocore==1.* 6 | cachetools==5.* 7 | certifi==2025.* 8 | cfgv==3.* 9 | chardet==5.* 10 | charset-normalizer==3.* 11 | click==8.* 12 | colorama==0.* 13 | coverage==7.* 14 | cryptography==44.* 15 | distlib==0.* 16 | dnspython==2.* 17 | docker==7.* 18 | exceptiongroup==1.* 19 | filelock==3.* 20 | httplib2==0.* 21 | identify==2.* 22 | idna==3.* 23 | iniconfig==2.* 24 | Jinja2==3.* 25 | jmespath==1.* 26 | MarkupSafe==3.* 27 | moto==5.* 28 | mypy-extensions==1.* 29 | netaddr==1.* 30 | nodeenv==1.* 31 | packaging==24.* 32 | pathspec==0.* 33 | platformdirs==4.* 34 | pluggy==1.* 35 | pre-commit==4.* 36 | pycryptodomex==3.* 37 | pyparsing==3.* 38 | pyproject-api==1.* 39 | pytest==8.* 40 | pytest-cov==6.* 41 | pytest-pythonpath==0.* 42 | python-apt==2.* 43 | python-dateutil==2.* 44 | pytz==2025.* 45 | PyYAML==6.* 46 | requests==2.* 47 | responses==0.* 48 | s3transfer==0.* 49 | six==1.* 50 | tomli==2.* 51 | tox==4.* 52 | types-PyYAML==6.* 53 | urllib3==2.* 54 | virtualenv==20.* 55 | websocket-client==1.* 56 | Werkzeug==3.* 57 | xmltodict==0.* 58 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/sanity/.bandit.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | skips: 3 | - B404 # Ignore warnings about importing subprocess 4 | - B603 # Ignore warnings about calling subprocess.Popen without shell=True 5 | - B607 # Ignore warnings about calling subprocess.Popen without a full path to executable 6 | 7 | ### (optional) plugin settings - some test plugins require configuration data 8 | ### that may be given here, per-plugin. All bandit test plugins have a built in 9 | ### set of sensible defaults and these will be used if no configuration is 10 | ### provided. It is not necessary to provide settings for every (or any) plugin 11 | ### if the defaults are acceptable. 12 | 13 | any_other_function_with_shell_equals_true: 14 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 15 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 16 | os.spawnvp, os.spawnvpe, os.startfile] 17 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 18 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 19 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 20 | utils.execute, utils.execute_with_timeout] 21 | execute_with_run_as_root_equals_true: 22 | function_names: [ceilometer.utils.execute, cinder.utils.execute, neutron.agent.linux.utils.execute, 23 | nova.utils.execute, nova.utils.trycmd] 24 | hardcoded_tmp_directory: 25 | tmp_dirs: [/tmp, /var/tmp, /dev/shm] 26 | linux_commands_wildcard_injection: 27 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 28 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 29 | os.spawnvp, os.spawnvpe, os.startfile] 30 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 31 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 32 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 33 | utils.execute, utils.execute_with_timeout] 34 | password_config_option_not_marked_secret: 35 | function_names: [oslo.config.cfg.StrOpt, oslo_config.cfg.StrOpt] 36 | ssl_with_bad_defaults: 37 | bad_protocol_versions: [PROTOCOL_SSLv2, SSLv2_METHOD, SSLv23_METHOD, PROTOCOL_SSLv3, 38 | PROTOCOL_TLSv1, SSLv3_METHOD, TLSv1_METHOD] 39 | ssl_with_bad_version: 40 | bad_protocol_versions: [PROTOCOL_SSLv2, SSLv2_METHOD, SSLv23_METHOD, PROTOCOL_SSLv3, 41 | PROTOCOL_TLSv1, SSLv3_METHOD, TLSv1_METHOD] 42 | start_process_with_a_shell: 43 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 44 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 45 | os.spawnvp, os.spawnvpe, os.startfile] 46 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 47 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 48 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 49 | utils.execute, utils.execute_with_timeout] 50 | start_process_with_no_shell: 51 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 52 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 53 | os.spawnvp, os.spawnvpe, os.startfile] 54 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 55 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 56 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 57 | utils.execute, utils.execute_with_timeout] 58 | start_process_with_partial_path: 59 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 60 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 61 | os.spawnvp, os.spawnvpe, os.startfile] 62 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 63 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 64 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 65 | utils.execute, utils.execute_with_timeout] 66 | subprocess_popen_with_shell_equals_true: 67 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 68 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 69 | os.spawnvp, os.spawnvpe, os.startfile] 70 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 71 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 72 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 73 | utils.execute, utils.execute_with_timeout] 74 | subprocess_without_shell_equals_true: 75 | no_shell: [os.execl, os.execle, os.execlp, os.execlpe, os.execv, os.execve, os.execvp, 76 | os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, 77 | os.spawnvp, os.spawnvpe, os.startfile] 78 | shell: [os.system, os.popen, os.popen2, os.popen3, os.popen4, popen2.popen2, popen2.popen3, 79 | popen2.popen4, popen2.Popen3, popen2.Popen4, commands.getoutput, commands.getstatusoutput] 80 | subprocess: [subprocess.Popen, subprocess.call, subprocess.check_call, subprocess.check_output, 81 | utils.execute, utils.execute_with_timeout] 82 | try_except_continue: {check_typed_exception: false} 83 | try_except_pass: {check_typed_exception: false} 84 | -------------------------------------------------------------------------------- /tests/sanity/terraform_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Install the Latest version of Terraform 5 | sudo pip install ansible 6 | sudo ansible-galaxy install diodonfrost.terraform 7 | sudo ansible-pull -U https://github.com/diodonfrost/ansible-role-terraform tests/test.yml -e "terraform_version=${terraform_version}" 8 | terraform -version 9 | terraform init 10 | 11 | # Test Terraform syntax 12 | export AWS_DEFAULT_REGION=eu-west-1 13 | terraform validate 14 | 15 | # Terraform lint 16 | terraform fmt -check -diff main.tf 17 | 18 | # Test Terraform fixture example 19 | cd examples/test_fixture || exist 20 | terraform init 21 | terraform validate 22 | terraform fmt 23 | terraform -v 24 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/analytic/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/analytic/test_emr_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the emr nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_emr 8 | 9 | from package.nuke.analytic.emr import NukeEmr 10 | 11 | from .utils import create_emr 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", 18 | [ 19 | ("eu-west-1", time.time() + 43200, "TERMINATED"), 20 | ("eu-west-2", time.time() + 43200, "TERMINATED"), 21 | ("eu-west-2", 630720000, "WAITING"), 22 | ], 23 | ) 24 | @mock_emr 25 | def test_emr_nuke(aws_region, older_than_seconds, result_count): 26 | """Verify emr nuke function.""" 27 | client = boto3.client("emr", region_name=aws_region) 28 | 29 | create_emr(region_name=aws_region) 30 | emr = NukeEmr(aws_region) 31 | emr.nuke(older_than_seconds=older_than_seconds) 32 | emr_list = client.list_clusters() 33 | assert emr_list["Clusters"][0]["Status"]["State"] == result_count 34 | -------------------------------------------------------------------------------- /tests/unit/analytic/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_emr(region_name): 8 | """Create emr cluster.""" 9 | client = boto3.client("emr", region_name=region_name) 10 | az = region_name + "a" 11 | client.run_job_flow( 12 | Instances={ 13 | "InstanceCount": 3, 14 | "KeepJobFlowAliveWhenNoSteps": True, 15 | "MasterInstanceType": "c3.medium", 16 | "Placement": {"AvailabilityZone": az}, 17 | "SlaveInstanceType": "c3.xlarge", 18 | }, 19 | JobFlowRole="EMR_EC2_DefaultRole", 20 | LogUri="s3://mybucket/log", 21 | Name="cluster", 22 | ServiceRole="EMR_DefaultRole", 23 | VisibleToAllUsers=True, 24 | ) 25 | -------------------------------------------------------------------------------- /tests/unit/compute/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/compute/test_ami_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the ami nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.ami import NukeAmi 10 | 11 | from .utils import create_ami 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_ec2 24 | def test_ebs_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify ami nuke function.""" 26 | client = boto3.client("ec2", region_name=aws_region) 27 | 28 | create_ami(region_name=aws_region) 29 | ami = NukeAmi(aws_region) 30 | ami.nuke(older_than_seconds=older_than_seconds) 31 | ami_list = client.describe_images(Owners=["self"])["Images"] 32 | assert len(ami_list) == result_count 33 | -------------------------------------------------------------------------------- /tests/unit/compute/test_autoscaling_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the autoscaling group nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_autoscaling, mock_ec2 8 | 9 | from package.nuke.compute.autoscaling import NukeAutoscaling 10 | 11 | from .utils import create_autoscaling 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_ec2 24 | @mock_autoscaling 25 | def test_autoscaling_nuke(aws_region, older_than_seconds, result_count): 26 | """Verify autoscaling nuke class.""" 27 | client = boto3.client("autoscaling", region_name=aws_region) 28 | 29 | create_autoscaling(aws_region) 30 | asg = NukeAutoscaling(aws_region) 31 | asg.nuke(older_than_seconds=older_than_seconds) 32 | asg_list = client.describe_auto_scaling_groups()["AutoScalingGroups"] 33 | assert len(asg_list) == result_count 34 | 35 | 36 | @pytest.mark.parametrize( 37 | "aws_region, older_than_seconds, result_count", [ 38 | ("eu-west-1", time.time() + 43200, 0), 39 | ("eu-west-2", time.time() + 43200, 0), 40 | ("eu-west-2", 630720000, 1), 41 | ] 42 | ) 43 | @mock_ec2 44 | @mock_autoscaling 45 | def test_launch_conf_nuke(aws_region, older_than_seconds, result_count): 46 | """Verify launch configuration nuke function.""" 47 | client = boto3.client("autoscaling", region_name=aws_region) 48 | 49 | create_autoscaling(aws_region) 50 | asg = NukeAutoscaling(aws_region) 51 | asg.nuke(older_than_seconds=older_than_seconds) 52 | launch_conf_list = client.describe_launch_configurations()["LaunchConfigurations"] 53 | assert len(launch_conf_list) == result_count 54 | -------------------------------------------------------------------------------- /tests/unit/compute/test_ebs_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the ebs nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.ebs import NukeEbs 10 | 11 | from .utils import create_ebs 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_ec2 24 | def test_ebs_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify ebs volume nuke function.""" 26 | client = boto3.client("ec2", region_name=aws_region) 27 | 28 | create_ebs(region_name=aws_region) 29 | ebs = NukeEbs(aws_region) 30 | ebs.nuke(older_than_seconds=older_than_seconds) 31 | ebs_list = client.describe_volumes()["Volumes"] 32 | assert len(ebs_list) == result_count 33 | -------------------------------------------------------------------------------- /tests/unit/compute/test_ec2_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the ec2 nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.ec2 import NukeEc2 10 | 11 | from .utils import create_instances 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, {'Code': 48, 'Name': 'terminated'}), 19 | ("eu-west-2", time.time() + 43200, {'Code': 48, 'Name': 'terminated'}), 20 | ("eu-west-2", 630720000, {'Code': 16, 'Name': 'running'}), 21 | ] 22 | ) 23 | @mock_ec2 24 | def test_ec2_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify instances nuke function.""" 26 | client = boto3.client("ec2", region_name=aws_region) 27 | 28 | create_instances(count=3, region_name=aws_region) 29 | ec2 = NukeEc2(aws_region) 30 | ec2.nuke_instances(time_delete=older_than_seconds) 31 | ec2_list = client.describe_instances()["Reservations"][0]["Instances"] 32 | for instance in ec2_list: 33 | assert instance["State"] == result_count 34 | -------------------------------------------------------------------------------- /tests/unit/compute/test_elb_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the elb nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_elb, mock_elbv2, mock_ec2 8 | 9 | from package.nuke.compute.elb import NukeElb 10 | 11 | from .utils import create_elb, create_elbv2 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_ec2 24 | @mock_elb 25 | @mock_elbv2 26 | def test_elb_nuke(aws_region, older_than_seconds, result_count): 27 | """Verify elb nuke function.""" 28 | elb = boto3.client("elb", region_name=aws_region) 29 | 30 | create_elb(region_name=aws_region) 31 | lb = NukeElb(aws_region) 32 | lb.nuke(older_than_seconds=older_than_seconds) 33 | elb_list = elb.describe_load_balancers()["LoadBalancerDescriptions"] 34 | assert len(elb_list) == result_count 35 | 36 | 37 | @pytest.mark.parametrize( 38 | "aws_region, older_than_seconds, result_count", [ 39 | ("eu-west-1", time.time() + 43200, 0), 40 | ("eu-west-2", time.time() + 43200, 0), 41 | ("eu-west-2", 630720000, 1), 42 | ] 43 | ) 44 | @mock_ec2 45 | @mock_elb 46 | @mock_elbv2 47 | def test_elbv2_nuke(aws_region, older_than_seconds, result_count): 48 | """Verify elbv2 nuke function.""" 49 | elbv2 = boto3.client("elbv2", region_name=aws_region) 50 | 51 | create_elbv2(region_name=aws_region) 52 | lb = NukeElb(aws_region) 53 | lb.nuke(older_than_seconds=older_than_seconds) 54 | elbv2_list = elbv2.describe_load_balancers()["LoadBalancers"] 55 | assert len(elbv2_list) == result_count 56 | -------------------------------------------------------------------------------- /tests/unit/compute/test_keypair_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the keypair nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.key_pair import NukeKeypair 10 | 11 | from .utils import create_keypair 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, result_count", [ 18 | ("eu-west-1", 0), 19 | ("eu-west-2", 0), 20 | ] 21 | ) 22 | @mock_ec2 23 | def test_keypair_nuke(aws_region, result_count): 24 | """Verify keypair nuke class.""" 25 | client = boto3.client("ec2", region_name=aws_region) 26 | 27 | create_keypair(region_name=aws_region) 28 | keypair = NukeKeypair(aws_region) 29 | keypair.nuke() 30 | keypair_list = client.describe_key_pairs()["KeyPairs"] 31 | assert len(keypair_list) == result_count 32 | -------------------------------------------------------------------------------- /tests/unit/compute/test_snapshot_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the snapshot nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.snapshot import NukeSnapshot 10 | 11 | from .utils import create_snapshot 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", 18 | [ 19 | ("eu-west-1", time.time() + 43200, 0), 20 | ("eu-west-2", time.time() + 43200, 0), 21 | ("eu-west-2", 630720000, 1), 22 | ], 23 | ) 24 | @mock_ec2 25 | def test_snapshot_nuke(aws_region, older_than_seconds, result_count): 26 | """Verify snapshot nuke function.""" 27 | client = boto3.client("ec2", region_name=aws_region) 28 | 29 | create_snapshot(region_name=aws_region) 30 | snapshot = NukeSnapshot(aws_region) 31 | snapshot.nuke(older_than_seconds=older_than_seconds) 32 | snapshot_list = client.describe_snapshots( 33 | Filters=[{"Name": "owner-id", "Values": ["111122223333"]}] 34 | )["Snapshots"] 35 | assert len(snapshot_list) == result_count 36 | -------------------------------------------------------------------------------- /tests/unit/compute/test_spot_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the spot nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_ec2 8 | 9 | from package.nuke.compute.spot import NukeSpot 10 | 11 | from .utils import create_spot 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 3), 21 | ] 22 | ) 23 | @mock_ec2 24 | def test_spot_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify spot nuke class.""" 26 | client = boto3.client("ec2", region_name=aws_region) 27 | 28 | create_spot(region_name=aws_region, count=3) 29 | spot = NukeSpot(aws_region) 30 | spot.nuke(older_than_seconds=older_than_seconds) 31 | spot_request_list = client.describe_spot_instance_requests()["SpotInstanceRequests"] 32 | assert len(spot_request_list) == result_count 33 | -------------------------------------------------------------------------------- /tests/unit/compute/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_autoscaling(region_name): 8 | """Create autoscaling group.""" 9 | client = boto3.client("autoscaling", region_name=region_name) 10 | client.create_launch_configuration( 11 | LaunchConfigurationName="lc-test", 12 | ImageId="ami-02df9ea15c1778c9c", 13 | InstanceType="t2.micro", 14 | ) 15 | client.create_auto_scaling_group( 16 | AutoScalingGroupName="asg-test", 17 | MaxSize=5, 18 | DesiredCapacity=3, 19 | MinSize=1, 20 | LaunchConfigurationName="lc-test", 21 | AvailabilityZones=[region_name + "a", region_name + "b"], 22 | ) 23 | 24 | 25 | def create_ebs(region_name): 26 | """Create ebs volume.""" 27 | client = boto3.client("ec2", region_name=region_name) 28 | client.create_volume(AvailabilityZone=region_name + "a", Size=50) 29 | 30 | 31 | def create_instances(count, region_name): 32 | """Create ec2 instances.""" 33 | client = boto3.client("ec2", region_name=region_name) 34 | client.run_instances( 35 | ImageId="ami-02df9ea15c1778c9c", MaxCount=count, MinCount=count 36 | ) 37 | 38 | 39 | def create_ecr(region_name): 40 | """Create ecr repository.""" 41 | client = boto3.client("ecr", region_name=region_name) 42 | client.create_repository(repositoryName="ecr-test") 43 | 44 | 45 | def create_elb(region_name): 46 | """Create elb.""" 47 | elb = boto3.client("elb", region_name=region_name) 48 | 49 | elb.create_load_balancer( 50 | LoadBalancerName="elb-test", 51 | AvailabilityZones=[region_name + "a", region_name + "b"], 52 | Listeners=[ 53 | { 54 | "Protocol": "tcp", 55 | "LoadBalancerPort": 80, 56 | "InstanceProtocol": "tcp", 57 | "InstancePort": 80, 58 | } 59 | ], 60 | ) 61 | 62 | 63 | def create_elbv2(region_name): 64 | """Create elbv2.""" 65 | elbv2 = boto3.client("elbv2", region_name=region_name) 66 | ec2 = boto3.client("ec2", region_name=region_name) 67 | 68 | elbv2.create_load_balancer( 69 | Name="elbv2-test", 70 | Subnets=[ 71 | ec2.describe_subnets()["Subnets"][0]["SubnetId"], 72 | ec2.describe_subnets()["Subnets"][1]["SubnetId"], 73 | ], 74 | Scheme="internal", 75 | ) 76 | 77 | 78 | def create_keypair(region_name): 79 | """Create keypair.""" 80 | client = boto3.client("ec2", region_name=region_name) 81 | 82 | client.create_key_pair(KeyName="keypair-test") 83 | 84 | 85 | def create_spot(count, region_name): 86 | """Create spot request and spot fleet.""" 87 | client = boto3.client("ec2", region_name=region_name) 88 | 89 | client.request_spot_instances(InstanceCount=count) 90 | 91 | 92 | def create_ami(region_name): 93 | """Create ami image.""" 94 | client = boto3.client("ec2", region_name=region_name) 95 | 96 | instance = client.run_instances( 97 | ImageId="ami-02df9ea15c1778c9c", MaxCount=1, MinCount=1 98 | ) 99 | client.create_image( 100 | InstanceId=instance["Instances"][0]["InstanceId"], Name="nuke-ami" 101 | ) 102 | 103 | 104 | def create_snapshot(region_name): 105 | """Create ec2 snapshot.""" 106 | client = boto3.client("ec2", region_name=region_name) 107 | 108 | ebs_volume = client.create_volume( 109 | AvailabilityZone=region_name + "a", Size=50 110 | ) 111 | client.create_snapshot(VolumeId=ebs_volume["VolumeId"]) 112 | -------------------------------------------------------------------------------- /tests/unit/database/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/database/test_redshift_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the redshift nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_redshift 8 | 9 | from package.nuke.database.redshift import NukeRedshift 10 | 11 | from .utils import create_redshift 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_redshift 24 | def test_redshift_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify redshift nuke function.""" 26 | client = boto3.client("redshift", region_name=aws_region) 27 | 28 | create_redshift(region_name=aws_region) 29 | redshift = NukeRedshift(aws_region) 30 | redshift.nuke(older_than_seconds=older_than_seconds) 31 | redshift_list = client.describe_clusters()["Clusters"] 32 | assert len(redshift_list) == result_count 33 | -------------------------------------------------------------------------------- /tests/unit/database/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_dynamodb(region_name): 8 | """Create dynamodb table.""" 9 | client = boto3.client("dynamodb", region_name=region_name) 10 | 11 | client.create_table( 12 | TableName="table-test", 13 | KeySchema=[ 14 | {"AttributeName": "UserId", "KeyType": "HASH"}, 15 | {"AttributeName": "GameTitle", "KeyType": "RANGE"}, 16 | ], 17 | AttributeDefinitions=[ 18 | {"AttributeName": "UserId", "AttributeType": "S"}, 19 | {"AttributeName": "GameTitle", "AttributeType": "S"}, 20 | ], 21 | ProvisionedThroughput={ 22 | "ReadCapacityUnits": 20, 23 | "WriteCapacityUnits": 20, 24 | }, 25 | ) 26 | 27 | 28 | def create_redshift(region_name): 29 | """Create redshift cluster.""" 30 | client = boto3.client("redshift", region_name=region_name) 31 | 32 | client.create_cluster( 33 | DBName="db-test", 34 | ClusterIdentifier="redshift-test", 35 | ClusterType="single-node", 36 | NodeType="ds2.xlarge", 37 | MasterUsername="user", 38 | MasterUserPassword="IamNotHere", 39 | ) 40 | -------------------------------------------------------------------------------- /tests/unit/governance/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/governance/test_cloudwatch_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the cloudwatch nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_cloudwatch 8 | 9 | from package.nuke.governance.cloudwatch import NukeCloudwatch 10 | 11 | from .utils import create_cloudwatch_dashboard, create_cloudwatch_alarm 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_cloudwatch 24 | def test_cloudwatch_dashboard_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify cloudwatch dashboard nuke function.""" 26 | client = boto3.client("cloudwatch", region_name=aws_region) 27 | 28 | create_cloudwatch_dashboard(region_name=aws_region) 29 | cloudwatch = NukeCloudwatch(aws_region) 30 | cloudwatch.nuke(older_than_seconds) 31 | cloudwatch_dashboard_list = client.list_dashboards()["DashboardEntries"] 32 | assert len(cloudwatch_dashboard_list) == result_count 33 | 34 | 35 | @pytest.mark.parametrize( 36 | "aws_region, older_than_seconds, result_count", [ 37 | ("eu-west-1", time.time() + 43200, 0), 38 | ("eu-west-2", time.time() + 43200, 0), 39 | ("eu-west-2", 630720000, 1), 40 | ] 41 | ) 42 | @mock_cloudwatch 43 | def test_cloudwatch_alarm_nuke(aws_region, older_than_seconds, result_count): 44 | """Verify cloudwatch alarm nuke function.""" 45 | client = boto3.client("cloudwatch", region_name=aws_region) 46 | 47 | create_cloudwatch_alarm(region_name=aws_region) 48 | cloudwatch = NukeCloudwatch(aws_region) 49 | cloudwatch.nuke(older_than_seconds) 50 | cloudwatch_list = client.describe_alarms()["MetricAlarms"] 51 | assert len(cloudwatch_list) == result_count 52 | -------------------------------------------------------------------------------- /tests/unit/governance/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_cloudwatch_dashboard(region_name): 8 | """Create cloudwatch dashboard.""" 9 | client = boto3.client("cloudwatch", region_name=region_name) 10 | widget = { 11 | "widgets": [ 12 | { 13 | "type": "text", 14 | "x": 0, 15 | "y": 7, 16 | "width": 3, 17 | "height": 3, 18 | "properties": {"markdown": "Hello world"}, 19 | } 20 | ] 21 | } 22 | client.put_dashboard( 23 | DashboardName="dashboard-test", 24 | DashboardBody=str(widget).replace("'", '"'), 25 | ) 26 | 27 | 28 | def create_cloudwatch_alarm(region_name): 29 | """Create cloudwatch metric alarm.""" 30 | client = boto3.client("cloudwatch", region_name=region_name) 31 | 32 | client.put_metric_alarm( 33 | AlarmName="alarm-test", 34 | MetricName="CPUUtilization", 35 | Namespace="AWS/EC2", 36 | Period=120, 37 | EvaluationPeriods=2, 38 | Statistic="Average", 39 | Threshold=80, 40 | ComparisonOperator="GreaterThanOrEqualToThreshold", 41 | ) 42 | -------------------------------------------------------------------------------- /tests/unit/network/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/network/test_eip_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the eip nuke class.""" 3 | 4 | import boto3 5 | 6 | from moto import mock_ec2 7 | 8 | from package.nuke.network.eip import NukeEip 9 | 10 | from .utils import create_eip 11 | 12 | import pytest 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "aws_region, result_count", [ 17 | ("eu-west-1", 0), 18 | ("eu-west-2", 0), 19 | ] 20 | ) 21 | @mock_ec2 22 | def test_eip_nuke(aws_region, result_count): 23 | """Verify eip nuke function.""" 24 | client = boto3.client("ec2", region_name=aws_region) 25 | 26 | create_eip(region_name=aws_region) 27 | eip = NukeEip(aws_region) 28 | eip.nuke() 29 | eip_list = client.describe_addresses()["Addresses"] 30 | assert len(eip_list) == result_count 31 | -------------------------------------------------------------------------------- /tests/unit/network/test_network_acl_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the security nuke class.""" 3 | 4 | import boto3 5 | 6 | from moto import mock_ec2 7 | 8 | from package.nuke.network.network_acl import NukeNetworkAcl 9 | 10 | from .utils import create_network_acl 11 | 12 | import pytest 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "aws_region, result_count", [ 17 | ("eu-west-1", 0), 18 | ("eu-west-2", 0), 19 | ] 20 | ) 21 | @mock_ec2 22 | def test_security_nuke(aws_region, result_count): 23 | """Verify security nuke function.""" 24 | client = boto3.client("ec2", region_name=aws_region) 25 | 26 | create_network_acl(region_name=aws_region) 27 | security = NukeNetworkAcl(aws_region) 28 | security.nuke() 29 | network_acl_list = client.describe_network_acls()["NetworkAcls"] 30 | assert len(network_acl_list) == result_count 31 | -------------------------------------------------------------------------------- /tests/unit/network/test_security_group_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the security nuke class.""" 3 | 4 | import boto3 5 | 6 | from moto import mock_ec2 7 | 8 | from package.nuke.network.security_group import NukeSecurityGroup 9 | 10 | from .utils import create_security_group 11 | 12 | import pytest 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "aws_region, result_count", [ 17 | ("eu-west-1", 0), 18 | ("eu-west-2", 0), 19 | ] 20 | ) 21 | @mock_ec2 22 | def test_security_nuke(aws_region, result_count): 23 | """Verify security nuke function.""" 24 | client = boto3.client("ec2", region_name=aws_region) 25 | 26 | create_security_group(region_name=aws_region) 27 | security = NukeSecurityGroup(aws_region) 28 | security.nuke() 29 | security_group_list = client.describe_security_groups()["SecurityGroups"] 30 | assert len(security_group_list) == result_count 31 | -------------------------------------------------------------------------------- /tests/unit/network/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_eip(region_name): 8 | """Create eip.""" 9 | client = boto3.client("ec2", region_name=region_name) 10 | client.allocate_address(Domain="vpc", Address="127.38.43.222") 11 | 12 | 13 | def create_security_group(region_name): 14 | """Create security group and network acl.""" 15 | client = boto3.client("ec2", region_name=region_name) 16 | 17 | client.create_security_group(GroupName="sg-test", Description="sg test") 18 | 19 | def create_network_acl(region_name): 20 | """Create security group and network acl.""" 21 | client = boto3.client("ec2", region_name=region_name) 22 | 23 | client.create_network_acl(VpcId=client.describe_vpcs()["Vpcs"][0]["VpcId"]) 24 | -------------------------------------------------------------------------------- /tests/unit/storage/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Main entry point for unit tests.""" 3 | -------------------------------------------------------------------------------- /tests/unit/storage/test_glacier_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the glacier nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_glacier 8 | 9 | from package.nuke.storage.glacier import NukeGlacier 10 | 11 | from .utils import create_glacier 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", 18 | [ 19 | ("eu-west-1", time.time() + 43200, 0), 20 | ("eu-west-2", time.time() + 43200, 0), 21 | ("eu-west-2", 630720000, 1), 22 | ], 23 | ) 24 | @mock_glacier 25 | def test_glacier_nuke(aws_region, older_than_seconds, result_count): 26 | """Verify glacier nuke function.""" 27 | client = boto3.client("glacier", region_name=aws_region) 28 | 29 | create_glacier(region_name=aws_region) 30 | glacier = NukeGlacier(aws_region) 31 | glacier.nuke(older_than_seconds=older_than_seconds) 32 | glacier_list = client.list_vaults()["VaultList"] 33 | assert len(glacier_list) == result_count 34 | -------------------------------------------------------------------------------- /tests/unit/storage/test_s3_nuke.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Tests for the s3 nuke class.""" 3 | 4 | import boto3 5 | import time 6 | 7 | from moto import mock_s3 8 | 9 | from package.nuke.storage.s3 import NukeS3 10 | 11 | from .utils import create_s3 12 | 13 | import pytest 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "aws_region, older_than_seconds, result_count", [ 18 | ("eu-west-1", time.time() + 43200, 0), 19 | ("eu-west-2", time.time() + 43200, 0), 20 | ("eu-west-2", 630720000, 1), 21 | ] 22 | ) 23 | @mock_s3 24 | def test_s3_nuke(aws_region, older_than_seconds, result_count): 25 | """Verify s3 nuke function.""" 26 | client = boto3.client("s3", region_name=aws_region) 27 | 28 | create_s3(region_name=aws_region) 29 | s3 = NukeS3(aws_region) 30 | s3.nuke(older_than_seconds=older_than_seconds) 31 | s3_list = client.list_buckets()["Buckets"] 32 | assert len(s3_list) == result_count 33 | -------------------------------------------------------------------------------- /tests/unit/storage/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Module use by nuke unit tests.""" 3 | 4 | import boto3 5 | 6 | 7 | def create_glacier(region_name): 8 | """Create glacier vault.""" 9 | client = boto3.client("glacier", region_name=region_name) 10 | client.create_vault(vaultName="glacier-test") 11 | 12 | 13 | def create_s3(region_name): 14 | """Create s3 bucket.""" 15 | client = boto3.client("s3", region_name=region_name) 16 | client_resource = boto3.resource('s3', region_name=region_name) 17 | client.create_bucket(Bucket="s3-test") 18 | file_content = b"This is a content file" 19 | s3_object = client_resource.Object("s3-test", "hello.txt") 20 | s3_object.put(Body=file_content) 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion=2.3.1 3 | envlist = py35,py36,py37,py38,pytest,black,flake8,pylint,bandit,mypy 4 | skipsdist = True 5 | 6 | [testenv:pytest] 7 | basepython = python3 8 | skip_install = true 9 | deps = 10 | pytest==8.* 11 | pytest-cov==6.* 12 | pytest-pythonpath==0.* 13 | botocore==1.* 14 | boto3==1.* 15 | moto==5.* 16 | commands = 17 | coverage run -m pytest tests/unit --disable-pytest-warnings --cov package 18 | 19 | [testenv:black] 20 | basepython = python3 21 | skip_install = true 22 | deps = 23 | black==25.* 24 | commands = 25 | black package/ --line-length 79 --check 26 | 27 | [testenv:flake8] 28 | basepython = python3 29 | skip_install = true 30 | deps = 31 | flake8==3.* 32 | flake8-colors==0.* 33 | flake8-docstrings==1.* 34 | pydocstyle==5.* 35 | flake8-import-order==0.* 36 | flake8-typing-imports==1.* 37 | pep8-naming==0.* 38 | commands = 39 | flake8 package/ 40 | 41 | [testenv:pylint] 42 | basepython = python3 43 | skip_install = true 44 | deps = 45 | pyflakes==2.* 46 | pylint==2.* 47 | commands = 48 | pylint package/ --rcfile=tests/sanity/.pylintrc 49 | 50 | [testenv:bandit] 51 | basepython = python3 52 | skip_install = true 53 | deps = 54 | bandit==1.* 55 | commands = 56 | bandit -r package/ -c tests/sanity/.bandit.yml 57 | 58 | [testenv:mypy] 59 | basepython = python3 60 | skip_install = true 61 | deps = 62 | mypy==0.* 63 | commands = 64 | mypy --ignore-missing-imports package/ 65 | 66 | [flake8] 67 | ignore = D401, W503 68 | max-complexity = 10 69 | exclude = package/nuke/timeparse.py 70 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # Terraform variables file 2 | 3 | # Set cloudwatch events for shutingdown instances 4 | # trigger lambda functuon every night at 22h00 from Monday to Friday 5 | # cf doc : https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html 6 | variable "cloudwatch_schedule_expression" { 7 | description = "Define the aws cloudwatch event rule schedule expression" 8 | type = string 9 | default = "cron(0 22 ? * MON-FRI *)" 10 | } 11 | 12 | variable "name" { 13 | description = "Define name to use for lambda function, cloudwatch event and iam role" 14 | type = string 15 | default = "everything" 16 | } 17 | 18 | variable "runtime" { 19 | description = "Lambda function runtime" 20 | type = string 21 | default = "python3.11" 22 | } 23 | 24 | variable "custom_iam_role_arn" { 25 | description = "Custom IAM role arn for the scheduling lambda" 26 | type = string 27 | default = null 28 | } 29 | 30 | variable "kms_key_arn" { 31 | description = "The ARN for the KMS encryption key. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key." 32 | type = string 33 | default = null 34 | } 35 | 36 | variable "aws_regions" { 37 | description = "A list of one or more aws regions where the lambda will be apply, default use the current region" 38 | type = list(string) 39 | default = null 40 | } 41 | 42 | variable "exclude_resources" { 43 | description = "Define the resources that will not be destroyed" 44 | type = string 45 | default = null 46 | } 47 | 48 | variable "older_than" { 49 | description = "Only destroy resources that were created before a certain period" 50 | type = string 51 | default = "0d" 52 | } 53 | 54 | variable "tags" { 55 | description = "A map of tags to assign to the resources." 56 | type = map(any) 57 | default = null 58 | } 59 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | 5 | required_providers { 6 | aws = ">= 2.9.0" 7 | archive = ">= 1.2.2" 8 | } 9 | } 10 | --------------------------------------------------------------------------------