├── .github └── workflows │ └── archive.yml ├── .gitignore ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md └── resource_mover.rb /.github/workflows/archive.yml: -------------------------------------------------------------------------------- 1 | name: Archive and Attach to Release for Brew Formula 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | archiveAndAttach: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v2 15 | with: 16 | ref: main 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: '3.x' 22 | 23 | - name: Install dependencies 24 | run: pip install pyopenssl 25 | 26 | - name: Create and Switch to New Branch 27 | run: | 28 | git switch main 29 | 30 | - name: Copy File with New Name 31 | run: | 32 | cp resource_mover.rb terraform-state-split 33 | 34 | - name: Create Archive 35 | run: tar -czvf terraform-state-split.tar.gz terraform-state-split 36 | 37 | - name: Upload Archive 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: terraform-state-split 41 | path: terraform-state-split.tar.gz 42 | 43 | - name: Get Release ID 44 | id: release 45 | run: echo "::set-output name=id::${{ github.event.release.id }}" 46 | 47 | - name: Download Archive 48 | uses: actions/download-artifact@v2 49 | with: 50 | name: terraform-state-split 51 | path: temp 52 | 53 | - name: Attach Archive to Release 54 | uses: actions/upload-release-asset@v1 55 | env: 56 | GITHUB_TOKEN: ${{ github.token }} 57 | with: 58 | upload_url: ${{ github.event.release.upload_url }} 59 | asset_path: temp/terraform-state-split.tar.gz 60 | asset_name: terraform-state-split.tar.gz 61 | asset_content_type: application/gzip 62 | 63 | 64 | - name: SHA256 Calculation 65 | id: sha256_calc 66 | run: | 67 | mkdir new_repo 68 | wget "https://github.com/shebang-labs/terraform-state-split/releases/download/${{ github.event.release.tag_name }}/terraform-state-split.tar.gz" -O "terraform-state-split.tar.gz" 69 | TAR_FILE="terraform-state-split.tar.gz" 70 | 71 | SHA256_HASH=$(openssl dgst -sha256 "$TAR_FILE" | awk '{print $2}') 72 | echo "SHA256 Hash: $SHA256_HASH" 73 | echo "::set-output name=hash::$SHA256_HASH" 74 | working-directory: ${{ github.workspace }} 75 | 76 | - name: Clone another repository 77 | env: 78 | access_token: ${{ secrets.HOMEBREW_TAP_GH_PAT }} 79 | run: | 80 | git config --global user.name "Shebang Labs" 81 | git config --global user.email "hello@shebanglabs.io" 82 | git clone https://shebang-labs:${{ secrets.HOMEBREW_TAP_GH_PAT }}@github.com/shebang-labs/homebrew-tap.git 83 | working-directory: ${{ github.workspace }} 84 | 85 | - name: Copy and Replace Version Name 86 | run: | 87 | sed -i 's/^\(\s*url\s*\)[^[:space:]]*/\1"https:\/\/github.com\/shebang-labs\/terraform-state-split\/releases\/download\/${{ github.event.release.tag_name }}\/terraform-state-split.tar.gz"/' homebrew-tap/terraform-state-split.rb 88 | sed -i 's/^\(\s*sha256\s*\)[^[:space:]]*/\1"${{ steps.sha256_calc.outputs.hash }}"/' homebrew-tap/terraform-state-split.rb 89 | 90 | working-directory: ${{ github.workspace }} 91 | 92 | 93 | - name: Commit and Push changes 94 | env: 95 | access_token: ${{ secrets.HOMEBREW_TAP_GH_PAT }} 96 | run: | 97 | cd homebrew-tap 98 | git add terraform-state-split.rb 99 | git commit -m "Update terraform-state-split.rb with new version" 100 | git push origin main 101 | working-directory: ${{ github.workspace }} 102 | 103 | - name: Clean up 104 | run: | 105 | git checkout main 106 | rm -rf temp terraform-state-split.tar.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /doc/ 39 | /rdoc/ 40 | 41 | ## Environment normalization: 42 | /.bundle/ 43 | /vendor/bundle 44 | /lib/bundler/man/ 45 | 46 | # for a library or gem, you might want to ignore these files since the code is 47 | # intended to run in multiple environments; otherwise, check them in: 48 | # Gemfile.lock 49 | # .ruby-version 50 | # .ruby-gemset 51 | 52 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 53 | .rvmrc 54 | 55 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 56 | # .rubocop-https?--* 57 | .idea 58 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby 2 | 3 | ENV WORKDIR /terraform 4 | ENV LIBDIR /terraform-state-split 5 | ARG TF_VERSION=1.5.0 6 | 7 | ################################ 8 | # Install Terraform 9 | ################################ 10 | RUN wget https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip 11 | 12 | # Unzip 13 | RUN unzip terraform_${TF_VERSION}_linux_amd64.zip 14 | 15 | # Move to local bin 16 | RUN mv terraform /usr/local/bin/ 17 | 18 | # Check that it's installed 19 | RUN terraform --version && rm terraform_${TF_VERSION}_linux_amd64.zip 20 | 21 | ################################ 22 | # Install terraform-state-split dependencies 23 | ################################ 24 | ADD . $LIBDIR 25 | 26 | RUN gem install bundler 27 | 28 | RUN cd $LIBDIR && bundle install 29 | 30 | ################################ 31 | # Set working directory and run 32 | ################################ 33 | WORKDIR $WORKDIR 34 | CMD ruby $LIBDIR/resource_mover.rb 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'tty-prompt' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | pastel (0.8.0) 5 | tty-color (~> 0.5) 6 | tty-color (0.6.0) 7 | tty-cursor (0.7.1) 8 | tty-prompt (0.23.1) 9 | pastel (~> 0.8) 10 | tty-reader (~> 0.8) 11 | tty-reader (0.9.0) 12 | tty-cursor (~> 0.7) 13 | tty-screen (~> 0.8) 14 | wisper (~> 2.0) 15 | tty-screen (0.8.1) 16 | wisper (2.0.1) 17 | 18 | PLATFORMS 19 | arm64-darwin-22 20 | 21 | DEPENDENCIES 22 | tty-prompt 23 | 24 | BUNDLED WITH 25 | 2.4.10 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shebang Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform State Split 2 | 3 | Terraform State Split is a Ruby-based CLI tool designed to help you organize and manage large Terraform state files. It leverages the tty-prompt gem to provide an interactive command-line interface for moving selected resources between Terraform state files. 4 | 5 | ## Demo 6 | 7 | [![asciicast](https://asciinema.org/a/qqF2E5Uz2ybwzhJdMpuufzblu.svg)](https://asciinema.org/a/qqF2E5Uz2ybwzhJdMpuufzblu) 8 | 9 | ## Getting Started 10 | 11 | ### From Source 12 | 13 | For utilizing this tool, ensure Ruby is installed on your machine. Also, tty-prompt gem along with other required dependencies must be installed using the following command: 14 | 15 | ```bash 16 | bundle install 17 | ``` 18 | 19 | Terraform must also be [installed](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) and [initialized](https://developer.hashicorp.com/terraform/cli/commands/init) within your working directory. 20 | 21 | To execute `terraform-state-split`, use the following commands: 22 | 23 | ```bash 24 | terraform state pull > tf.state # Extract the state file from your Terraform workspace or remote state storage 25 | ruby resource_mover.rb # Run the script and follow the prompts to select the resources to be moved 26 | ``` 27 | 28 | ### Homebrew (For MacOS) 29 | 30 | For MacOS users, the tool can be installed via Homebrew using the following commands: 31 | 32 | ```bash 33 | brew tap shebang-labs/tap 34 | brew install terraform-state-split 35 | ``` 36 | 37 | To run `terraform-state-split`, execute the following: 38 | 39 | ```bash 40 | terraform state pull > tf.state # Extract the state file from your Terraform workspace or remote state storage 41 | terraform-state-split # Run the script and follow the prompts to select the resources to be moved 42 | ``` 43 | 44 | ## How to Use 45 | 46 | The tool will prompt you to provide the absolute input state file path and the absolute output state file path. These are the paths of the state files from which and to which you wish to move resources. 47 | 48 | Next, all resources present in the input state file will be displayed and you'll be prompted to select the ones to be moved. Make your selection using arrow keys, space bar, and enter key. 49 | 50 | The tool will then utilize the `terraform state mv` command to transfer the selected resources from the input state file to the output state file. 51 | -------------------------------------------------------------------------------- /resource_mover.rb: -------------------------------------------------------------------------------- 1 | require 'tty-prompt' 2 | require 'json' 3 | 4 | prompt = TTY::Prompt.new 5 | 6 | in_state_path = ENV['TF_IN_STATE_PATH'] || prompt.ask('Please enter the absolute input state file path.', convert: :filepath, default: '/terraform/in-terraform.tfstate') 7 | out_state_path = ENV['TF_OUT_STATE_PATH'] || prompt.ask('Please enter the absolute output state file path.', convert: :filepath, default: '/terraform//out-terraform.tfstate') 8 | 9 | in_state_file = File.read(in_state_path) 10 | in_state = JSON.parse(in_state_file) 11 | resources = in_state['resources'] 12 | choices = [] 13 | 14 | resources.each do |resource| 15 | choice = "" 16 | unless resource['module'].nil? 17 | choices << resource['module'] if choices.none? { |c| c == resource['module'] } 18 | choice += resource['module'] + '.' 19 | end 20 | choice += resource['type'] + '.' 21 | choice += resource['name'] 22 | choices << choice 23 | end 24 | 25 | system("cd '#{File.dirname(in_state_path)}' && terraform init") 26 | result = prompt.multi_select("Resources to be moved? (Use ↑/↓ arrow keys, press Space to select and Enter to finish)", choices, symbols: { marker: ">" }) 27 | result.each do |resource_name| 28 | system("cd '#{File.dirname(in_state_path)}' && terraform state mv -state='#{in_state_path}' -state-out='#{out_state_path}' '#{resource_name}' '#{resource_name}'") 29 | end 30 | 31 | --------------------------------------------------------------------------------