├── .github ├── no-response.yml └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── GNUmakefile ├── LICENSE ├── README.md ├── README.release.md ├── b2 ├── bindings.go ├── client.go ├── data_source_b2_account_info.go ├── data_source_b2_account_info_test.go ├── data_source_b2_application_key.go ├── data_source_b2_application_key_test.go ├── data_source_b2_bucket.go ├── data_source_b2_bucket_file.go ├── data_source_b2_bucket_file_signed_url.go ├── data_source_b2_bucket_file_signed_url_test.go ├── data_source_b2_bucket_file_test.go ├── data_source_b2_bucket_files.go ├── data_source_b2_bucket_files_test.go ├── data_source_b2_bucket_notification_rules.go ├── data_source_b2_bucket_notification_rules_test.go ├── data_source_b2_bucket_test.go ├── provider.go ├── provider_test.go ├── resource_b2_application_key.go ├── resource_b2_application_key_test.go ├── resource_b2_bucket.go ├── resource_b2_bucket_file_version.go ├── resource_b2_bucket_file_version_test.go ├── resource_b2_bucket_notification_rules.go ├── resource_b2_bucket_notification_rules_test.go ├── resource_b2_bucket_test.go ├── templates.go ├── utils.go └── validators.go ├── docs ├── data-sources │ ├── account_info.md │ ├── application_key.md │ ├── bucket.md │ ├── bucket_file.md │ ├── bucket_file_signed_url.md │ ├── bucket_files.md │ └── bucket_notification_rules.md ├── index.md └── resources │ ├── application_key.md │ ├── bucket.md │ ├── bucket_file_version.md │ └── bucket_notification_rules.md ├── examples ├── application_key │ └── main.tf ├── bucket │ ├── example.txt │ └── main.tf ├── bucket_file_lock │ └── main.tf ├── bucket_notification_rules │ └── main.tf ├── file_version_sse_c │ └── main.tf └── provider │ └── provider.tf ├── go.mod ├── go.sum ├── main.go ├── python-bindings ├── GNUmakefile ├── b2_terraform │ ├── __init__.py │ ├── arg_parser.py │ ├── json_encoder.py │ ├── provider_tool.py │ └── terraform_structures.py ├── py-terraform-provider-b2.spec ├── requirements-dev.txt └── requirements.txt ├── scripts ├── check-gofmt.py └── check-headers.py ├── templates └── index.md.tmpl └── tools ├── go.mod ├── go.sum └── main.go /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate or assist you further. 16 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | push: 5 | tags: ['v*'] # push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | defaults: 8 | run: 9 | shell: bash 10 | 11 | env: 12 | PYTHON_DEFAULT_VERSION: '3.13' 13 | GO_DEFAULT_VERSION: '1.24' 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | jobs: 17 | build-pybindings: 18 | runs-on: ${{ matrix.conf.runner }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | conf: 23 | - { runner: ubuntu-latest, os: linux, arch: amd64 } 24 | - { runner: ubuntu-latest, os: linux, arch: arm64 } 25 | - { runner: macos-13, os: darwin, arch: amd64 } 26 | - { runner: macos-14, os: darwin, arch: arm64 } 27 | - { runner: windows-2019, os: windows, arch: amd64 } 28 | outputs: 29 | version: ${{ steps.build.outputs.version }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Start a Docker container (linux-arm64) 33 | if: matrix.conf.os == 'linux' && matrix.conf.arch == 'arm64' 34 | run: | 35 | docker run --privileged --rm tonistiigi/binfmt:qemu-v8.0.4 --install arm64 36 | docker run --detach \ 37 | --platform linux/arm64 \ 38 | --volume .:/work \ 39 | --name builder \ 40 | arm64v8/python:${{ env.PYTHON_DEFAULT_VERSION }}-bullseye \ 41 | /bin/bash -c "sleep infinity" 42 | - name: Start a Docker container (linux-amd64) 43 | if: matrix.conf.os == 'linux' && matrix.conf.arch == 'amd64' 44 | run: | 45 | docker run --detach \ 46 | --volume .:/work \ 47 | --name builder \ 48 | python:${{ env.PYTHON_DEFAULT_VERSION }}-bullseye \ 49 | /bin/bash -c "sleep infinity" 50 | - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} (darwin, windows) 51 | if: matrix.conf.os != 'linux' 52 | uses: actions/setup-python@v5 53 | with: 54 | python-version: ${{ env.PYTHON_DEFAULT_VERSION }} 55 | - name: Define command wrapper (linux) 56 | if: matrix.conf.os == 'linux' 57 | run: | 58 | # We'll define a 'run' command that will run our commands in the container. 59 | echo '#!/bin/bash' > run 60 | echo 'command="$*"; docker exec --workdir /work/python-bindings builder /bin/bash -c "$command"' >> run 61 | chmod +x run 62 | sudo mv run /usr/local/bin/run 63 | - name: Define command wrapper (darwin) 64 | if: matrix.conf.os == 'darwin' 65 | run: | 66 | # MacOS wrapper just runs commands in python-bindings directory 67 | echo '#!/bin/bash' > run 68 | echo 'pushd python-bindings; "$@"; popd' >> run 69 | chmod +x run 70 | sudo mv run /usr/local/bin/run 71 | - name: Define command wrapper (windows) 72 | if: matrix.conf.os == 'windows' 73 | run: | 74 | # Windows wrapper just runs commands in python-bindings directory 75 | echo '#!/bin/bash' > run 76 | echo 'pushd python-bindings; "$@"; popd' >> run 77 | chmod +x run 78 | mv run /usr/bin/run 79 | - name: Install system dependencies (linux) 80 | if: matrix.conf.os == 'linux' 81 | run: | 82 | run apt update -y 83 | run apt install scons patchelf libnss3-dev -y 84 | - name: Install Python dependencies 85 | run: | 86 | run make deps 87 | - name: Build Python bindings 88 | id: build 89 | run: | 90 | run make build 91 | - if: matrix.conf.os == 'linux' 92 | run: | 93 | sudo chmod -R a+r python-bindings 94 | - name: Upload Python bindings 95 | uses: actions/upload-artifact@v4 96 | with: 97 | name: py-terraform-provider-b2-${{ matrix.conf.os }}-${{ matrix.conf.arch }} 98 | path: python-bindings/dist/py-terraform-provider-b2 99 | if-no-files-found: error 100 | retention-days: 1 101 | build-and-deploy: 102 | needs: [build-pybindings] 103 | env: 104 | NOPYBINDINGS: 1 # do not build python bindings 105 | runs-on: ubuntu-latest 106 | outputs: 107 | upload_url: ${{ steps.create-release.outputs.upload_url }} 108 | steps: 109 | - uses: actions/checkout@v4 110 | with: 111 | fetch-depth: 0 112 | - name: Set up Go ${{ env.GO_DEFAULT_VERSION }} 113 | uses: actions/setup-go@v5 114 | with: 115 | go-version: ${{ env.GO_DEFAULT_VERSION }} 116 | - name: Install dependencies 117 | run: | 118 | make deps 119 | - name: Download python bindings for all OSes 120 | uses: actions/download-artifact@v4 121 | with: 122 | path: python-bindings/dist/artifacts/ 123 | - name: Postprocess python bindings 124 | working-directory: python-bindings/dist 125 | run: | 126 | mv artifacts/py-terraform-provider-b2-linux-amd64/py-terraform-provider-b2 py-terraform-provider-b2-linux-amd64 127 | mv artifacts/py-terraform-provider-b2-linux-arm64/py-terraform-provider-b2 py-terraform-provider-b2-linux-arm64 128 | mv artifacts/py-terraform-provider-b2-darwin-amd64/py-terraform-provider-b2 py-terraform-provider-b2-darwin-amd64 129 | mv artifacts/py-terraform-provider-b2-darwin-arm64/py-terraform-provider-b2 py-terraform-provider-b2-darwin-arm64 130 | mv artifacts/py-terraform-provider-b2-windows-amd64/py-terraform-provider-b2 py-terraform-provider-b2-windows-amd64 131 | - name: Set release version output 132 | id: version 133 | run: | 134 | tag=${{ github.ref_name }} 135 | # Strip the prefix 'v' 136 | version=${tag:1} 137 | echo "version=$version" >> $GITHUB_OUTPUT 138 | - name: Read the Changelog 139 | id: read-changelog 140 | uses: mindsers/changelog-reader-action@v2 141 | with: 142 | version: ${{ steps.version.outputs.version }} 143 | - name: Import GPG key 144 | id: import_gpg 145 | uses: crazy-max/ghaction-import-gpg@v6 146 | with: 147 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 148 | passphrase: ${{ secrets.PASSPHRASE }} 149 | - name: Create GitHub release 150 | uses: goreleaser/goreleaser-action@v6 151 | with: 152 | version: '~> v2' 153 | args: release --clean -p 1 154 | env: 155 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 156 | - name: Update GitHub release 157 | uses: softprops/action-gh-release@v2 158 | with: 159 | name: v${{ steps.version.outputs.version }} 160 | body: ${{ steps.read-changelog.outputs.changes }} 161 | draft: false 162 | prerelease: false 163 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: 7 | - '.github/no-response.yml' 8 | - '.github/workflows/cd.yml' 9 | - 'LICENSE' 10 | - 'README.md' 11 | - 'README.release.md' 12 | pull_request: 13 | branches: [master] 14 | 15 | defaults: 16 | run: 17 | shell: bash 18 | 19 | env: 20 | PYTHON_DEFAULT_VERSION: '3.13' 21 | GO_DEFAULT_VERSION: '1.24' 22 | 23 | jobs: 24 | lint: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} 29 | uses: deadsnakes/action@v3.1.0 # staticx doesn't work with python installed by setup-python action 30 | with: 31 | python-version: ${{ env.PYTHON_DEFAULT_VERSION }} 32 | - name: Set up Go ${{ env.GO_DEFAULT_VERSION }} 33 | uses: actions/setup-go@v5 34 | with: 35 | go-version: ${{ env.GO_DEFAULT_VERSION }} 36 | - name: Install dependencies 37 | run: | 38 | make deps 39 | - name: Run dependency checker 40 | run: | 41 | make deps-check 42 | - name: Run linters 43 | run: | 44 | make lint 45 | - name: Run docs linters 46 | run: | 47 | make docs-lint 48 | - name: Validate changelog 49 | # Library was designed to be used with pull requests only. 50 | if: ${{ github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }} 51 | uses: zattoo/changelog@v2 52 | with: 53 | token: ${{ github.token }} 54 | build: 55 | needs: lint 56 | runs-on: ${{ matrix.conf.runner }} 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | conf: 61 | - { runner: ubuntu-latest, os: linux, arch: amd64 } 62 | - { runner: macos-13, os: darwin, arch: amd64 } 63 | - { runner: macos-14, os: darwin, arch: arm64 } 64 | - { runner: windows-2019, os: windows, arch: amd64 } 65 | steps: 66 | - uses: actions/checkout@v4 67 | - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} (ubuntu-latest) 68 | if: matrix.conf.os == 'linux' 69 | uses: deadsnakes/action@v3.1.0 # staticx doesn't work with python installed by setup-python action 70 | with: 71 | python-version: ${{ env.PYTHON_DEFAULT_VERSION }} 72 | - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} 73 | if: matrix.conf.os != 'linux' 74 | uses: actions/setup-python@v5 75 | with: 76 | python-version: ${{ env.PYTHON_DEFAULT_VERSION }} 77 | - name: Set up Go ${{ env.GO_DEFAULT_VERSION }} 78 | uses: actions/setup-go@v5 79 | with: 80 | go-version: ${{ env.GO_DEFAULT_VERSION }} 81 | - name: Install dependencies 82 | run: | 83 | make deps 84 | - name: Build the provider 85 | run: | 86 | make build 87 | - name: Upload python bindings 88 | uses: actions/upload-artifact@v4 89 | with: 90 | name: py-terraform-provider-b2-${{ runner.os }}-${{ runner.arch }} 91 | path: python-bindings/dist/py-terraform-provider-b2 92 | if-no-files-found: error 93 | retention-days: 1 94 | test: 95 | needs: build 96 | env: 97 | B2_TEST_APPLICATION_KEY: ${{ secrets.B2_TEST_APPLICATION_KEY }} 98 | B2_TEST_APPLICATION_KEY_ID: ${{ secrets.B2_TEST_APPLICATION_KEY_ID }} 99 | NOPYBINDINGS: 1 # do not build python buildings 100 | runs-on: ${{ matrix.conf.runner }} 101 | strategy: 102 | fail-fast: false 103 | matrix: 104 | conf: 105 | - { runner: ubuntu-latest, os: linux, arch: amd64, terraform: '1.9.*' } 106 | - { runner: ubuntu-latest, os: linux, arch: amd64, terraform: '1.8.*' } 107 | # for macOS, the latest terraform is enough for ACC tests 108 | - { runner: macos-13, os: darwin, arch: amd64, terraform: '1.9.*' } 109 | - { runner: macos-14, os: darwin, arch: arm64, terraform: '1.9.*' } 110 | # for Windows, the latest terraform is enough for ACC tests 111 | - { runner: windows-2019, os: windows, arch: amd64, terraform: '1.9.*' } 112 | steps: 113 | - uses: actions/checkout@v4 114 | - name: Set up Go ${{ env.GO_DEFAULT_VERSION }} 115 | if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} 116 | uses: actions/setup-go@v5 117 | with: 118 | go-version: ${{ env.GO_DEFAULT_VERSION }} 119 | - uses: hashicorp/setup-terraform@v3 120 | name: Set up Terraform ${{ matrix.conf.terraform }} 121 | with: 122 | terraform_version: ${{ matrix.conf.terraform }} 123 | terraform_wrapper: false 124 | - name: Download python bindings for given OS 125 | if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} 126 | uses: actions/download-artifact@v4 127 | with: 128 | name: py-terraform-provider-b2-${{ runner.os }}-${{ runner.arch }} 129 | path: python-bindings/dist/ 130 | - name: Run acceptance tests 131 | if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} 132 | timeout-minutes: 120 133 | run: | 134 | make testacc 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | terraform.tfplan 6 | terraform.tfstate 7 | bin/ 8 | dist/ 9 | modules-dev/ 10 | /pkg/ 11 | website/.vagrant 12 | website/.bundle 13 | website/build 14 | website/node_modules 15 | .vagrant/ 16 | *.backup 17 | ./*.tfstate 18 | .terraform/ 19 | .terraform.lock.hcl 20 | *.log 21 | *.bak 22 | *~ 23 | .*.swp 24 | .idea 25 | *.iml 26 | *.test 27 | *.iml 28 | 29 | website/vendor 30 | 31 | # Test exclusions 32 | !command/test-fixtures/**/*.tfstate 33 | !command/test-fixtures/**/.terraform/ 34 | 35 | # Keep windows files with windows line endings 36 | *.winfile eol=crlf 37 | 38 | # Go 39 | .go-version 40 | 41 | # Provider binary 42 | /terraform-provider-b2 43 | 44 | # Python bindings 45 | *.pyc 46 | *.egg-info 47 | .python-version 48 | b2/py-terraform-provider-b2 49 | build/ 50 | dist/ 51 | venv/ 52 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | before: 3 | hooks: 4 | - go mod download 5 | builds: 6 | - binary: '{{ .ProjectName }}_{{ .Version }}' 7 | mod_timestamp: '{{ .CommitTimestamp }}' 8 | flags: 9 | - -trimpath 10 | ldflags: 11 | - '-s -w -X main.version={{.Version}}' 12 | goos: 13 | - linux 14 | - darwin 15 | - windows 16 | goarch: 17 | - amd64 18 | - arm64 19 | ignore: 20 | - goos: windows 21 | goarch: arm64 22 | hooks: 23 | pre: 24 | - cp python-bindings/dist/py-terraform-provider-b2-{{ .Os }}-{{ .Arch }} b2/py-terraform-provider-b2 25 | post: 26 | - rm -f b2/py-terraform-provider-b2 27 | env: 28 | # goreleaser does not work with CGO, it could also complicate 29 | # usage by users in CI/CD systems like Terraform Cloud where 30 | # they are unable to install libraries. 31 | - CGO_ENABLED=0 32 | archives: 33 | - format: zip 34 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 35 | checksum: 36 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 37 | algorithm: sha256 38 | signs: 39 | - artifacts: checksum 40 | args: 41 | # if you are using this in a GitHub action or some other automated pipeline, you 42 | # need to pass the batch flag to indicate its not interactive. 43 | - "--batch" 44 | - "--local-user" 45 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 46 | - "--output" 47 | - "${signature}" 48 | - "--detach-sign" 49 | - "${artifact}" 50 | changelog: 51 | disable: true 52 | -------------------------------------------------------------------------------- /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 | 9 | ### Changed 10 | * Upgraded b2sdk from v2.4.1 to v2.8.0 11 | * Use `b2sdk.v2` in python bindings 12 | 13 | ### Infrastructure 14 | * Upgrade pyinstaller to 6.11.1 15 | * Use `go vet` for linting 16 | * Use Go 1.24.1 17 | 18 | ### Fixed 19 | * Fix an issue with missing `content_md5` in case the file has been uploaded as a large file 20 | 21 | ## [0.10.0] - 2025-01-10 22 | 23 | ### Added 24 | * Added support for Event Notifications via `b2_bucket_notification_rules` resource and data source 25 | 26 | ### Infrastructure 27 | * Replace deprecated macos-12 with macos-13 28 | * Use crazy-max/ghaction-import-gpg action as a replacement of deprecated paultyng/ghaction-import-gpg 29 | * Use Python 3.13 for embedded pybindings 30 | 31 | ## [0.9.0] - 2024-10-20 32 | 33 | ### Infrastructure 34 | * Replace removed macOS 11 Big Sur in favour of macOS 12 Monterey in CI/CD 35 | * Add support for Windows 36 | * Use Python 3.12 for embedded pybindings 37 | * Use Go 1.22 38 | 39 | ### Fixed 40 | * Fixed `allowed_operations` stability issue 41 | * Use macos-14 for ARM as macos-13-large is Intel 42 | * Clarified the purpose of the `endpoint`/`B2_ENDPOINT` configuration value 43 | 44 | ## [0.8.12] - 2024-06-20 45 | 46 | ### Infrastructure 47 | * Fixed goreleaser v2 configuration 48 | 49 | ## [0.8.11] - 2024-06-20 50 | 51 | ### Infrastructure 52 | * Fixed continuous delivery issue caused by goreleaser update 53 | 54 | ## [0.8.10] - 2024-06-20 55 | 56 | ### Changed 57 | * Upgraded b2sdk from v1.18.0 to v2.4.1 58 | * Upgraded pyinstaller from v6.3.0 to v6.8.0 59 | 60 | ## [0.8.9] - 2024-01-05 61 | 62 | ### Changed 63 | * Upgraded github.com/cloudflare/circl from v1.3.3 to v1.3.7 64 | 65 | ### Fixed 66 | * Fixed bucket update when is_file_lock_enabled is not set 67 | 68 | ## [0.8.8] - 2023-12-28 69 | 70 | ### Changed 71 | * Upgraded pyinstaller to v6.3.0 72 | 73 | ## [0.8.7] - 2023-12-27 74 | 75 | ### Changed 76 | * Upgraded github.com/hashicorp/terraform-plugin-sdk to v2.31.0 77 | 78 | ## [0.8.6] - 2023-12-22 79 | 80 | ### Fixed 81 | * Fix arm64 builds 82 | 83 | ## [0.8.5] - 2023-11-24 84 | 85 | ### Changed 86 | * Upgraded go to 1.20 and github.com/hashicorp/terraform-plugin-sdk to v2.30.0 87 | 88 | ### Infrastructure 89 | * Disable changelog verification for dependabot PRs 90 | * Upgrade macOS version in CI/CD 91 | * Upgrade Terraform version for ACC tests 92 | * Run ACC tests for all supported Terraform versions 93 | * Do not use deprecated `::set-output` GitHub Actions command in favor of `GITHUB_OUTPUT` env 94 | 95 | ### Fixed 96 | * Reconcile missing Application Key caused by the resource drift 97 | * Fix reconciliation of missing Bucket caused by the resource drift 98 | * Fix bucket cleanup after failed creation 99 | 100 | ## [0.8.4] - 2023-03-13 101 | 102 | ### Infrastructure 103 | * Upgraded terraform-plugin-docs 0.5.1 -> 0.13.0 104 | * Upgraded golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd -> 0.7.0 105 | 106 | ## [0.8.3] - 2023-02-20 107 | 108 | ### Infrastructure 109 | * Upgraded golang.org/x/net 0.5.0 -> 0.7.0 110 | 111 | ## [0.8.2] - 2023-02-17 112 | 113 | ### Infrastructure 114 | * Upgraded goutils 1.1.0 -> 1.1.1 and aws to 1.33.0 115 | * Ensured that changelog validation only happens on pull requests 116 | 117 | ## [0.8.1] - 2022-06-24 118 | 119 | ### Changed 120 | * Upgraded github.com/hashicorp/terraform-plugin-sdk/ to v2.17.0 and github.com/hashicorp/go-getter to v1.6.2 121 | 122 | ### Fixed 123 | * Fixed golangcli-lint breaking on Github 124 | 125 | ## [0.8.0] - 2022-03-27 126 | 127 | ### Added 128 | * Added importer for b2_bucket and b2_application_key resources 129 | * Added signed URL as data source to allow downloading files from private bucket during provisioning without storing an API key 130 | 131 | ### Changed 132 | * Upgraded go to 1.18 and github.com/hashicorp/terraform-plugin-sdk/ to v2.12.0 133 | * Upgraded b2sdk to 1.14.1, which allowed using improved API calls for listing files and making Python parts simpler 134 | * Upgraded PyInstaller to 4.10, which should help resolve some issues with running on Apple M1 silicon 135 | 136 | ## [0.7.1] - 2021-10-14 137 | 138 | ### Changed 139 | * When a bucket that is in state no longer exists, warning is logged and the bucket is removed from state 140 | 141 | ## [0.7.0] - 2021-09-24 142 | 143 | ### Fixed 144 | * Fix for static linking of Python bindings for Linux (CD uses python container) 145 | 146 | ## [0.6.1] - 2021-09-01 147 | 148 | ### Changed 149 | * When deleting bucket that bucket no longer exists, error is silently ignored 150 | * Terraform 1.0.0 or later required 151 | 152 | ## [0.6.0] - 2021-07-06 153 | 154 | ### Added 155 | * Support SSE-C encryption mode for files 156 | * Initial (experimental) support for Alpine Linux 157 | * Support for Apple M1 (arm64) architecture on Mac OS (for main plugin binary, Python bindings will still use Rosetta) 158 | 159 | ## [0.5.0] - 2021-05-31 160 | 161 | ### Added 162 | * Support isFileLockEnabled for buckets 163 | * Support defaultRetention for buckets 164 | 165 | ### Fixed 166 | * Fix acceptance tests breaking when new response fields are added to the API 167 | 168 | ### Changed 169 | * Upgraded b2sdk version to 1.8.0 170 | 171 | ## [0.4.0] - 2021-04-08 172 | 173 | ### Added 174 | * Show S3-compatible API URL in `b2_account_info` data source 175 | 176 | ### Fixed 177 | * Upgrade b2sdk version - fix for server response change regarding SSE 178 | 179 | ## [0.3.0] - 2021-03-27 180 | 181 | ### Added 182 | * Added `b2_account_info` data source 183 | * Add support for SSE-B2 server-side encryption mode 184 | 185 | ### Changed 186 | * Better handling sensitive data in Terraform logs 187 | * Upgrade b2sdk version `>=1.4.0,<2.0.0` 188 | 189 | ## [0.2.1] - 2021-02-11 190 | 191 | ### Changed 192 | * Upgrade b2sdk version 193 | 194 | ### Fixed 195 | * Append Terraform versions to the User-Agent 196 | * Fix the defaults for lifecycle rules #4 197 | 198 | ## [0.2.0] - 2021-01-22 199 | 200 | ### Added 201 | * Added `b2_bucket` data source 202 | * Added `b2_bucket_file` data source 203 | * Added `b2_bucket_files` data source 204 | * Added `b2_application_key` resource 205 | * Added `b2_bucket` resource 206 | * Added `b2_bucket_file_version` resource 207 | 208 | ### Changed 209 | * Extended `b2` provider 210 | * Extended `b2_application_key` data source 211 | * Improved python bindings 212 | 213 | ## [0.1.0] - 2020-11-30 214 | 215 | ### Added 216 | * Implementation of PoC (simple `b2_application_key` data source) 217 | 218 | [Unreleased]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.10.0...HEAD 219 | [0.10.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.9.0...v0.10.0 220 | [0.9.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.12...v0.9.0 221 | [0.8.12]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.11...v0.8.12 222 | [0.8.11]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.10...v0.8.11 223 | [0.8.10]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.9...v0.8.10 224 | [0.8.9]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.8...v0.8.9 225 | [0.8.8]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.7...v0.8.8 226 | [0.8.7]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.6...v0.8.7 227 | [0.8.6]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.5...v0.8.6 228 | [0.8.5]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.4...v0.8.5 229 | [0.8.4]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.3...v0.8.4 230 | [0.8.3]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.2...v0.8.3 231 | [0.8.2]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.1...v0.8.2 232 | [0.8.1]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.8.0...v0.8.1 233 | [0.8.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.7.1...v0.8.0 234 | [0.7.1]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.7.0...v0.7.1 235 | [0.7.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.6.1...v0.7.0 236 | [0.6.1]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.6.0...v0.6.1 237 | [0.6.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.5.0...v0.6.0 238 | [0.5.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.4.0...v0.5.0 239 | [0.4.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.3.0...v0.4.0 240 | [0.3.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.2.1...v0.3.0 241 | [0.2.1]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.2.0...v0.2.1 242 | [0.2.0]: https://github.com/Backblaze/terraform-provider-b2/compare/v0.1.0...v0.2.0 243 | [0.1.0]: https://github.com/Backblaze/terraform-provider-b2/compare/240851d...v0.1.0 244 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | HOSTNAME=localhost 2 | NAMESPACE=backblaze 3 | NAME=b2 4 | BINARY=terraform-provider-${NAME} 5 | VERSION=$(shell git describe --tags --abbrev=0 | cut -c2-) 6 | OS_ARCH=$(shell go env GOOS)_$(shell go env GOARCH) 7 | 8 | default: build 9 | 10 | .PHONY: _pybindings deps deps-check format lint testacc clean build install docs docs-lint 11 | 12 | _pybindings: 13 | ifeq ($(origin NOPYBINDINGS), undefined) 14 | @$(MAKE) -C python-bindings $(MAKECMDGOALS) 15 | else 16 | $(info Skipping python bindings (NOPYBINDINGS is defined)) 17 | endif 18 | 19 | deps: _pybindings 20 | @go mod download 21 | @go mod tidy 22 | @cd tools && go mod download 23 | @cd tools && go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 24 | @cd tools && go mod tidy 25 | 26 | deps-check: 27 | @go mod tidy 28 | @cd tools && go mod tidy 29 | @git diff --exit-code -- go.mod go.sum tools/go.mod tools/go.sum || \ 30 | (echo; echo "Unexpected difference in go.mod/go.sum files. Run 'make deps' command or revert any go.mod/go.sum changes and commit."; exit 1) 31 | 32 | format: _pybindings 33 | @go fmt ./... 34 | @terraform fmt -recursive ./examples/ 35 | 36 | lint: _pybindings 37 | @python scripts/check-gofmt.py '**/*.go' 38 | @python scripts/check-headers.py '**/*.go' 39 | @test -f b2/py-terraform-provider-b2 || touch b2/py-terraform-provider-b2 # required by go:embed in bindings.go 40 | @go vet ./... 41 | 42 | testacc: _pybindings 43 | @cp python-bindings/dist/py-terraform-provider-b2 b2/ 44 | @chmod +rx b2/py-terraform-provider-b2 45 | TF_ACC=1 go test ./${NAME} -v -count 1 -parallel 4 -timeout 120m $(TESTARGS) 46 | 47 | clean: _pybindings 48 | @rm -rf dist b2/py-terraform-provider-b2 ${BINARY} 49 | 50 | build: _pybindings 51 | @cp python-bindings/dist/py-terraform-provider-b2 b2/ 52 | @go build -tags netgo -o ${BINARY} 53 | 54 | install: build 55 | @mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 56 | @mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 57 | 58 | docs: build 59 | @tfplugindocs 60 | 61 | docs-lint: build 62 | @tfplugindocs validate 63 | @tfplugindocs 64 | @git diff --exit-code -- docs/ || \ 65 | (echo; echo "Unexpected difference in docs. Run 'make docs' command or revert any changes in the schema."; exit 1) 66 | 67 | all: deps lint build testacc 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Backblaze wants developers and organization to copy and re-use our 2 | code examples, so we make the samples available by several different 3 | licenses. One option is the MIT license (below). Other options are 4 | available here: 5 | 6 | https://www.backblaze.com/using_b2_code.html 7 | 8 | 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2020 Backblaze 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform Provider B2 2 | ===================== 3 | [![Continuous Integration](https://github.com/Backblaze/terraform-provider-b2/workflows/Continuous%20Integration/badge.svg)](https://github.com/Backblaze/terraform-provider-b2/actions?query=workflow%3A%22Continuous+Integration%22) 4 | 5 | Terraform provider for Backblaze B2. 6 | 7 | The provider is written in go, but it uses official [B2 python SDK](https://github.com/Backblaze/b2-sdk-python/) embedded into the binary. 8 | 9 | Requirements 10 | ------------ 11 | 12 | Runtime requirements: 13 | - [Terraform](https://www.terraform.io/downloads.html) >= 1.0.0 14 | 15 | Development requirements: 16 | - [Go](https://golang.org/doc/install) == 1.24 17 | - [Python](https://github.com/pyenv/pyenv) == 3.13 18 | 19 | Dependencies 20 | ------------ 21 | *Note:* You should run it inside python virtualenv as it installs the dependencies for the python bindings as well. 22 | 23 | ``` 24 | make deps 25 | ``` 26 | 27 | Building 28 | -------- 29 | 30 | ``` 31 | make build 32 | ``` 33 | 34 | Documentation 35 | ------------- 36 | 37 | The documentation is generated from the provider source code using 38 | [`tfplugindocs`](https://github.com/hashicorp/terraform-plugin-docs). You will need to regenerate the documentation if 39 | you add or change a data source, resource or argument. 40 | 41 | ``` 42 | make docs 43 | ``` 44 | 45 | Installing 46 | ---------- 47 | 48 | ``` 49 | make install 50 | ``` 51 | 52 | Testing 53 | ------- 54 | 55 | *Note:* Acceptance tests create real resources, and often cost money to run. 56 | 57 | ``` 58 | export B2_TEST_APPLICATION_KEY=your_app_key 59 | export B2_TEST_APPLICATION_KEY_ID=your_app_key_id 60 | make testacc 61 | ``` 62 | 63 | Debugging 64 | --------- 65 | 66 | Set TF_LOG_PROVIDER and TF_LOG_PATH env variables to see detailed information from the provider. 67 | Check https://www.terraform.io/docs/internals/debugging.html for details 68 | 69 | Release History 70 | ----------------- 71 | 72 | Please refer to the [changelog](CHANGELOG.md). 73 | -------------------------------------------------------------------------------- /README.release.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | - Install dependencies: 4 | - `make deps` 5 | - Update the release history in `CHANGELOG.md`: 6 | - Change "Unreleased" to the current release version and date. 7 | - Create empty "Unreleased" section. 8 | - Add proper link to the new release (at the bottom of the file). Use GitHub [compare feature](https://docs.github.com/en/free-pro-team@latest/github/committing-changes-to-your-project/comparing-commits#comparing-tags) between two tags. 9 | - Update "Unreleased" link (at the bottom of the file). 10 | - Run linters: 11 | - `make lint` 12 | - Run tests: 13 | - `export B2_TEST_APPLICATION_KEY=your_app_key` 14 | - `export B2_TEST_APPLICATION_KEY_ID=your_app_key_id` 15 | - `make testacc` 16 | - Update docs: 17 | - `make docs` 18 | - Commit and push to GitHub, then wait for CI workflow to complete successfully. 19 | - No need to make a branch. Push straight to `master`. 20 | - Tag in git and push tag to `origin`. (Version tags look like `v0.4.6`.) 21 | - `git tag vx.x.x` 22 | - `git push origin vx.x.x` 23 | - Wait for CD workflow to complete successfully. 24 | - Verify that the GitHub release is created 25 | - Verify that the release has been uploaded to the Terraform Registry 26 | -------------------------------------------------------------------------------- /b2/bindings.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/bindings.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "bufio" 15 | "embed" 16 | "io" 17 | "log" 18 | "os" 19 | "path/filepath" 20 | "runtime" 21 | "sync" 22 | ) 23 | 24 | var ( 25 | bindings *string 26 | //go:embed py-terraform-provider-b2 27 | content embed.FS 28 | lock = &sync.Mutex{} 29 | ) 30 | 31 | func GetBindings() (string, error) { 32 | if bindings == nil { 33 | lock.Lock() 34 | defer lock.Unlock() 35 | } 36 | if bindings != nil { 37 | return *bindings, nil 38 | } 39 | 40 | sourceFile, err := content.Open("py-terraform-provider-b2") 41 | if err != nil { 42 | return "", err 43 | } 44 | defer sourceFile.Close() 45 | 46 | var tmpPattern string 47 | if runtime.GOOS == "windows" { 48 | tmpPattern = "py-terraform-provider*.exe" 49 | } else { 50 | tmpPattern = "py-terraform-provider*" 51 | } 52 | 53 | destinationFile, err := os.CreateTemp("", tmpPattern) 54 | if err != nil { 55 | return "", err 56 | } 57 | defer destinationFile.Close() 58 | 59 | destinationPath := filepath.ToSlash(destinationFile.Name()) 60 | reader := bufio.NewReader(sourceFile) 61 | buf := make([]byte, 2048) 62 | 63 | for { 64 | _, err := reader.Read(buf) 65 | 66 | if err != nil { 67 | if err != io.EOF { 68 | return destinationPath, err 69 | } 70 | 71 | _, err = destinationFile.Seek(0, 0) 72 | if err != nil { 73 | return destinationPath, err 74 | } 75 | 76 | break 77 | } 78 | 79 | _, err = destinationFile.Write(buf) 80 | if err != nil { 81 | return destinationPath, err 82 | } 83 | } 84 | 85 | destinationFile.Close() 86 | 87 | err = os.Chmod(destinationPath, 0770) 88 | if err != nil { 89 | return destinationPath, err 90 | } 91 | 92 | bindings = &destinationPath 93 | log.Printf("Extracted pybindings: %s\n", *bindings) 94 | return *bindings, nil 95 | } 96 | -------------------------------------------------------------------------------- /b2/client.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/client.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "bytes" 15 | "context" 16 | "encoding/json" 17 | "fmt" 18 | "os" 19 | "os/exec" 20 | 21 | "github.com/hashicorp/terraform-plugin-log/tflog" 22 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 23 | ) 24 | 25 | const ( 26 | DATA_SOURCE_READ string = "data_source_read" 27 | 28 | RESOURCE_CREATE string = "resource_create" 29 | RESOURCE_READ string = "resource_read" 30 | RESOURCE_UPDATE string = "resource_update" 31 | RESOURCE_DELETE string = "resource_delete" 32 | ) 33 | 34 | type Client struct { 35 | Exec string 36 | UserAgentAppend string 37 | ApplicationKeyId string 38 | ApplicationKey string 39 | Endpoint string 40 | DataSources map[string][]string 41 | Resources map[string][]string 42 | SensitiveDataSources map[string]map[string]bool 43 | SensitiveResources map[string]map[string]bool 44 | } 45 | 46 | func (c Client) apply(ctx context.Context, name string, op string, input map[string]interface{}) (map[string]interface{}, error) { 47 | tflog.Info(ctx, "Executing pybindings", map[string]interface{}{ 48 | "name": name, 49 | "op": op, 50 | }) 51 | 52 | tflog.Debug(ctx, "Input for pybindings", map[string]interface{}{ 53 | "input": input, 54 | }) 55 | 56 | cmd := exec.Command(c.Exec, name, op) 57 | cmd.Env = os.Environ() 58 | cmd.Env = append(cmd.Env, fmt.Sprintf("B2_USER_AGENT_APPEND=%s", c.UserAgentAppend)) 59 | 60 | input["provider_application_key_id"] = c.ApplicationKeyId 61 | input["provider_application_key"] = c.ApplicationKey 62 | input["provider_endpoint"] = c.Endpoint 63 | 64 | inputJson, err := json.Marshal(input) 65 | if err != nil { 66 | // Should never happen 67 | return nil, err 68 | } 69 | cmd.Stdin = bytes.NewReader(inputJson) 70 | 71 | outputJson, err := cmd.Output() 72 | 73 | if err != nil { 74 | if exitErr, ok := err.(*exec.ExitError); ok { 75 | if exitErr.Stderr != nil && len(exitErr.Stderr) > 0 { 76 | err := fmt.Errorf("%s", string(exitErr.Stderr)) 77 | tflog.Error(ctx, "Error in pybindings", map[string]interface{}{ 78 | "stderr": err, 79 | }) 80 | return nil, err 81 | } 82 | return nil, fmt.Errorf("failed to execute") 83 | } else { 84 | tflog.Error(ctx, "Error", map[string]interface{}{ 85 | "err": err, 86 | }) 87 | return nil, err 88 | } 89 | } 90 | 91 | output := map[string]interface{}{} 92 | err = json.Unmarshal(outputJson, &output) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | resourceName := "b2_" + name 98 | var sensitiveSchemaMap map[string]bool 99 | if op == DATA_SOURCE_READ { 100 | sensitiveSchemaMap = c.SensitiveDataSources[resourceName] 101 | } else { 102 | sensitiveSchemaMap = c.SensitiveResources[resourceName] 103 | } 104 | 105 | // Do not log application_key 106 | safeOutput := map[string]interface{}{} 107 | for k, v := range output { 108 | if sensitiveSchemaMap[k] { 109 | safeOutput[k] = "***" 110 | } else { 111 | safeOutput[k] = v 112 | } 113 | } 114 | tflog.Debug(ctx, "Safe output from pybindings", map[string]interface{}{ 115 | "output": safeOutput, 116 | }) 117 | 118 | return output, nil 119 | } 120 | 121 | func (c Client) populate(ctx context.Context, name string, op string, output map[string]interface{}, d *schema.ResourceData) error { 122 | tflog.Info(ctx, "Populating data from pybindings", map[string]interface{}{ 123 | "name": name, 124 | "op": op, 125 | }) 126 | 127 | resourceName := "b2_" + name 128 | var schemaList []string 129 | if op == DATA_SOURCE_READ { 130 | schemaList = c.DataSources[resourceName] 131 | } else { 132 | schemaList = c.Resources[resourceName] 133 | } 134 | 135 | for _, k := range schemaList { 136 | v, ok := output[k] 137 | if !ok { 138 | return fmt.Errorf("error getting %s", k) 139 | } 140 | if err := d.Set(k, v); err != nil { 141 | return fmt.Errorf("error setting %s: %s", k, err) 142 | } 143 | } 144 | 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /b2/data_source_b2_account_info.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_account_info.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | ) 19 | 20 | func dataSourceB2AccountInfo() *schema.Resource { 21 | return &schema.Resource{ 22 | Description: "B2 account info data source.", 23 | 24 | ReadContext: dataSourceB2AccountInfoRead, 25 | 26 | Schema: map[string]*schema.Schema{ 27 | "account_id": { 28 | Description: "The identifier for the account.", 29 | Type: schema.TypeString, 30 | Computed: true, 31 | }, 32 | "account_auth_token": { 33 | Description: "An authorization token to use with all calls, other than b2_authorize_account, that need an Authorization header. This authorization token is valid for at most 24 hours.", 34 | Type: schema.TypeString, 35 | Sensitive: true, 36 | Computed: true, 37 | }, 38 | "api_url": { 39 | Description: "The base URL to use for all API calls except for uploading and downloading files.", 40 | Type: schema.TypeString, 41 | Computed: true, 42 | }, 43 | "allowed": { 44 | Description: "An object containing the capabilities of this auth token, and any restrictions on using it.", 45 | Type: schema.TypeList, 46 | Elem: getDataSourceAllowedElem(), 47 | Computed: true, 48 | }, 49 | "download_url": { 50 | Description: "The base URL to use for downloading files.", 51 | Type: schema.TypeString, 52 | Computed: true, 53 | }, 54 | "s3_api_url": { 55 | Description: "The base URL to use for S3-compatible API calls.", 56 | Type: schema.TypeString, 57 | Computed: true, 58 | }, 59 | }, 60 | } 61 | } 62 | 63 | func dataSourceB2AccountInfoRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 64 | client := meta.(*Client) 65 | const name = "account_info" 66 | const op = DATA_SOURCE_READ 67 | 68 | input := map[string]interface{}{} 69 | 70 | output, err := client.apply(ctx, name, op, input) 71 | if err != nil { 72 | return diag.FromErr(err) 73 | } 74 | 75 | d.SetId(output["account_id"].(string)) 76 | 77 | err = client.populate(ctx, name, op, output, d) 78 | if err != nil { 79 | return diag.FromErr(err) 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /b2/data_source_b2_account_info_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_account_info_test.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "regexp" 15 | "testing" 16 | 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 18 | ) 19 | 20 | func TestAccDataSourceB2AccountInfo_basic(t *testing.T) { 21 | dataSourceName := "data.b2_account_info.test" 22 | 23 | resource.Test(t, resource.TestCase{ 24 | PreCheck: func() { testAccPreCheck(t) }, 25 | ProviderFactories: providerFactories, 26 | Steps: []resource.TestStep{ 27 | { 28 | Config: testAccDataSourceB2AccountInfoConfig_basic(), 29 | Check: resource.ComposeTestCheckFunc( 30 | resource.TestMatchResourceAttr(dataSourceName, "account_id", regexp.MustCompile("^[a-z0-9]{12}$")), 31 | resource.TestMatchResourceAttr(dataSourceName, "account_auth_token", regexp.MustCompile("^[-=_a-zA-Z0-9]{77}$")), 32 | resource.TestMatchResourceAttr(dataSourceName, "api_url", regexp.MustCompile("https://api00[0-9].backblazeb2.com")), 33 | resource.TestCheckResourceAttr(dataSourceName, "allowed.#", "1"), 34 | resource.TestMatchResourceAttr(dataSourceName, "allowed.0.capabilities.#", regexp.MustCompile("[1-9][0-9]*")), 35 | resource.TestCheckResourceAttr(dataSourceName, "allowed.0.bucket_name", ""), 36 | resource.TestCheckResourceAttr(dataSourceName, "allowed.0.bucket_id", ""), 37 | resource.TestCheckResourceAttr(dataSourceName, "allowed.0.bucket_id", ""), 38 | resource.TestMatchResourceAttr(dataSourceName, "download_url", regexp.MustCompile("https://f00[0-9].backblazeb2.com")), 39 | resource.TestMatchResourceAttr(dataSourceName, "s3_api_url", regexp.MustCompile("https://s3.(us-west|eu-central)-00[0-9].backblazeb2.com")), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | func testAccDataSourceB2AccountInfoConfig_basic() string { 47 | return ` 48 | data "b2_account_info" "test" { 49 | } 50 | ` 51 | } 52 | -------------------------------------------------------------------------------- /b2/data_source_b2_application_key.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_application_key.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2ApplicationKey() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 application key data source.", 24 | 25 | ReadContext: dataSourceB2ApplicationKeyRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "key_name": { 29 | Description: "The name assigned when the key was created.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.NoZeroValues, 33 | }, 34 | "application_key_id": { 35 | Description: "The ID of the key.", 36 | Type: schema.TypeString, 37 | Computed: true, 38 | }, 39 | "bucket_id": { 40 | Description: "When present, restricts access to one bucket.", 41 | Type: schema.TypeString, 42 | Computed: true, 43 | }, 44 | "capabilities": { 45 | Description: "A set of strings, each one naming a capability the key has.", 46 | Type: schema.TypeSet, 47 | Elem: &schema.Schema{ 48 | Type: schema.TypeString, 49 | }, 50 | Computed: true, 51 | }, 52 | "name_prefix": { 53 | Description: "When present, restricts access to files whose names start with the prefix.", 54 | Type: schema.TypeString, 55 | Optional: true, 56 | Computed: true, 57 | }, 58 | "options": { 59 | Description: "A list of application key options.", 60 | Type: schema.TypeSet, 61 | Elem: &schema.Schema{ 62 | Type: schema.TypeString, 63 | }, 64 | Computed: true, 65 | }, 66 | }, 67 | } 68 | } 69 | 70 | func dataSourceB2ApplicationKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 71 | client := meta.(*Client) 72 | const name = "application_key" 73 | const op = DATA_SOURCE_READ 74 | 75 | input := map[string]interface{}{ 76 | "key_name": d.Get("key_name").(string), 77 | } 78 | 79 | output, err := client.apply(ctx, name, op, input) 80 | if err != nil { 81 | return diag.FromErr(err) 82 | } 83 | 84 | d.SetId(output["application_key_id"].(string)) 85 | 86 | err = client.populate(ctx, name, op, output, d) 87 | if err != nil { 88 | return diag.FromErr(err) 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /b2/data_source_b2_application_key_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_application_key_test.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "testing" 16 | 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 19 | ) 20 | 21 | func TestAccDataSourceB2ApplicationKey_basic(t *testing.T) { 22 | resourceName := "b2_application_key.test" 23 | dataSourceName := "data.b2_application_key.test" 24 | 25 | keyName := acctest.RandomWithPrefix("test-b2-tfp") 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccPreCheck(t) }, 29 | ProviderFactories: providerFactories, 30 | Steps: []resource.TestStep{ 31 | { 32 | Config: testAccDataSourceB2ApplicationKeyConfig_basic(keyName), 33 | Check: resource.ComposeTestCheckFunc( 34 | resource.TestCheckResourceAttrPair(dataSourceName, "application_key_id", resourceName, "application_key_id"), 35 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", resourceName, "bucket_id"), 36 | resource.TestCheckResourceAttr(dataSourceName, "capabilities.#", "1"), 37 | resource.TestCheckResourceAttr(dataSourceName, "capabilities.0", "readFiles"), 38 | resource.TestCheckResourceAttrPair(dataSourceName, "capabilities", resourceName, "capabilities"), 39 | resource.TestCheckResourceAttr(dataSourceName, "key_name", keyName), 40 | resource.TestCheckResourceAttrPair(dataSourceName, "key_name", resourceName, "key_name"), 41 | resource.TestCheckResourceAttrPair(dataSourceName, "name_prefix", resourceName, "name_prefix"), 42 | resource.TestCheckResourceAttrPair(dataSourceName, "options", resourceName, "options"), 43 | ), 44 | }, 45 | }, 46 | }) 47 | } 48 | 49 | func TestAccDataSourceB2ApplicationKey_all(t *testing.T) { 50 | resourceName := "b2_application_key.test" 51 | dataSourceName := "data.b2_application_key.test" 52 | 53 | keyName := acctest.RandomWithPrefix("test-b2-tfp") 54 | 55 | resource.Test(t, resource.TestCase{ 56 | PreCheck: func() { testAccPreCheck(t) }, 57 | ProviderFactories: providerFactories, 58 | Steps: []resource.TestStep{ 59 | { 60 | Config: testAccDataSourceB2ApplicationKeyConfig_all(keyName), 61 | Check: resource.ComposeTestCheckFunc( 62 | resource.TestCheckResourceAttrPair(dataSourceName, "application_key_id", resourceName, "application_key_id"), 63 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", resourceName, "bucket_id"), 64 | resource.TestCheckResourceAttr(dataSourceName, "capabilities.#", "1"), 65 | resource.TestCheckResourceAttr(dataSourceName, "capabilities.0", "writeFiles"), 66 | resource.TestCheckResourceAttrPair(dataSourceName, "capabilities", resourceName, "capabilities"), 67 | resource.TestCheckResourceAttr(dataSourceName, "key_name", keyName), 68 | resource.TestCheckResourceAttrPair(dataSourceName, "key_name", resourceName, "key_name"), 69 | resource.TestCheckResourceAttr(dataSourceName, "name_prefix", "prefix"), 70 | resource.TestCheckResourceAttrPair(dataSourceName, "name_prefix", resourceName, "name_prefix"), 71 | resource.TestCheckResourceAttrPair(dataSourceName, "options", resourceName, "options"), 72 | ), 73 | }, 74 | }, 75 | }) 76 | } 77 | 78 | func testAccDataSourceB2ApplicationKeyConfig_basic(keyName string) string { 79 | return fmt.Sprintf(` 80 | resource "b2_application_key" "test" { 81 | key_name = "%s" 82 | capabilities = ["readFiles"] 83 | } 84 | 85 | data "b2_application_key" "test" { 86 | key_name = b2_application_key.test.key_name 87 | 88 | depends_on = [ 89 | b2_application_key.test, 90 | ] 91 | } 92 | `, keyName) 93 | } 94 | 95 | func testAccDataSourceB2ApplicationKeyConfig_all(keyName string) string { 96 | return fmt.Sprintf(` 97 | resource "b2_bucket" "test" { 98 | bucket_name = "%s" 99 | bucket_type = "allPrivate" 100 | } 101 | 102 | resource "b2_application_key" "test" { 103 | key_name = "%s" 104 | capabilities = ["writeFiles"] 105 | bucket_id = b2_bucket.test.bucket_id 106 | name_prefix = "prefix" 107 | } 108 | 109 | data "b2_application_key" "test" { 110 | key_name = b2_application_key.test.key_name 111 | 112 | depends_on = [ 113 | b2_application_key.test, 114 | ] 115 | } 116 | `, keyName, keyName) 117 | } 118 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2Bucket() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 bucket data source.", 24 | 25 | ReadContext: dataSourceB2BucketRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "bucket_name": { 29 | Description: "The name of the bucket.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.NoZeroValues, 33 | }, 34 | "account_id": { 35 | Description: "Account ID that the bucket belongs to.", 36 | Type: schema.TypeString, 37 | Computed: true, 38 | }, 39 | "bucket_id": { 40 | Description: "The ID of the bucket.", 41 | Type: schema.TypeString, 42 | Computed: true, 43 | }, 44 | "bucket_info": { 45 | Description: "User-defined information to be stored with the bucket.", 46 | Type: schema.TypeMap, 47 | Elem: &schema.Schema{ 48 | Type: schema.TypeString, 49 | }, 50 | Computed: true, 51 | }, 52 | "bucket_type": { 53 | Description: "The bucket type. Either 'allPublic', meaning that files in this bucket can be downloaded by anybody, or 'allPrivate'.", 54 | Type: schema.TypeString, 55 | Computed: true, 56 | }, 57 | "cors_rules": { 58 | Description: "The initial list of CORS rules for this bucket.", 59 | Type: schema.TypeList, 60 | Elem: getCorsRulesElem(true), 61 | Computed: true, 62 | }, 63 | "file_lock_configuration": { 64 | Description: "The default File Lock retention settings for this bucket.", 65 | Type: schema.TypeList, 66 | Elem: getFileLockConfigurationElem(true), 67 | Computed: true, 68 | }, 69 | "default_server_side_encryption": { 70 | Description: "The default server-side encryption settings of this bucket.", 71 | Type: schema.TypeList, 72 | Elem: getServerSideEncryptionElem(true), 73 | Computed: true, 74 | }, 75 | "lifecycle_rules": { 76 | Description: "The initial list of lifecycle rules for this bucket.", 77 | Type: schema.TypeList, 78 | Elem: getLifecycleRulesElem(true), 79 | Computed: true, 80 | }, 81 | "options": { 82 | Description: "List of bucket options.", 83 | Type: schema.TypeSet, 84 | Elem: &schema.Schema{ 85 | Type: schema.TypeString, 86 | }, 87 | Computed: true, 88 | }, 89 | "revision": { 90 | Description: "Bucket revision.", 91 | Type: schema.TypeInt, 92 | Computed: true, 93 | }, 94 | }, 95 | } 96 | } 97 | 98 | func dataSourceB2BucketRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 99 | client := meta.(*Client) 100 | const name = "bucket" 101 | const op = DATA_SOURCE_READ 102 | 103 | input := map[string]interface{}{ 104 | "bucket_name": d.Get("bucket_name").(string), 105 | } 106 | 107 | output, err := client.apply(ctx, name, op, input) 108 | if err != nil { 109 | return diag.FromErr(err) 110 | } 111 | 112 | d.SetId(output["bucket_id"].(string)) 113 | 114 | err = client.populate(ctx, name, op, output, d) 115 | if err != nil { 116 | return diag.FromErr(err) 117 | } 118 | 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_file.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_file.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2BucketFile() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 bucket file data source.", 24 | 25 | ReadContext: dataSourceB2BucketFileRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "bucket_id": { 29 | Description: "The ID of the bucket.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.NoZeroValues, 33 | }, 34 | "file_name": { 35 | Description: "The file name.", 36 | Type: schema.TypeString, 37 | Required: true, 38 | ValidateFunc: validation.NoZeroValues, 39 | }, 40 | "show_versions": { 41 | Description: "Show all file versions.", 42 | Type: schema.TypeBool, 43 | Optional: true, 44 | }, 45 | "file_versions": { 46 | Description: "File versions.", 47 | Type: schema.TypeList, 48 | Elem: getDataSourceFileVersionsElem(), 49 | Computed: true, 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | func dataSourceB2BucketFileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 56 | client := meta.(*Client) 57 | const name = "bucket_file" 58 | const op = DATA_SOURCE_READ 59 | 60 | input := map[string]interface{}{ 61 | "bucket_id": d.Get("bucket_id").(string), 62 | "file_name": d.Get("file_name").(string), 63 | "show_versions": d.Get("show_versions").(bool), 64 | } 65 | 66 | output, err := client.apply(ctx, name, op, input) 67 | if err != nil { 68 | return diag.FromErr(err) 69 | } 70 | 71 | d.SetId(output["_sha1"].(string)) 72 | 73 | err = client.populate(ctx, name, op, output, d) 74 | if err != nil { 75 | return diag.FromErr(err) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_file_signed_url.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_file_signed_url.go 4 | // 5 | // Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2BucketFileSignedUrl() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 signed URL for a bucket file data source.", 24 | 25 | ReadContext: dataSourceB2BucketFileSignedUrlRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "bucket_id": { 29 | Description: "The ID of the bucket.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.StringIsNotEmpty, 33 | }, 34 | "file_name": { 35 | Description: "The file name.", 36 | Type: schema.TypeString, 37 | Required: true, 38 | ValidateFunc: validation.StringIsNotEmpty, 39 | }, 40 | "duration": { 41 | Description: "The duration for which the presigned URL is valid", 42 | Type: schema.TypeInt, 43 | Optional: true, 44 | }, 45 | "signed_url": { 46 | Description: "The signed URL for the given file", 47 | Type: schema.TypeString, 48 | Computed: true, 49 | }, 50 | }, 51 | } 52 | } 53 | 54 | func dataSourceB2BucketFileSignedUrlRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 55 | client := meta.(*Client) 56 | const name = "bucket_file_signed_url" 57 | const op = DATA_SOURCE_READ 58 | 59 | input := map[string]interface{}{ 60 | "bucket_id": d.Get("bucket_id").(string), 61 | "file_name": d.Get("file_name").(string), 62 | "duration": d.Get("duration").(int), 63 | } 64 | 65 | output, err := client.apply(ctx, name, op, input) 66 | if err != nil { 67 | return diag.FromErr(err) 68 | } 69 | 70 | d.SetId(output["signed_url"].(string)) 71 | 72 | err = client.populate(ctx, name, op, output, d) 73 | if err != nil { 74 | return diag.FromErr(err) 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_file_signed_url_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_file_signed_url_test.go 4 | // 5 | // Copyright 2022 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "testing" 17 | 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 20 | ) 21 | 22 | func TestAccDataSourceB2BucketFileSignedUrl_singleFile(t *testing.T) { 23 | parentResourceName := "b2_bucket.test" 24 | resourceName := "b2_bucket_file_version.test" 25 | dataSourceName := "data.b2_bucket_file_signed_url.test" 26 | 27 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 28 | tempFile := createTempFileString(t, "hello") 29 | defer os.Remove(tempFile) 30 | 31 | resource.Test(t, resource.TestCase{ 32 | PreCheck: func() { testAccPreCheck(t) }, 33 | ProviderFactories: providerFactories, 34 | Steps: []resource.TestStep{ 35 | { 36 | Config: testAccDataSourceB2BucketFileSignedUrlConfig_singleFile(bucketName, tempFile), 37 | Check: resource.ComposeTestCheckFunc( 38 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 39 | resource.TestCheckResourceAttrPair(dataSourceName, "file_name", resourceName, "file_name"), 40 | resource.TestCheckResourceAttrSet(dataSourceName, "signed_url"), 41 | resource.TestCheckResourceAttrPair(dataSourceName, "id", dataSourceName, "signed_url"), 42 | ), 43 | }, 44 | }, 45 | }) 46 | } 47 | 48 | func testAccDataSourceB2BucketFileSignedUrlConfig_singleFile(bucketName string, tempFile string) string { 49 | return fmt.Sprintf(` 50 | resource "b2_bucket" "test" { 51 | bucket_name = "%s" 52 | bucket_type = "allPrivate" 53 | } 54 | 55 | resource "b2_bucket_file_version" "test" { 56 | bucket_id = b2_bucket.test.id 57 | file_name = "temp.txt" 58 | source = "%s" 59 | } 60 | 61 | data "b2_bucket_file_signed_url" "test" { 62 | bucket_id = b2_bucket_file_version.test.bucket_id 63 | file_name = b2_bucket_file_version.test.file_name 64 | duration = 3600 65 | } 66 | `, bucketName, tempFile) 67 | } 68 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_file_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_file_test.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "testing" 17 | 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 20 | ) 21 | 22 | func TestAccDataSourceB2BucketFile_noFiles(t *testing.T) { 23 | parentResourceName := "b2_bucket.test" 24 | dataSourceName := "data.b2_bucket_file.test" 25 | 26 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 27 | 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccPreCheck(t) }, 30 | ProviderFactories: providerFactories, 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: testAccDataSourceB2BucketFileConfig_noFiles(bucketName), 34 | Check: resource.ComposeTestCheckFunc( 35 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 36 | resource.TestCheckResourceAttr(dataSourceName, "file_name", "non_existing_file.txt"), 37 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "0"), 38 | resource.TestCheckResourceAttr(dataSourceName, "show_versions", "false"), 39 | ), 40 | }, 41 | }, 42 | }) 43 | } 44 | 45 | func TestAccDataSourceB2BucketFile_singleFile(t *testing.T) { 46 | parentResourceName := "b2_bucket.test" 47 | resourceName := "b2_bucket_file_version.test" 48 | dataSourceName := "data.b2_bucket_file.test" 49 | 50 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 51 | tempFile := createTempFileString(t, "hello") 52 | defer os.Remove(tempFile) 53 | 54 | resource.Test(t, resource.TestCase{ 55 | PreCheck: func() { testAccPreCheck(t) }, 56 | ProviderFactories: providerFactories, 57 | Steps: []resource.TestStep{ 58 | { 59 | Config: testAccDataSourceB2BucketFileConfig_singleFile(bucketName, tempFile), 60 | Check: resource.ComposeTestCheckFunc( 61 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 62 | resource.TestCheckResourceAttrPair(dataSourceName, "file_name", resourceName, "file_name"), 63 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "1"), 64 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 65 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resourceName, "content_md5"), 66 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resourceName, "content_sha1"), 67 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resourceName, "content_type"), 68 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 69 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resourceName, "file_id"), 70 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resourceName, "file_info"), 71 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resourceName, "file_name"), 72 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resourceName, "server_side_encryption"), 73 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resourceName, "size"), 74 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resourceName, "upload_timestamp"), 75 | resource.TestCheckResourceAttr(dataSourceName, "show_versions", "false"), 76 | ), 77 | }, 78 | }, 79 | }) 80 | } 81 | 82 | func TestAccDataSourceB2BucketFile_multipleFilesWithoutVersions(t *testing.T) { 83 | parentResourceName := "b2_bucket.test" 84 | resourceName := "b2_bucket_file_version.test2" 85 | dataSourceName := "data.b2_bucket_file.test" 86 | 87 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 88 | tempFile := createTempFileString(t, "hello") 89 | 90 | resource.Test(t, resource.TestCase{ 91 | PreCheck: func() { testAccPreCheck(t) }, 92 | ProviderFactories: providerFactories, 93 | Steps: []resource.TestStep{ 94 | { 95 | Config: testAccDataSourceB2BucketFileConfig_multipleFiles(bucketName, tempFile, "false"), 96 | Check: resource.ComposeTestCheckFunc( 97 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 98 | resource.TestCheckResourceAttrPair(dataSourceName, "file_name", resourceName, "file_name"), 99 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "1"), 100 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 101 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resourceName, "content_md5"), 102 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resourceName, "content_sha1"), 103 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resourceName, "content_type"), 104 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 105 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resourceName, "file_id"), 106 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resourceName, "file_info"), 107 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resourceName, "file_name"), 108 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resourceName, "server_side_encryption"), 109 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resourceName, "size"), 110 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resourceName, "upload_timestamp"), 111 | resource.TestCheckResourceAttr(dataSourceName, "show_versions", "false"), 112 | ), 113 | }, 114 | }, 115 | }) 116 | } 117 | 118 | func TestAccDataSourceB2BucketFile_multipleFilesWithVersions(t *testing.T) { 119 | parentResourceName := "b2_bucket.test" 120 | resource1Name := "b2_bucket_file_version.test1" 121 | resource2Name := "b2_bucket_file_version.test2" 122 | dataSourceName := "data.b2_bucket_file.test" 123 | 124 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 125 | tempFile := createTempFileString(t, "hello") 126 | 127 | resource.Test(t, resource.TestCase{ 128 | PreCheck: func() { testAccPreCheck(t) }, 129 | ProviderFactories: providerFactories, 130 | Steps: []resource.TestStep{ 131 | { 132 | Config: testAccDataSourceB2BucketFileConfig_multipleFiles(bucketName, tempFile, "true"), 133 | Check: resource.ComposeTestCheckFunc( 134 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 135 | resource.TestCheckResourceAttrPair(dataSourceName, "file_name", resource2Name, "file_name"), 136 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "2"), 137 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 138 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resource2Name, "content_md5"), 139 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resource2Name, "content_sha1"), 140 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resource2Name, "content_type"), 141 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 142 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resource2Name, "file_id"), 143 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resource2Name, "file_info"), 144 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resource2Name, "file_name"), 145 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource2Name, "server_side_encryption"), 146 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resource2Name, "size"), 147 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resource2Name, "upload_timestamp"), 148 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource1Name, "action"), 149 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_md5", resource1Name, "content_md5"), 150 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_sha1", resource1Name, "content_sha1"), 151 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_type", resource1Name, "content_type"), 152 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource1Name, "action"), 153 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_id", resource1Name, "file_id"), 154 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_info", resource1Name, "file_info"), 155 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_name", resource1Name, "file_name"), 156 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource1Name, "server_side_encryption"), 157 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.size", resource1Name, "size"), 158 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.upload_timestamp", resource1Name, "upload_timestamp"), 159 | resource.TestCheckResourceAttr(dataSourceName, "show_versions", "true"), 160 | ), 161 | }, 162 | }, 163 | }) 164 | } 165 | 166 | func testAccDataSourceB2BucketFileConfig_noFiles(bucketName string) string { 167 | return fmt.Sprintf(` 168 | resource "b2_bucket" "test" { 169 | bucket_name = "%s" 170 | bucket_type = "allPublic" 171 | } 172 | 173 | data "b2_bucket_file" "test" { 174 | bucket_id = b2_bucket.test.bucket_id 175 | file_name = "non_existing_file.txt" 176 | } 177 | `, bucketName) 178 | } 179 | 180 | func testAccDataSourceB2BucketFileConfig_singleFile(bucketName string, tempFile string) string { 181 | return fmt.Sprintf(` 182 | resource "b2_bucket" "test" { 183 | bucket_name = "%s" 184 | bucket_type = "allPublic" 185 | } 186 | 187 | resource "b2_bucket_file_version" "test" { 188 | bucket_id = b2_bucket.test.id 189 | file_name = "temp.txt" 190 | source = "%s" 191 | } 192 | 193 | data "b2_bucket_file" "test" { 194 | bucket_id = b2_bucket_file_version.test.bucket_id 195 | file_name = b2_bucket_file_version.test.file_name 196 | } 197 | `, bucketName, tempFile) 198 | } 199 | 200 | func testAccDataSourceB2BucketFileConfig_multipleFiles(bucketName string, tempFile string, showVersions string) string { 201 | return fmt.Sprintf(` 202 | resource "b2_bucket" "test" { 203 | bucket_name = "%s" 204 | bucket_type = "allPublic" 205 | } 206 | 207 | resource "b2_bucket_file_version" "test1" { 208 | bucket_id = b2_bucket.test.id 209 | file_name = "temp1.txt" 210 | source = "%s" 211 | } 212 | 213 | resource "b2_bucket_file_version" "test2" { 214 | bucket_id = b2_bucket_file_version.test1.bucket_id 215 | file_name = b2_bucket_file_version.test1.file_name 216 | source = b2_bucket_file_version.test1.source 217 | file_info = { 218 | description = "second version" 219 | } 220 | 221 | depends_on = [ 222 | b2_bucket_file_version.test1, 223 | ] 224 | } 225 | 226 | resource "b2_bucket_file_version" "test3" { 227 | bucket_id = b2_bucket_file_version.test2.bucket_id 228 | file_name = "temp2.txt" 229 | source = b2_bucket_file_version.test2.source 230 | server_side_encryption { 231 | mode = "SSE-B2" 232 | algorithm = "AES256" 233 | } 234 | 235 | depends_on = [ 236 | b2_bucket_file_version.test2, 237 | ] 238 | } 239 | 240 | data "b2_bucket_file" "test" { 241 | bucket_id = b2_bucket_file_version.test3.bucket_id 242 | file_name = b2_bucket_file_version.test1.file_name 243 | show_versions = %s 244 | } 245 | `, bucketName, tempFile, showVersions) 246 | } 247 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_files.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_files.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2BucketFiles() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 bucket files data source.", 24 | 25 | ReadContext: dataSourceB2BucketFilesRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "bucket_id": { 29 | Description: "The ID of the bucket.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.NoZeroValues, 33 | }, 34 | "folder_name": { 35 | Description: "The folder name (B2 file name prefix).", 36 | Type: schema.TypeString, 37 | Optional: true, 38 | }, 39 | "show_versions": { 40 | Description: "Show all file versions.", 41 | Type: schema.TypeBool, 42 | Optional: true, 43 | }, 44 | "recursive": { 45 | Description: "Recursive mode.", 46 | Type: schema.TypeBool, 47 | Optional: true, 48 | }, 49 | "file_versions": { 50 | Description: "File versions in the folder.", 51 | Type: schema.TypeList, 52 | Elem: getDataSourceFileVersionsElem(), 53 | Computed: true, 54 | }, 55 | }, 56 | } 57 | } 58 | 59 | func dataSourceB2BucketFilesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 60 | client := meta.(*Client) 61 | const name = "bucket_files" 62 | const op = DATA_SOURCE_READ 63 | 64 | input := map[string]interface{}{ 65 | "bucket_id": d.Get("bucket_id").(string), 66 | "folder_name": d.Get("folder_name").(string), 67 | "show_versions": d.Get("show_versions").(bool), 68 | "recursive": d.Get("recursive").(bool), 69 | } 70 | 71 | output, err := client.apply(ctx, name, op, input) 72 | if err != nil { 73 | return diag.FromErr(err) 74 | } 75 | 76 | d.SetId(output["_sha1"].(string)) 77 | 78 | err = client.populate(ctx, name, op, output, d) 79 | if err != nil { 80 | return diag.FromErr(err) 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_files_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_files_test.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "testing" 17 | 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 20 | ) 21 | 22 | func TestAccDataSourceB2BucketFiles_noFiles(t *testing.T) { 23 | parentResourceName := "b2_bucket.test" 24 | dataSourceName := "data.b2_bucket_files.test" 25 | 26 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 27 | tempFile := createTempFileString(t, "hello") 28 | defer os.Remove(tempFile) 29 | 30 | resource.Test(t, resource.TestCase{ 31 | PreCheck: func() { testAccPreCheck(t) }, 32 | ProviderFactories: providerFactories, 33 | Steps: []resource.TestStep{ 34 | { 35 | Config: testAccDataSourceB2BucketFilesConfig_noFiles(bucketName, tempFile), 36 | Check: resource.ComposeTestCheckFunc( 37 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 38 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "0"), 39 | resource.TestCheckResourceAttr(dataSourceName, "folder_name", "non_existing_folder"), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | func TestAccDataSourceB2BucketFiles_singleFile(t *testing.T) { 47 | parentResourceName := "b2_bucket.test" 48 | resourceName := "b2_bucket_file_version.test" 49 | dataSourceName := "data.b2_bucket_files.test" 50 | 51 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 52 | tempFile := createTempFileString(t, "hello") 53 | defer os.Remove(tempFile) 54 | 55 | resource.Test(t, resource.TestCase{ 56 | PreCheck: func() { testAccPreCheck(t) }, 57 | ProviderFactories: providerFactories, 58 | Steps: []resource.TestStep{ 59 | { 60 | Config: testAccDataSourceB2BucketFilesConfig_singleFile(bucketName, tempFile), 61 | Check: resource.ComposeTestCheckFunc( 62 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 63 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "1"), 64 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 65 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resourceName, "content_md5"), 66 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resourceName, "content_sha1"), 67 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resourceName, "content_type"), 68 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resourceName, "action"), 69 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resourceName, "file_id"), 70 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resourceName, "file_info"), 71 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resourceName, "file_name"), 72 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resourceName, "server_side_encryption"), 73 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resourceName, "size"), 74 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resourceName, "upload_timestamp"), 75 | resource.TestCheckResourceAttr(dataSourceName, "folder_name", ""), 76 | ), 77 | }, 78 | }, 79 | }) 80 | } 81 | 82 | func TestAccDataSourceB2BucketFiles_multipleFilesWithoutVersions(t *testing.T) { 83 | parentResourceName := "b2_bucket.test" 84 | resource2Name := "b2_bucket_file_version.test2" 85 | resource3Name := "b2_bucket_file_version.test3" 86 | dataSourceName := "data.b2_bucket_files.test" 87 | 88 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 89 | tempFile := createTempFileString(t, "hello") 90 | 91 | resource.Test(t, resource.TestCase{ 92 | PreCheck: func() { testAccPreCheck(t) }, 93 | ProviderFactories: providerFactories, 94 | Steps: []resource.TestStep{ 95 | { 96 | Config: testAccDataSourceB2BucketFilesConfig_multipleFiles(bucketName, tempFile, "false"), 97 | Check: resource.ComposeTestCheckFunc( 98 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 99 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "2"), 100 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 101 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resource2Name, "content_md5"), 102 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resource2Name, "content_sha1"), 103 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resource2Name, "content_type"), 104 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 105 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resource2Name, "file_id"), 106 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resource2Name, "file_info"), 107 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resource2Name, "file_name"), 108 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource2Name, "server_side_encryption"), 109 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resource2Name, "size"), 110 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resource2Name, "upload_timestamp"), 111 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource3Name, "action"), 112 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_md5", resource3Name, "content_md5"), 113 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_sha1", resource3Name, "content_sha1"), 114 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_type", resource3Name, "content_type"), 115 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource3Name, "action"), 116 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_id", resource3Name, "file_id"), 117 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_info", resource3Name, "file_info"), 118 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_name", resource3Name, "file_name"), 119 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource3Name, "server_side_encryption"), 120 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.size", resource3Name, "size"), 121 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.upload_timestamp", resource3Name, "upload_timestamp"), 122 | resource.TestCheckResourceAttr(dataSourceName, "folder_name", ""), 123 | ), 124 | }, 125 | }, 126 | }) 127 | } 128 | 129 | func TestAccDataSourceB2BucketFiles_multipleFilesWithVersions(t *testing.T) { 130 | parentResourceName := "b2_bucket.test" 131 | resource1Name := "b2_bucket_file_version.test1" 132 | resource2Name := "b2_bucket_file_version.test2" 133 | resource3Name := "b2_bucket_file_version.test3" 134 | dataSourceName := "data.b2_bucket_files.test" 135 | 136 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 137 | tempFile := createTempFileString(t, "hello") 138 | 139 | resource.Test(t, resource.TestCase{ 140 | PreCheck: func() { testAccPreCheck(t) }, 141 | ProviderFactories: providerFactories, 142 | Steps: []resource.TestStep{ 143 | { 144 | Config: testAccDataSourceB2BucketFilesConfig_multipleFiles(bucketName, tempFile, "true"), 145 | Check: resource.ComposeTestCheckFunc( 146 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 147 | resource.TestCheckResourceAttr(dataSourceName, "file_versions.#", "3"), 148 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 149 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_md5", resource2Name, "content_md5"), 150 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_sha1", resource2Name, "content_sha1"), 151 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.content_type", resource2Name, "content_type"), 152 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.action", resource2Name, "action"), 153 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_id", resource2Name, "file_id"), 154 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_info", resource2Name, "file_info"), 155 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.file_name", resource2Name, "file_name"), 156 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource2Name, "server_side_encryption"), 157 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.size", resource2Name, "size"), 158 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.upload_timestamp", resource2Name, "upload_timestamp"), 159 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource1Name, "action"), 160 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_md5", resource1Name, "content_md5"), 161 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_sha1", resource1Name, "content_sha1"), 162 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.content_type", resource1Name, "content_type"), 163 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.action", resource1Name, "action"), 164 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_id", resource1Name, "file_id"), 165 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_info", resource1Name, "file_info"), 166 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.file_name", resource1Name, "file_name"), 167 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource1Name, "server_side_encryption"), 168 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.size", resource1Name, "size"), 169 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.1.upload_timestamp", resource1Name, "upload_timestamp"), 170 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.action", resource3Name, "action"), 171 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.content_md5", resource3Name, "content_md5"), 172 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.content_sha1", resource3Name, "content_sha1"), 173 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.content_type", resource3Name, "content_type"), 174 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.action", resource3Name, "action"), 175 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.file_id", resource3Name, "file_id"), 176 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.file_info", resource3Name, "file_info"), 177 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.file_name", resource3Name, "file_name"), 178 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.size", resource3Name, "size"), 179 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.0.server_side_encryption", resource3Name, "server_side_encryption"), 180 | resource.TestCheckResourceAttrPair(dataSourceName, "file_versions.2.upload_timestamp", resource3Name, "upload_timestamp"), 181 | resource.TestCheckResourceAttr(dataSourceName, "folder_name", ""), 182 | ), 183 | }, 184 | }, 185 | }) 186 | } 187 | 188 | func testAccDataSourceB2BucketFilesConfig_noFiles(bucketName string, tempFile string) string { 189 | return fmt.Sprintf(` 190 | resource "b2_bucket" "test" { 191 | bucket_name = "%s" 192 | bucket_type = "allPublic" 193 | } 194 | 195 | resource "b2_bucket_file_version" "test" { 196 | bucket_id = b2_bucket.test.id 197 | file_name = "existing_folder/temp.txt" 198 | source = "%s" 199 | } 200 | 201 | data "b2_bucket_files" "test" { 202 | bucket_id = b2_bucket_file_version.test.bucket_id 203 | folder_name = "non_existing_folder" 204 | } 205 | `, bucketName, tempFile) 206 | } 207 | 208 | func testAccDataSourceB2BucketFilesConfig_singleFile(bucketName string, tempFile string) string { 209 | return fmt.Sprintf(` 210 | resource "b2_bucket" "test" { 211 | bucket_name = "%s" 212 | bucket_type = "allPublic" 213 | } 214 | 215 | resource "b2_bucket_file_version" "test" { 216 | bucket_id = b2_bucket.test.id 217 | file_name = "temp.txt" 218 | source = "%s" 219 | } 220 | 221 | data "b2_bucket_files" "test" { 222 | bucket_id = b2_bucket_file_version.test.bucket_id 223 | } 224 | `, bucketName, tempFile) 225 | } 226 | 227 | func testAccDataSourceB2BucketFilesConfig_multipleFiles(bucketName string, tempFile string, showVersions string) string { 228 | return fmt.Sprintf(` 229 | resource "b2_bucket" "test" { 230 | bucket_name = "%s" 231 | bucket_type = "allPublic" 232 | } 233 | 234 | resource "b2_bucket_file_version" "test1" { 235 | bucket_id = b2_bucket.test.id 236 | file_name = "temp1.txt" 237 | source = "%s" 238 | } 239 | 240 | resource "b2_bucket_file_version" "test2" { 241 | bucket_id = b2_bucket_file_version.test1.bucket_id 242 | file_name = b2_bucket_file_version.test1.file_name 243 | source = b2_bucket_file_version.test1.source 244 | file_info = { 245 | description = "second version" 246 | } 247 | 248 | depends_on = [ 249 | b2_bucket_file_version.test1, 250 | ] 251 | } 252 | 253 | resource "b2_bucket_file_version" "test3" { 254 | bucket_id = b2_bucket_file_version.test2.bucket_id 255 | file_name = "temp2.txt" 256 | source = b2_bucket_file_version.test2.source 257 | server_side_encryption { 258 | mode = "SSE-B2" 259 | algorithm = "AES256" 260 | } 261 | 262 | depends_on = [ 263 | b2_bucket_file_version.test2, 264 | ] 265 | } 266 | 267 | data "b2_bucket_files" "test" { 268 | bucket_id = b2_bucket_file_version.test3.bucket_id 269 | show_versions = %s 270 | } 271 | `, bucketName, tempFile, showVersions) 272 | } 273 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_notification_rules.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_notification_rules.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func dataSourceB2BucketNotificationRules() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 bucket notification rules data source.", 24 | 25 | ReadContext: dataSourceB2BucketNotificationRulesRead, 26 | 27 | Schema: map[string]*schema.Schema{ 28 | "bucket_id": { 29 | Description: "The ID of the bucket.", 30 | Type: schema.TypeString, 31 | Required: true, 32 | ValidateFunc: validation.NoZeroValues, 33 | }, 34 | "notification_rules": { 35 | Description: "An array of Event Notification Rules.", 36 | Type: schema.TypeList, 37 | Elem: getNotificationRulesElem(true), 38 | Computed: true, 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | func dataSourceB2BucketNotificationRulesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 45 | client := meta.(*Client) 46 | const name = "bucket_notification_rules" 47 | const op = DATA_SOURCE_READ 48 | 49 | input := map[string]interface{}{ 50 | "bucket_id": d.Get("bucket_id").(string), 51 | } 52 | 53 | output, err := client.apply(ctx, name, op, input) 54 | if err != nil { 55 | return diag.FromErr(err) 56 | } 57 | 58 | d.SetId(output["bucket_id"].(string)) 59 | 60 | err = client.populate(ctx, name, op, output, d) 61 | if err != nil { 62 | return diag.FromErr(err) 63 | } 64 | 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_notification_rules_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_notification_rules_test.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "testing" 16 | 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 19 | ) 20 | 21 | func TestAccDataSourceB2BucketNotificationRules_basic(t *testing.T) { 22 | parentResourceName := "b2_bucket.test" 23 | resourceName := "b2_bucket_notification_rules.test" 24 | dataSourceName := "data.b2_bucket_notification_rules.test" 25 | 26 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 27 | ruleName := acctest.RandomWithPrefix("test-b2-tfp") 28 | 29 | resource.Test(t, resource.TestCase{ 30 | PreCheck: func() { testAccPreCheck(t) }, 31 | ProviderFactories: providerFactories, 32 | Steps: []resource.TestStep{ 33 | { 34 | Config: testAccDataSourceB2BucketNotificationRulesConfig_basic(bucketName, ruleName), 35 | Check: resource.ComposeTestCheckFunc( 36 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", resourceName, "bucket_id"), 37 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", parentResourceName, "bucket_id"), 38 | resource.TestCheckResourceAttrPair(dataSourceName, "notification_rules", resourceName, "notification_rules"), 39 | ), 40 | }, 41 | }, 42 | }) 43 | } 44 | 45 | func testAccDataSourceB2BucketNotificationRulesConfig_basic(bucketName string, ruleName string) string { 46 | return fmt.Sprintf(` 47 | resource "b2_bucket" "test" { 48 | bucket_name = "%s" 49 | bucket_type = "allPublic" 50 | } 51 | 52 | resource "b2_bucket_notification_rules" "test" { 53 | bucket_id = b2_bucket.test.id 54 | notification_rules { 55 | name = "%s" 56 | event_types = ["b2:ObjectCreated:*"] 57 | target_configuration { 58 | target_type = "webhook" 59 | url = "https://example.com/webhook" 60 | } 61 | } 62 | } 63 | 64 | data "b2_bucket_notification_rules" "test" { 65 | bucket_id = b2_bucket.test.bucket_id 66 | depends_on = [ 67 | b2_bucket_notification_rules.test, 68 | ] 69 | } 70 | `, bucketName, ruleName) 71 | } 72 | -------------------------------------------------------------------------------- /b2/data_source_b2_bucket_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/data_source_b2_bucket_test.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "testing" 16 | 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 19 | ) 20 | 21 | func TestAccDataSourceB2Bucket_basic(t *testing.T) { 22 | resourceName := "b2_bucket.test" 23 | dataSourceName := "data.b2_bucket.test" 24 | 25 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccPreCheck(t) }, 29 | ProviderFactories: providerFactories, 30 | Steps: []resource.TestStep{ 31 | { 32 | Config: testAccDataSourceB2BucketConfig_basic(bucketName), 33 | Check: resource.ComposeTestCheckFunc( 34 | resource.TestCheckResourceAttrPair(dataSourceName, "account_id", resourceName, "account_id"), 35 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", resourceName, "bucket_id"), 36 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_info", resourceName, "bucket_info"), 37 | resource.TestCheckResourceAttr(dataSourceName, "bucket_name", bucketName), 38 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_name", resourceName, "bucket_name"), 39 | resource.TestCheckResourceAttr(dataSourceName, "bucket_type", "allPublic"), 40 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_type", resourceName, "bucket_type"), 41 | resource.TestCheckResourceAttrPair(dataSourceName, "cors_rules", resourceName, "cors_rules"), 42 | resource.TestCheckResourceAttrPair(dataSourceName, "default_server_side_encryption", resourceName, "default_server_side_encryption"), 43 | resource.TestCheckResourceAttrPair(dataSourceName, "lifecycle_rules", resourceName, "lifecycle_rules"), 44 | resource.TestCheckResourceAttrPair(dataSourceName, "options", resourceName, "options"), 45 | resource.TestCheckResourceAttrPair(dataSourceName, "revision", resourceName, "revision"), 46 | ), 47 | }, 48 | }, 49 | }) 50 | } 51 | 52 | func TestAccDataSourceB2Bucket_all(t *testing.T) { 53 | resourceName := "b2_bucket.test" 54 | dataSourceName := "data.b2_bucket.test" 55 | 56 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 57 | 58 | resource.Test(t, resource.TestCase{ 59 | PreCheck: func() { testAccPreCheck(t) }, 60 | ProviderFactories: providerFactories, 61 | Steps: []resource.TestStep{ 62 | { 63 | Config: testAccDataSourceB2BucketConfig_all(bucketName), 64 | Check: resource.ComposeTestCheckFunc( 65 | resource.TestCheckResourceAttrPair(dataSourceName, "account_id", resourceName, "account_id"), 66 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_id", resourceName, "bucket_id"), 67 | resource.TestCheckResourceAttr(dataSourceName, "bucket_info.%", "1"), 68 | resource.TestCheckResourceAttr(dataSourceName, "bucket_info.description", "the bucket"), 69 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_info", resourceName, "bucket_info"), 70 | resource.TestCheckResourceAttr(dataSourceName, "bucket_name", bucketName), 71 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_name", resourceName, "bucket_name"), 72 | resource.TestCheckResourceAttr(dataSourceName, "bucket_type", "allPrivate"), 73 | resource.TestCheckResourceAttrPair(dataSourceName, "bucket_type", resourceName, "bucket_type"), 74 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.#", "1"), 75 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.cors_rule_name", "downloadFromAnyOrigin"), 76 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_origins.#", "1"), 77 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_origins.0", "https"), 78 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_operations.#", "2"), 79 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_operations.0", "b2_download_file_by_id"), 80 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_operations.1", "b2_download_file_by_name"), 81 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.expose_headers.#", "1"), 82 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.expose_headers.0", "x-bz-content-sha1"), 83 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_headers.#", "1"), 84 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.allowed_headers.0", "range"), 85 | resource.TestCheckResourceAttr(dataSourceName, "cors_rules.0.max_age_seconds", "3600"), 86 | resource.TestCheckResourceAttrPair(dataSourceName, "cors_rules", resourceName, "cors_rules"), 87 | resource.TestCheckResourceAttr(dataSourceName, "default_server_side_encryption.#", "1"), 88 | resource.TestCheckResourceAttr(dataSourceName, "default_server_side_encryption.0.mode", "SSE-B2"), 89 | resource.TestCheckResourceAttr(dataSourceName, "default_server_side_encryption.0.algorithm", "AES256"), 90 | resource.TestCheckResourceAttrPair(dataSourceName, "default_server_side_encryption", resourceName, "default_server_side_encryption"), 91 | resource.TestCheckResourceAttr(dataSourceName, "lifecycle_rules.#", "1"), 92 | resource.TestCheckResourceAttr(dataSourceName, "lifecycle_rules.0.file_name_prefix", ""), 93 | resource.TestCheckResourceAttr(dataSourceName, "lifecycle_rules.0.days_from_hiding_to_deleting", "2"), 94 | resource.TestCheckResourceAttr(dataSourceName, "lifecycle_rules.0.days_from_uploading_to_hiding", "1"), 95 | resource.TestCheckResourceAttrPair(dataSourceName, "lifecycle_rules", resourceName, "lifecycle_rules"), 96 | resource.TestCheckResourceAttrPair(dataSourceName, "options", resourceName, "options"), 97 | resource.TestCheckResourceAttrPair(dataSourceName, "revision", resourceName, "revision"), 98 | ), 99 | }, 100 | }, 101 | }) 102 | } 103 | 104 | func testAccDataSourceB2BucketConfig_basic(bucketName string) string { 105 | return fmt.Sprintf(` 106 | resource "b2_bucket" "test" { 107 | bucket_name = "%s" 108 | bucket_type = "allPublic" 109 | } 110 | 111 | data "b2_bucket" "test" { 112 | bucket_name = b2_bucket.test.bucket_name 113 | 114 | depends_on = [ 115 | b2_bucket.test, 116 | ] 117 | } 118 | `, bucketName) 119 | } 120 | 121 | func testAccDataSourceB2BucketConfig_all(bucketName string) string { 122 | return fmt.Sprintf(` 123 | resource "b2_bucket" "test" { 124 | bucket_name = "%s" 125 | bucket_type = "allPrivate" 126 | bucket_info = { 127 | description = "the bucket" 128 | } 129 | cors_rules { 130 | cors_rule_name = "downloadFromAnyOrigin" 131 | allowed_origins = [ 132 | "https" 133 | ] 134 | allowed_operations = [ 135 | "b2_download_file_by_id", 136 | "b2_download_file_by_name" 137 | ] 138 | expose_headers = ["x-bz-content-sha1"] 139 | allowed_headers = ["range"] 140 | max_age_seconds = 3600 141 | } 142 | default_server_side_encryption { 143 | mode = "SSE-B2" 144 | algorithm = "AES256" 145 | } 146 | lifecycle_rules { 147 | file_name_prefix = "" 148 | days_from_hiding_to_deleting = 2 149 | days_from_uploading_to_hiding = 1 150 | } 151 | } 152 | 153 | data "b2_bucket" "test" { 154 | bucket_name = b2_bucket.test.bucket_name 155 | 156 | depends_on = [ 157 | b2_bucket.test, 158 | ] 159 | } 160 | `, bucketName) 161 | } 162 | -------------------------------------------------------------------------------- /b2/provider.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/provider.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "bytes" 15 | "context" 16 | "fmt" 17 | "strings" 18 | 19 | "github.com/hashicorp/terraform-plugin-log/tflog" 20 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 21 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 22 | ) 23 | 24 | func init() { 25 | schema.DescriptionKind = schema.StringMarkdown 26 | 27 | schema.SchemaDescriptionBuilder = func(s *schema.Schema) string { 28 | desc := s.Description 29 | desc = strings.TrimSpace(desc) 30 | 31 | if !bytes.HasSuffix([]byte(desc), []byte(".")) && desc != "" { 32 | desc += "." 33 | } 34 | 35 | if s.Default != nil || s.DefaultFunc != nil { 36 | if s.DefaultFunc != nil { 37 | val, err := s.DefaultFunc() 38 | if err == nil && val != nil { 39 | desc += fmt.Sprintf(" Defaults to `%v`.", val) 40 | } 41 | } else if s.Default == "" { 42 | desc += " Defaults to `\"\"`." 43 | } else { 44 | desc += fmt.Sprintf(" Defaults to `%v`.", s.Default) 45 | } 46 | } 47 | 48 | if s.RequiredWith != nil && len(s.RequiredWith) > 0 { 49 | requiredWith := make([]string, len(s.RequiredWith)) 50 | for i, c := range s.RequiredWith { 51 | requiredWith[i] = fmt.Sprintf("`%s`", c) 52 | } 53 | desc += fmt.Sprintf(" Required when using %s.", strings.Join(requiredWith, ", ")) 54 | } 55 | 56 | if s.ConflictsWith != nil && len(s.ConflictsWith) > 0 { 57 | conflicts := make([]string, len(s.ConflictsWith)) 58 | for i, c := range s.ConflictsWith { 59 | conflicts[i] = fmt.Sprintf("`%s`", c) 60 | } 61 | desc += fmt.Sprintf(" Conflicts with %s.", strings.Join(conflicts, ", ")) 62 | } 63 | 64 | if s.ExactlyOneOf != nil && len(s.ExactlyOneOf) > 0 { 65 | exactlyOneOfs := make([]string, len(s.ExactlyOneOf)) 66 | for i, c := range s.ExactlyOneOf { 67 | exactlyOneOfs[i] = fmt.Sprintf("`%s`", c) 68 | } 69 | desc += fmt.Sprintf(" Must provide only one of %s.", strings.Join(exactlyOneOfs, ", ")) 70 | } 71 | 72 | if s.AtLeastOneOf != nil && len(s.AtLeastOneOf) > 0 { 73 | atLeastOneOfs := make([]string, len(s.AtLeastOneOf)) 74 | for i, c := range s.AtLeastOneOf { 75 | atLeastOneOfs[i] = fmt.Sprintf("`%s`", c) 76 | } 77 | desc += fmt.Sprintf(" Must provide at least one of %s.", strings.Join(atLeastOneOfs, ", ")) 78 | } 79 | 80 | if s.ForceNew { 81 | desc += " **Modifying this attribute will force creation of a new resource.**" 82 | } 83 | 84 | return strings.TrimSpace(desc) 85 | } 86 | } 87 | 88 | func New(version string, exec string) func() *schema.Provider { 89 | return func() *schema.Provider { 90 | p := &schema.Provider{ 91 | Schema: map[string]*schema.Schema{ 92 | "application_key_id": { 93 | Description: "B2 Application Key ID (B2_APPLICATION_KEY_ID env)", 94 | Type: schema.TypeString, 95 | Optional: true, 96 | Sensitive: true, 97 | DefaultFunc: schema.EnvDefaultFunc("B2_APPLICATION_KEY_ID", nil), 98 | }, 99 | "application_key": { 100 | Description: "B2 Application Key (B2_APPLICATION_KEY env)", 101 | Type: schema.TypeString, 102 | Optional: true, 103 | Sensitive: true, 104 | DefaultFunc: schema.EnvDefaultFunc("B2_APPLICATION_KEY", nil), 105 | }, 106 | "endpoint": { 107 | Description: "B2 endpoint - the string 'production' or a custom B2 API URL (B2_ENDPOINT env)." + 108 | " You should not need to set this unless you work at Backblaze.", 109 | Type: schema.TypeString, 110 | Optional: true, 111 | DefaultFunc: schema.EnvDefaultFunc("B2_ENDPOINT", "production"), 112 | }, 113 | }, 114 | DataSourcesMap: map[string]*schema.Resource{ 115 | "b2_account_info": dataSourceB2AccountInfo(), 116 | "b2_application_key": dataSourceB2ApplicationKey(), 117 | "b2_bucket": dataSourceB2Bucket(), 118 | "b2_bucket_file": dataSourceB2BucketFile(), 119 | "b2_bucket_file_signed_url": dataSourceB2BucketFileSignedUrl(), 120 | "b2_bucket_files": dataSourceB2BucketFiles(), 121 | "b2_bucket_notification_rules": dataSourceB2BucketNotificationRules(), 122 | }, 123 | ResourcesMap: map[string]*schema.Resource{ 124 | "b2_application_key": resourceB2ApplicationKey(), 125 | "b2_bucket": resourceB2Bucket(), 126 | "b2_bucket_file_version": resourceB2BucketFileVersion(), 127 | "b2_bucket_notification_rules": resourceB2BucketNotificationRules(), 128 | }, 129 | } 130 | 131 | p.ConfigureContextFunc = configure(version, exec, p) 132 | 133 | return p 134 | } 135 | } 136 | 137 | func configure(version string, exec string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) { 138 | return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 139 | dataSources := map[string][]string{} 140 | sensitiveDataSources := map[string]map[string]bool{} 141 | for k, v := range p.DataSourcesMap { 142 | sensitiveDataSources[k] = make(map[string]bool) 143 | for kk, vv := range v.Schema { 144 | dataSources[k] = append(dataSources[k], kk) 145 | if vv.Sensitive { 146 | sensitiveDataSources[k][kk] = true 147 | } 148 | } 149 | } 150 | 151 | resources := map[string][]string{} 152 | sensitiveResources := map[string]map[string]bool{} 153 | for k, v := range p.ResourcesMap { 154 | sensitiveResources[k] = make(map[string]bool) 155 | for kk, vv := range v.Schema { 156 | resources[k] = append(resources[k], kk) 157 | if vv.Sensitive { 158 | sensitiveResources[k][kk] = true 159 | } 160 | } 161 | } 162 | 163 | userAgent := p.UserAgent("Terraform-B2-Provider", version) 164 | client := &Client{ 165 | Exec: exec, 166 | UserAgentAppend: userAgent, 167 | ApplicationKeyId: d.Get("application_key_id").(string), 168 | ApplicationKey: d.Get("application_key").(string), 169 | Endpoint: d.Get("endpoint").(string), 170 | DataSources: dataSources, 171 | Resources: resources, 172 | SensitiveDataSources: sensitiveDataSources, 173 | SensitiveResources: sensitiveResources, 174 | } 175 | 176 | tflog.Info(ctx, "User Agent append", map[string]interface{}{ 177 | "user_agent_append": userAgent, 178 | }) 179 | 180 | return client, nil 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /b2/provider_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/provider_test.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "log" 15 | "os" 16 | "path/filepath" 17 | "testing" 18 | 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 20 | ) 21 | 22 | // providerFactories are used to instantiate a provider during acceptance testing. 23 | // The factory function will be invoked for every Terraform CLI command executed 24 | // to create a provider server to which the CLI can reattach. 25 | var providerFactories = map[string]func() (*schema.Provider, error){ 26 | "b2": func() (*schema.Provider, error) { 27 | pybindings, err := GetBindings() 28 | if err != nil { 29 | log.Fatal(err.Error()) 30 | return nil, err 31 | } 32 | return New("test", pybindings)(), nil 33 | }, 34 | } 35 | 36 | func TestProvider(t *testing.T) { 37 | pybindings, err := GetBindings() 38 | if err != nil { 39 | t.Fatalf("err: %s", err) 40 | } 41 | if err := New("test", pybindings)().InternalValidate(); err != nil { 42 | t.Fatalf("err: %s", err) 43 | } 44 | } 45 | 46 | func testAccPreCheck(t *testing.T) { 47 | _, present := os.LookupEnv("B2_TEST_APPLICATION_KEY_ID") 48 | if !present { 49 | t.Fatal("B2_TEST_APPLICATION_KEY_ID is not set") 50 | } 51 | _ = os.Setenv("B2_APPLICATION_KEY_ID", os.Getenv("B2_TEST_APPLICATION_KEY_ID")) 52 | 53 | _, present = os.LookupEnv("B2_TEST_APPLICATION_KEY") 54 | if !present { 55 | t.Fatal("B2_TEST_APPLICATION_KEY is not set") 56 | } 57 | _ = os.Setenv("B2_APPLICATION_KEY", os.Getenv("B2_TEST_APPLICATION_KEY")) 58 | } 59 | 60 | // Utility functions 61 | 62 | func createTempFile(t *testing.T) *os.File { 63 | tmpFile, err := os.CreateTemp("", "test-b2-tfp") 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | return tmpFile 69 | } 70 | 71 | func createTempFileString(t *testing.T, data string) string { 72 | tmpFile := createTempFile(t) 73 | 74 | _, err := tmpFile.WriteString(data) 75 | if err != nil { 76 | os.Remove(tmpFile.Name()) 77 | t.Fatal(err) 78 | } 79 | 80 | return filepath.ToSlash(tmpFile.Name()) 81 | } 82 | 83 | func createTempFileTruncate(t *testing.T, size int64) string { 84 | tmpFile := createTempFile(t) 85 | 86 | err := tmpFile.Truncate(size) 87 | if err != nil { 88 | os.Remove(tmpFile.Name()) 89 | t.Fatal(err) 90 | } 91 | 92 | return filepath.ToSlash(tmpFile.Name()) 93 | } 94 | -------------------------------------------------------------------------------- /b2/resource_b2_application_key.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_application_key.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-log/tflog" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 20 | ) 21 | 22 | func resourceB2ApplicationKey() *schema.Resource { 23 | return &schema.Resource{ 24 | Description: "B2 application key resource.", 25 | 26 | CreateContext: resourceB2ApplicationKeyCreate, 27 | ReadContext: resourceB2ApplicationKeyRead, 28 | DeleteContext: resourceB2ApplicationKeyDelete, 29 | Importer: &schema.ResourceImporter{ 30 | StateContext: schema.ImportStatePassthroughContext, 31 | }, 32 | 33 | Schema: map[string]*schema.Schema{ 34 | "capabilities": { 35 | Description: "A set of strings, each one naming a capability the key has.", 36 | Type: schema.TypeSet, 37 | Elem: &schema.Schema{ 38 | Type: schema.TypeString, 39 | }, 40 | Required: true, 41 | ForceNew: true, 42 | }, 43 | "key_name": { 44 | Description: "The name of the key.", 45 | Type: schema.TypeString, 46 | Required: true, 47 | ForceNew: true, 48 | ValidateFunc: validation.NoZeroValues, 49 | }, 50 | "bucket_id": { 51 | Description: "When present, restricts access to one bucket.", 52 | Type: schema.TypeString, 53 | Optional: true, 54 | ForceNew: true, 55 | }, 56 | "name_prefix": { 57 | Description: "When present, restricts access to files whose names start with the prefix.", 58 | Type: schema.TypeString, 59 | Optional: true, 60 | ForceNew: true, 61 | RequiredWith: []string{"bucket_id"}, 62 | }, 63 | "application_key": { 64 | Description: "The key.", 65 | Type: schema.TypeString, 66 | Computed: true, 67 | Sensitive: true, 68 | }, 69 | "application_key_id": { 70 | Description: "The ID of the newly created key.", 71 | Type: schema.TypeString, 72 | Computed: true, 73 | }, 74 | "options": { 75 | Description: "List of application key options.", 76 | Type: schema.TypeSet, 77 | Elem: &schema.Schema{ 78 | Type: schema.TypeString, 79 | }, 80 | Computed: true, 81 | }, 82 | }, 83 | } 84 | } 85 | 86 | func resourceB2ApplicationKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 87 | client := meta.(*Client) 88 | const name = "application_key" 89 | const op = RESOURCE_CREATE 90 | 91 | input := map[string]interface{}{ 92 | "key_name": d.Get("key_name").(string), 93 | "capabilities": d.Get("capabilities").(*schema.Set).List(), 94 | "bucket_id": d.Get("bucket_id").(string), 95 | "name_prefix": d.Get("name_prefix").(string), 96 | } 97 | 98 | output, err := client.apply(ctx, name, op, input) 99 | if err != nil { 100 | return diag.FromErr(err) 101 | } 102 | 103 | d.SetId(output["application_key_id"].(string)) 104 | 105 | err = client.populate(ctx, name, op, output, d) 106 | if err != nil { 107 | return diag.FromErr(err) 108 | } 109 | 110 | return nil 111 | } 112 | 113 | func resourceB2ApplicationKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 114 | client := meta.(*Client) 115 | const name = "application_key" 116 | const op = RESOURCE_READ 117 | 118 | input := map[string]interface{}{ 119 | "application_key_id": d.Id(), 120 | } 121 | 122 | output, err := client.apply(ctx, name, op, input) 123 | if err != nil { 124 | return diag.FromErr(err) 125 | } 126 | if _, ok := output["application_key_id"]; !ok && !d.IsNewResource() { 127 | // deleted application key 128 | tflog.Warn(ctx, "Application Key not found, possible resource drift", map[string]interface{}{ 129 | "application_key_id": d.Id(), 130 | }) 131 | d.SetId("") 132 | return nil 133 | } 134 | 135 | output["application_key"] = d.Get("application_key").(string) 136 | 137 | err = client.populate(ctx, name, op, output, d) 138 | if err != nil { 139 | return diag.FromErr(err) 140 | } 141 | 142 | return nil 143 | } 144 | 145 | func resourceB2ApplicationKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 146 | client := meta.(*Client) 147 | const name = "application_key" 148 | const op = RESOURCE_DELETE 149 | 150 | input := map[string]interface{}{ 151 | "application_key_id": d.Id(), 152 | } 153 | 154 | _, err := client.apply(ctx, name, op, input) 155 | if err != nil { 156 | return diag.FromErr(err) 157 | } 158 | 159 | d.SetId("") 160 | 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /b2/resource_b2_application_key_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_application_key_test.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "regexp" 16 | "testing" 17 | 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 20 | ) 21 | 22 | func TestAccResourceB2ApplicationKey_basic(t *testing.T) { 23 | resourceName := "b2_application_key.test" 24 | 25 | keyName := acctest.RandomWithPrefix("test-b2-tfp") 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccPreCheck(t) }, 29 | ProviderFactories: providerFactories, 30 | Steps: []resource.TestStep{ 31 | { 32 | Config: testAccResourceB2ApplicationKeyConfig_basic(keyName), 33 | Check: resource.ComposeTestCheckFunc( 34 | resource.TestMatchResourceAttr(resourceName, "application_key", regexp.MustCompile("^[\x20-\x7E]{31}$")), 35 | resource.TestMatchResourceAttr(resourceName, "application_key_id", regexp.MustCompile("^[a-zA-Z0-9]{25}$")), 36 | resource.TestCheckResourceAttr(resourceName, "bucket_id", ""), 37 | resource.TestCheckResourceAttr(resourceName, "bucket_id", ""), 38 | resource.TestCheckResourceAttr(resourceName, "capabilities.#", "1"), 39 | resource.TestCheckResourceAttr(resourceName, "capabilities.0", "readFiles"), 40 | resource.TestCheckResourceAttr(resourceName, "key_name", keyName), 41 | resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), 42 | resource.TestCheckResourceAttr(resourceName, "options.#", "1"), 43 | resource.TestCheckResourceAttr(resourceName, "options.0", "s3"), 44 | ), 45 | }, 46 | { 47 | ResourceName: resourceName, 48 | ImportState: true, 49 | ImportStateVerify: true, 50 | ImportStateVerifyIgnore: []string{"application_key"}, 51 | }, 52 | }, 53 | }) 54 | } 55 | 56 | func TestAccResourceB2ApplicationKey_all(t *testing.T) { 57 | parentResourceName := "b2_bucket.test" 58 | resourceName := "b2_application_key.test" 59 | 60 | keyName := acctest.RandomWithPrefix("test-b2-tfp") 61 | 62 | resource.Test(t, resource.TestCase{ 63 | PreCheck: func() { testAccPreCheck(t) }, 64 | ProviderFactories: providerFactories, 65 | Steps: []resource.TestStep{ 66 | { 67 | Config: testAccResourceB2ApplicationKeyConfig_all(keyName), 68 | Check: resource.ComposeTestCheckFunc( 69 | resource.TestMatchResourceAttr(resourceName, "application_key", regexp.MustCompile("^[\x20-\x7E]{31}$")), 70 | resource.TestMatchResourceAttr(resourceName, "application_key_id", regexp.MustCompile("^[a-zA-Z0-9]{25}$")), 71 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 72 | resource.TestCheckResourceAttr(resourceName, "capabilities.#", "1"), 73 | resource.TestCheckResourceAttr(resourceName, "capabilities.0", "writeFiles"), 74 | resource.TestCheckResourceAttr(resourceName, "key_name", keyName), 75 | resource.TestCheckResourceAttr(resourceName, "name_prefix", "prefix"), 76 | resource.TestCheckResourceAttr(resourceName, "options.#", "1"), 77 | resource.TestCheckResourceAttr(resourceName, "options.0", "s3"), 78 | ), 79 | }, 80 | { 81 | ResourceName: resourceName, 82 | ImportState: true, 83 | ImportStateVerify: true, 84 | ImportStateVerifyIgnore: []string{"application_key"}, 85 | }, 86 | }, 87 | }) 88 | } 89 | 90 | func testAccResourceB2ApplicationKeyConfig_basic(keyName string) string { 91 | return fmt.Sprintf(` 92 | resource "b2_application_key" "test" { 93 | key_name = "%s" 94 | capabilities = ["readFiles"] 95 | } 96 | `, keyName) 97 | } 98 | 99 | func testAccResourceB2ApplicationKeyConfig_all(keyName string) string { 100 | return fmt.Sprintf(` 101 | resource "b2_bucket" "test" { 102 | bucket_name = "%s" 103 | bucket_type = "allPrivate" 104 | } 105 | 106 | resource "b2_application_key" "test" { 107 | key_name = "%s" 108 | capabilities = ["writeFiles"] 109 | bucket_id = b2_bucket.test.bucket_id 110 | name_prefix = "prefix" 111 | } 112 | `, keyName, keyName) 113 | } 114 | -------------------------------------------------------------------------------- /b2/resource_b2_bucket.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_bucket.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-log/tflog" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 20 | ) 21 | 22 | func resourceB2Bucket() *schema.Resource { 23 | return &schema.Resource{ 24 | Description: "B2 bucket resource.", 25 | 26 | CreateContext: resourceB2BucketCreate, 27 | ReadContext: resourceB2BucketRead, 28 | UpdateContext: resourceB2BucketUpdate, 29 | DeleteContext: resourceB2BucketDelete, 30 | Importer: &schema.ResourceImporter{ 31 | StateContext: schema.ImportStatePassthroughContext, 32 | }, 33 | 34 | Schema: map[string]*schema.Schema{ 35 | "bucket_name": { 36 | Description: "The name of the bucket.", 37 | Type: schema.TypeString, 38 | Required: true, 39 | ForceNew: true, 40 | ValidateFunc: validation.NoZeroValues, 41 | }, 42 | "bucket_type": { 43 | Description: "The bucket type. Either 'allPublic', meaning that files in this bucket can be downloaded by anybody, or 'allPrivate'.", 44 | Type: schema.TypeString, 45 | Required: true, 46 | ValidateFunc: validation.NoZeroValues, 47 | }, 48 | "bucket_info": { 49 | Description: "User-defined information to be stored with the bucket.", 50 | Type: schema.TypeMap, 51 | Elem: &schema.Schema{ 52 | Type: schema.TypeString, 53 | }, 54 | Optional: true, 55 | }, 56 | "cors_rules": { 57 | Description: "The initial list of CORS rules for this bucket.", 58 | Type: schema.TypeList, 59 | Elem: getCorsRulesElem(false), 60 | Optional: true, 61 | }, 62 | "file_lock_configuration": { 63 | Description: "File lock enabled flag, and default retention settings.", 64 | Type: schema.TypeList, 65 | Elem: getFileLockConfigurationElem(false), 66 | Optional: true, 67 | DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 68 | // The API sets default value 69 | if k == "file_lock_configuration.#" { 70 | return old == "1" && new == "0" 71 | } 72 | return old == "none" && new == "" 73 | }, 74 | }, 75 | "default_server_side_encryption": { 76 | Description: "The default server-side encryption settings for this bucket.", 77 | Type: schema.TypeList, 78 | Elem: getServerSideEncryptionElem(false), 79 | Optional: true, 80 | MaxItems: 1, 81 | DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 82 | // The API sets default value 83 | if k == "default_server_side_encryption.#" { 84 | return old == "1" && new == "0" 85 | } 86 | return old == "none" && new == "" 87 | }, 88 | }, 89 | "lifecycle_rules": { 90 | Description: "The initial list of lifecycle rules for this bucket.", 91 | Type: schema.TypeList, 92 | Elem: getLifecycleRulesElem(false), 93 | Optional: true, 94 | }, 95 | "bucket_id": { 96 | Description: "The ID of the bucket.", 97 | Type: schema.TypeString, 98 | Computed: true, 99 | }, 100 | "account_id": { 101 | Description: "Account ID that the bucket belongs to.", 102 | Type: schema.TypeString, 103 | Computed: true, 104 | }, 105 | "options": { 106 | Description: "List of bucket options.", 107 | Type: schema.TypeSet, 108 | Elem: &schema.Schema{ 109 | Type: schema.TypeString, 110 | }, 111 | Computed: true, 112 | }, 113 | "revision": { 114 | Description: "Bucket revision.", 115 | Type: schema.TypeInt, 116 | Computed: true, 117 | }, 118 | }, 119 | } 120 | } 121 | 122 | func resourceB2BucketCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 123 | client := meta.(*Client) 124 | const name = "bucket" 125 | const op = RESOURCE_CREATE 126 | 127 | input := map[string]interface{}{ 128 | "bucket_name": d.Get("bucket_name").(string), 129 | "bucket_type": d.Get("bucket_type").(string), 130 | "bucket_info": d.Get("bucket_info").(map[string]interface{}), 131 | "cors_rules": d.Get("cors_rules").([]interface{}), 132 | "file_lock_configuration": d.Get("file_lock_configuration").([]interface{}), 133 | "default_server_side_encryption": d.Get("default_server_side_encryption").([]interface{}), 134 | "lifecycle_rules": d.Get("lifecycle_rules").([]interface{}), 135 | } 136 | 137 | output, err := client.apply(ctx, name, op, input) 138 | if err != nil { 139 | return diag.FromErr(err) 140 | } 141 | 142 | d.SetId(output["bucket_id"].(string)) 143 | 144 | err = client.populate(ctx, name, op, output, d) 145 | if err != nil { 146 | return diag.FromErr(err) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func resourceB2BucketRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 153 | client := meta.(*Client) 154 | const name = "bucket" 155 | const op = RESOURCE_READ 156 | 157 | input := map[string]interface{}{ 158 | "bucket_id": d.Id(), 159 | "cors_rules": d.Get("cors_rules"), 160 | } 161 | 162 | output, err := client.apply(ctx, name, op, input) 163 | if err != nil { 164 | return diag.FromErr(err) 165 | } 166 | if _, ok := output["bucket_id"]; !ok && !d.IsNewResource() { 167 | // deleted bucket 168 | tflog.Warn(ctx, "Bucket not found, possible resource drift", map[string]interface{}{ 169 | "bucket_id": d.Id(), 170 | }) 171 | d.SetId("") 172 | return nil 173 | } 174 | 175 | err = client.populate(ctx, name, op, output, d) 176 | if err != nil { 177 | return diag.FromErr(err) 178 | } 179 | 180 | return nil 181 | } 182 | 183 | func resourceB2BucketUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 184 | client := meta.(*Client) 185 | const name = "bucket" 186 | const op = RESOURCE_UPDATE 187 | 188 | input := map[string]interface{}{ 189 | "bucket_id": d.Id(), 190 | "account_id": d.Get("account_id").(string), 191 | "bucket_type": d.Get("bucket_type").(string), 192 | "bucket_info": d.Get("bucket_info").(map[string]interface{}), 193 | "cors_rules": d.Get("cors_rules").([]interface{}), 194 | "file_lock_configuration": d.Get("file_lock_configuration").([]interface{}), 195 | "default_server_side_encryption": d.Get("default_server_side_encryption").([]interface{}), 196 | "lifecycle_rules": d.Get("lifecycle_rules").([]interface{}), 197 | } 198 | 199 | output, err := client.apply(ctx, name, op, input) 200 | if err != nil { 201 | return diag.FromErr(err) 202 | } 203 | 204 | err = client.populate(ctx, name, op, output, d) 205 | if err != nil { 206 | return diag.FromErr(err) 207 | } 208 | 209 | return nil 210 | } 211 | 212 | func resourceB2BucketDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 213 | client := meta.(*Client) 214 | const name = "bucket" 215 | const op = RESOURCE_DELETE 216 | 217 | input := map[string]interface{}{ 218 | "bucket_id": d.Id(), 219 | } 220 | 221 | _, err := client.apply(ctx, name, op, input) 222 | if err != nil { 223 | return diag.FromErr(err) 224 | } 225 | 226 | d.SetId("") 227 | 228 | return nil 229 | } 230 | -------------------------------------------------------------------------------- /b2/resource_b2_bucket_file_version.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_bucket_file_version.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 19 | ) 20 | 21 | func resourceB2BucketFileVersion() *schema.Resource { 22 | return &schema.Resource{ 23 | Description: "B2 bucket file version resource.", 24 | 25 | CreateContext: resourceB2BucketFileVersionCreate, 26 | ReadContext: resourceB2BucketFileVersionRead, 27 | DeleteContext: resourceB2BucketFileVersionDelete, 28 | 29 | Schema: map[string]*schema.Schema{ 30 | "bucket_id": { 31 | Description: "The ID of the bucket.", 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | ValidateFunc: validation.NoZeroValues, 36 | }, 37 | "file_name": { 38 | Description: "The name of the B2 file.", 39 | Type: schema.TypeString, 40 | Required: true, 41 | ForceNew: true, 42 | ValidateFunc: validation.NoZeroValues, 43 | }, 44 | "source": { 45 | Description: "Path to the local file.", 46 | Type: schema.TypeString, 47 | Required: true, 48 | ForceNew: true, 49 | ValidateFunc: validation.NoZeroValues, 50 | }, 51 | "content_type": { 52 | Description: "Content type. If not set, it will be set based on the file extension.", 53 | Type: schema.TypeString, 54 | Optional: true, 55 | ForceNew: true, 56 | DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 57 | return new == "" // The API sets default value 58 | }, 59 | }, 60 | "file_info": { 61 | Description: "The custom information that is uploaded with the file.", 62 | Type: schema.TypeMap, 63 | Elem: &schema.Schema{ 64 | Type: schema.TypeString, 65 | }, 66 | Optional: true, 67 | ForceNew: true, 68 | Computed: true, 69 | DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 70 | return k == "file_info.sse_c_key_id" || old == new 71 | }, 72 | }, 73 | "server_side_encryption": { 74 | Description: "Server-side encryption settings.", 75 | Type: schema.TypeList, 76 | Elem: getResourceFileEncryptionElem(), 77 | Optional: true, 78 | ForceNew: true, 79 | MaxItems: 1, 80 | DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 81 | // The API sets default value 82 | if k == "server_side_encryption.#" { 83 | return old == "1" && new == "0" 84 | } 85 | return old == "none" && new == "" 86 | }, 87 | }, 88 | "action": { 89 | Description: "One of 'start', 'upload', 'hide', 'folder', or other values added in the future.", 90 | Type: schema.TypeString, 91 | Computed: true, 92 | }, 93 | "content_md5": { 94 | Description: "MD5 sum of the content.", 95 | Type: schema.TypeString, 96 | Computed: true, 97 | }, 98 | "content_sha1": { 99 | Description: "SHA1 hash of the content.", 100 | Type: schema.TypeString, 101 | Computed: true, 102 | }, 103 | "file_id": { 104 | Description: "The unique identifier for this version of this file.", 105 | Type: schema.TypeString, 106 | Computed: true, 107 | }, 108 | "size": { 109 | Description: "The file size.", 110 | Type: schema.TypeInt, 111 | Computed: true, 112 | }, 113 | "upload_timestamp": { 114 | Description: "This is a UTC time when this file was uploaded.", 115 | Type: schema.TypeInt, 116 | Computed: true, 117 | }, 118 | }, 119 | } 120 | } 121 | 122 | func resourceB2BucketFileVersionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 123 | client := meta.(*Client) 124 | const name = "bucket_file_version" 125 | const op = RESOURCE_CREATE 126 | 127 | input := map[string]interface{}{ 128 | "bucket_id": d.Get("bucket_id").(string), 129 | "file_name": d.Get("file_name").(string), 130 | "source": d.Get("source").(string), 131 | "content_type": d.Get("content_type").(string), 132 | "file_info": d.Get("file_info").(map[string]interface{}), 133 | "server_side_encryption": d.Get("server_side_encryption").([]interface{}), 134 | } 135 | 136 | output, err := client.apply(ctx, name, op, input) 137 | if err != nil { 138 | return diag.FromErr(err) 139 | } 140 | 141 | d.SetId(output["file_id"].(string)) 142 | 143 | err = client.populate(ctx, name, op, output, d) 144 | if err != nil { 145 | return diag.FromErr(err) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func resourceB2BucketFileVersionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 152 | client := meta.(*Client) 153 | const name = "bucket_file_version" 154 | const op = RESOURCE_READ 155 | 156 | input := map[string]interface{}{ 157 | "file_id": d.Id(), 158 | } 159 | 160 | output, err := client.apply(ctx, name, op, input) 161 | if err != nil { 162 | return diag.FromErr(err) 163 | } 164 | 165 | output["bucket_id"] = d.Get("bucket_id").(string) 166 | output["size"] = d.Get("size").(int) 167 | output["source"] = d.Get("source").(string) 168 | 169 | err = client.populate(ctx, name, op, output, d) 170 | if err != nil { 171 | return diag.FromErr(err) 172 | } 173 | 174 | return nil 175 | } 176 | 177 | func resourceB2BucketFileVersionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 178 | client := meta.(*Client) 179 | const name = "bucket_file_version" 180 | const op = RESOURCE_DELETE 181 | 182 | input := map[string]interface{}{ 183 | "file_id": d.Id(), 184 | "file_name": d.Get("file_name").(string), 185 | } 186 | 187 | _, err := client.apply(ctx, name, op, input) 188 | if err != nil { 189 | return diag.FromErr(err) 190 | } 191 | 192 | d.SetId("") 193 | 194 | return nil 195 | } 196 | -------------------------------------------------------------------------------- /b2/resource_b2_bucket_file_version_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_bucket_file_version_test.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "regexp" 17 | "strconv" 18 | "testing" 19 | 20 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 21 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 22 | ) 23 | 24 | func TestAccResourceB2BucketFileVersion_basic(t *testing.T) { 25 | parentResourceName := "b2_bucket.test" 26 | resourceName := "b2_bucket_file_version.test" 27 | 28 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 29 | tempFile := createTempFileString(t, "hello") 30 | defer os.Remove(tempFile) 31 | 32 | resource.Test(t, resource.TestCase{ 33 | PreCheck: func() { testAccPreCheck(t) }, 34 | ProviderFactories: providerFactories, 35 | Steps: []resource.TestStep{ 36 | { 37 | Config: testAccResourceB2BucketFileVersionConfig_basic(bucketName, tempFile), 38 | Check: resource.ComposeTestCheckFunc( 39 | resource.TestCheckResourceAttr(resourceName, "action", "upload"), 40 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 41 | resource.TestCheckResourceAttr(resourceName, "content_md5", "5d41402abc4b2a76b9719d911017c592"), 42 | resource.TestCheckResourceAttr(resourceName, "content_sha1", "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"), 43 | resource.TestCheckResourceAttr(resourceName, "content_type", "text/plain"), 44 | resource.TestCheckResourceAttr(resourceName, "file_info.%", "0"), 45 | resource.TestCheckResourceAttr(resourceName, "file_name", "temp.txt"), 46 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), 47 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.mode", "none"), 48 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.algorithm", ""), 49 | resource.TestCheckResourceAttr(resourceName, "size", "5"), 50 | resource.TestCheckResourceAttr(resourceName, "source", tempFile), 51 | resource.TestMatchResourceAttr(resourceName, "upload_timestamp", regexp.MustCompile("^[0-9]{13}$")), 52 | ), 53 | }, 54 | }, 55 | }) 56 | } 57 | 58 | func TestAccResourceB2BucketFileVersion_all(t *testing.T) { 59 | parentResourceName := "b2_bucket.test" 60 | resourceName := "b2_bucket_file_version.test" 61 | 62 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 63 | tempFile := createTempFileString(t, "hello") 64 | defer os.Remove(tempFile) 65 | 66 | resource.Test(t, resource.TestCase{ 67 | PreCheck: func() { testAccPreCheck(t) }, 68 | ProviderFactories: providerFactories, 69 | Steps: []resource.TestStep{ 70 | { 71 | Config: testAccResourceB2BucketFileVersionConfig_all(bucketName, tempFile), 72 | Check: resource.ComposeTestCheckFunc( 73 | resource.TestCheckResourceAttr(resourceName, "action", "upload"), 74 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 75 | resource.TestCheckResourceAttr(resourceName, "content_md5", "5d41402abc4b2a76b9719d911017c592"), 76 | resource.TestCheckResourceAttr(resourceName, "content_sha1", "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"), 77 | resource.TestCheckResourceAttr(resourceName, "content_type", "octet/stream"), 78 | resource.TestCheckResourceAttr(resourceName, "file_info.%", "1"), 79 | resource.TestCheckResourceAttr(resourceName, "file_info.description", "the file"), 80 | resource.TestCheckResourceAttr(resourceName, "file_name", "temp.bin"), 81 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), 82 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.mode", "SSE-B2"), 83 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.algorithm", "AES256"), 84 | resource.TestCheckResourceAttr(resourceName, "source", tempFile), 85 | resource.TestCheckResourceAttr(resourceName, "size", "5"), 86 | resource.TestMatchResourceAttr(resourceName, "upload_timestamp", regexp.MustCompile("^[0-9]{13}$")), 87 | ), 88 | }, 89 | }, 90 | }) 91 | } 92 | 93 | func TestAccResourceB2BucketFileVersion_forceNew(t *testing.T) { 94 | parentResourceName := "b2_bucket.test" 95 | resourceName := "b2_bucket_file_version.test" 96 | 97 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 98 | tempFile := createTempFileString(t, "hello") 99 | defer os.Remove(tempFile) 100 | 101 | resource.Test(t, resource.TestCase{ 102 | PreCheck: func() { testAccPreCheck(t) }, 103 | ProviderFactories: providerFactories, 104 | Steps: []resource.TestStep{ 105 | { 106 | Config: testAccResourceB2BucketFileVersionConfig_basic(bucketName, tempFile), 107 | }, 108 | { 109 | Config: testAccResourceB2BucketFileVersionConfig_all(bucketName, tempFile), 110 | Check: resource.ComposeTestCheckFunc( 111 | resource.TestCheckResourceAttr(resourceName, "action", "upload"), 112 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 113 | resource.TestCheckResourceAttr(resourceName, "content_md5", "5d41402abc4b2a76b9719d911017c592"), 114 | resource.TestCheckResourceAttr(resourceName, "content_sha1", "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"), 115 | resource.TestCheckResourceAttr(resourceName, "content_type", "octet/stream"), 116 | resource.TestCheckResourceAttr(resourceName, "file_info.%", "1"), 117 | resource.TestCheckResourceAttr(resourceName, "file_info.description", "the file"), 118 | resource.TestCheckResourceAttr(resourceName, "file_name", "temp.bin"), 119 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), 120 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.mode", "SSE-B2"), 121 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.algorithm", "AES256"), 122 | resource.TestCheckResourceAttr(resourceName, "source", tempFile), 123 | resource.TestCheckResourceAttr(resourceName, "size", "5"), 124 | resource.TestMatchResourceAttr(resourceName, "upload_timestamp", regexp.MustCompile("^[0-9]{13}$")), 125 | ), 126 | }, 127 | }, 128 | }) 129 | } 130 | 131 | func TestAccResourceB2BucketFileVersion_largeFile(t *testing.T) { 132 | parentResourceName := "b2_bucket.test" 133 | resourceName := "b2_bucket_file_version.test" 134 | var fileSize int64 = 105 * 1000 * 1000 // 105MB file is uploaded as a large file 135 | 136 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 137 | tempFile := createTempFileTruncate(t, fileSize) 138 | defer os.Remove(tempFile) 139 | 140 | resource.Test(t, resource.TestCase{ 141 | PreCheck: func() { testAccPreCheck(t) }, 142 | ProviderFactories: providerFactories, 143 | Steps: []resource.TestStep{ 144 | { 145 | Config: testAccResourceB2BucketFileVersionConfig_basic(bucketName, tempFile), 146 | Check: resource.ComposeTestCheckFunc( 147 | resource.TestCheckResourceAttr(resourceName, "action", "upload"), 148 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 149 | resource.TestCheckResourceAttr(resourceName, "content_md5", ""), // empty for large files 150 | resource.TestCheckResourceAttr(resourceName, "content_sha1", "none"), // "none" for large files 151 | resource.TestCheckResourceAttr(resourceName, "content_type", "text/plain"), 152 | resource.TestCheckResourceAttr(resourceName, "file_info.%", "1"), 153 | resource.TestMatchResourceAttr(resourceName, "file_info.large_file_sha1", regexp.MustCompile("^[a-z0-9]{40}$")), 154 | resource.TestCheckResourceAttr(resourceName, "file_name", "temp.txt"), 155 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), 156 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.mode", "none"), 157 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.algorithm", ""), 158 | resource.TestCheckResourceAttr(resourceName, "size", strconv.Itoa(int(fileSize))), 159 | resource.TestCheckResourceAttr(resourceName, "source", tempFile), 160 | resource.TestMatchResourceAttr(resourceName, "upload_timestamp", regexp.MustCompile("^[0-9]{13}$")), 161 | ), 162 | }, 163 | }, 164 | }) 165 | } 166 | 167 | func TestAccResourceB2BucketFileVersion_sse_c(t *testing.T) { 168 | parentResourceName := "b2_bucket.test" 169 | resourceName := "b2_bucket_file_version.test" 170 | 171 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 172 | tempFile := createTempFileString(t, "hello") 173 | defer os.Remove(tempFile) 174 | 175 | resource.Test(t, resource.TestCase{ 176 | PreCheck: func() { testAccPreCheck(t) }, 177 | ProviderFactories: providerFactories, 178 | Steps: []resource.TestStep{ 179 | { 180 | Config: testAccResourceB2BucketFileVersionConfig_sse_c(bucketName, tempFile), 181 | Check: resource.ComposeTestCheckFunc( 182 | resource.TestCheckResourceAttr(resourceName, "action", "upload"), 183 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 184 | resource.TestCheckResourceAttr(resourceName, "content_md5", "5d41402abc4b2a76b9719d911017c592"), 185 | resource.TestCheckResourceAttr(resourceName, "content_sha1", "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"), 186 | resource.TestCheckResourceAttr(resourceName, "content_type", "octet/stream"), 187 | resource.TestCheckResourceAttr(resourceName, "file_info.%", "2"), 188 | resource.TestCheckResourceAttr(resourceName, "file_info.description", "the file"), 189 | resource.TestCheckResourceAttr(resourceName, "file_info.sse_c_key_id", "test_id"), 190 | resource.TestCheckResourceAttr(resourceName, "file_name", "temp.bin"), 191 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), 192 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.mode", "SSE-C"), 193 | resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.algorithm", "AES256"), 194 | resource.TestCheckResourceAttr(resourceName, "source", tempFile), 195 | resource.TestCheckResourceAttr(resourceName, "size", "5"), 196 | resource.TestMatchResourceAttr(resourceName, "upload_timestamp", regexp.MustCompile("^[0-9]{13}$")), 197 | ), 198 | }, 199 | }, 200 | }) 201 | } 202 | 203 | func testAccResourceB2BucketFileVersionConfig_basic(bucketName string, tempFile string) string { 204 | return fmt.Sprintf(` 205 | resource "b2_bucket" "test" { 206 | bucket_name = "%s" 207 | bucket_type = "allPublic" 208 | } 209 | 210 | resource "b2_bucket_file_version" "test" { 211 | bucket_id = b2_bucket.test.id 212 | file_name = "temp.txt" 213 | source = "%s" 214 | } 215 | `, bucketName, tempFile) 216 | } 217 | 218 | func testAccResourceB2BucketFileVersionConfig_all(bucketName string, tempFile string) string { 219 | return fmt.Sprintf(` 220 | resource "b2_bucket" "test" { 221 | bucket_name = "%s" 222 | bucket_type = "allPublic" 223 | } 224 | 225 | resource "b2_bucket_file_version" "test" { 226 | bucket_id = b2_bucket.test.id 227 | file_name = "temp.bin" 228 | source = "%s" 229 | content_type = "octet/stream" 230 | file_info = { 231 | description = "the file" 232 | } 233 | server_side_encryption { 234 | mode = "SSE-B2" 235 | algorithm = "AES256" 236 | } 237 | } 238 | `, bucketName, tempFile) 239 | } 240 | 241 | func testAccResourceB2BucketFileVersionConfig_sse_c(bucketName string, tempFile string) string { 242 | return fmt.Sprintf(` 243 | resource "b2_bucket" "test" { 244 | bucket_name = "%s" 245 | bucket_type = "allPublic" 246 | } 247 | 248 | resource "b2_bucket_file_version" "test" { 249 | bucket_id = b2_bucket.test.id 250 | file_name = "temp.bin" 251 | source = "%s" 252 | content_type = "octet/stream" 253 | file_info = { 254 | description = "the file" 255 | } 256 | server_side_encryption { 257 | mode = "SSE-C" 258 | algorithm = "AES256" 259 | key { 260 | secret_b64 = "notarealkey11111111111111111111111111111111=" 261 | key_id = "test_id" 262 | } 263 | } 264 | } 265 | `, bucketName, tempFile) 266 | } 267 | -------------------------------------------------------------------------------- /b2/resource_b2_bucket_notification_rules.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_bucket_notification_rules.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "context" 15 | 16 | "github.com/hashicorp/terraform-plugin-log/tflog" 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 20 | ) 21 | 22 | func resourceB2BucketNotificationRules() *schema.Resource { 23 | return &schema.Resource{ 24 | Description: "B2 bucket notification rules resource.", 25 | 26 | CreateContext: resourceB2BucketNotificationRulesCreate, 27 | ReadContext: resourceB2BucketNotificationRulesRead, 28 | UpdateContext: resourceB2BucketNotificationRulesUpdate, 29 | DeleteContext: resourceB2BucketNotificationRulesDelete, 30 | 31 | Schema: map[string]*schema.Schema{ 32 | "bucket_id": { 33 | Description: "The ID of the bucket.", 34 | Type: schema.TypeString, 35 | Required: true, 36 | ForceNew: true, 37 | ValidateFunc: validation.NoZeroValues, 38 | }, 39 | "notification_rules": { 40 | Description: "An array of Event Notification Rules.", 41 | Type: schema.TypeList, 42 | Elem: getNotificationRulesElem(false), 43 | Required: true, 44 | MinItems: 1, 45 | }, 46 | }, 47 | } 48 | } 49 | 50 | func resourceB2BucketNotificationRulesCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 51 | client := meta.(*Client) 52 | const name = "bucket_notification_rules" 53 | const op = RESOURCE_CREATE 54 | 55 | input := map[string]interface{}{ 56 | "bucket_id": d.Get("bucket_id").(string), 57 | "notification_rules": d.Get("notification_rules").([]interface{}), 58 | } 59 | 60 | output, err := client.apply(ctx, name, op, input) 61 | if err != nil { 62 | return diag.FromErr(err) 63 | } 64 | 65 | d.SetId(output["bucket_id"].(string)) 66 | 67 | err = client.populate(ctx, name, op, output, d) 68 | if err != nil { 69 | return diag.FromErr(err) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func resourceB2BucketNotificationRulesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 76 | client := meta.(*Client) 77 | const name = "bucket_notification_rules" 78 | const op = RESOURCE_READ 79 | 80 | input := map[string]interface{}{ 81 | "bucket_id": d.Id(), 82 | } 83 | 84 | output, err := client.apply(ctx, name, op, input) 85 | if err != nil { 86 | return diag.FromErr(err) 87 | } 88 | if _, ok := output["bucket_id"]; !ok && !d.IsNewResource() { 89 | // deleted bucket, thus notification rules no longer exist 90 | tflog.Warn(ctx, "Bucket not found for Event Notifications, possible resource drift", map[string]interface{}{ 91 | "bucket_id": d.Id(), 92 | }) 93 | d.SetId("") 94 | return nil 95 | } 96 | 97 | err = client.populate(ctx, name, op, output, d) 98 | if err != nil { 99 | return diag.FromErr(err) 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func resourceB2BucketNotificationRulesUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 106 | client := meta.(*Client) 107 | const name = "bucket_notification_rules" 108 | const op = RESOURCE_UPDATE 109 | 110 | input := map[string]interface{}{ 111 | "bucket_id": d.Id(), 112 | "notification_rules": d.Get("notification_rules").([]interface{}), 113 | } 114 | 115 | output, err := client.apply(ctx, name, op, input) 116 | if err != nil { 117 | return diag.FromErr(err) 118 | } 119 | 120 | err = client.populate(ctx, name, op, output, d) 121 | if err != nil { 122 | return diag.FromErr(err) 123 | } 124 | 125 | return nil 126 | } 127 | 128 | func resourceB2BucketNotificationRulesDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 129 | client := meta.(*Client) 130 | const name = "bucket_notification_rules" 131 | const op = RESOURCE_DELETE 132 | 133 | input := map[string]interface{}{ 134 | "bucket_id": d.Id(), 135 | } 136 | 137 | _, err := client.apply(ctx, name, op, input) 138 | if err != nil { 139 | return diag.FromErr(err) 140 | } 141 | 142 | d.SetId("") 143 | 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /b2/resource_b2_bucket_notification_rules_test.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/resource_b2_bucket_notification_rules_test.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "fmt" 15 | "testing" 16 | 17 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 18 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 19 | ) 20 | 21 | func TestAccResourceB2BucketNotificationRules_basic(t *testing.T) { 22 | parentResourceName := "b2_bucket.test" 23 | resourceName := "b2_bucket_notification_rules.test" 24 | 25 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 26 | ruleName := acctest.RandomWithPrefix("test-b2-tfp") 27 | 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccPreCheck(t) }, 30 | ProviderFactories: providerFactories, 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: testAccResourceB2BucketNotificationRulesConfig_basic(bucketName, ruleName), 34 | Check: resource.ComposeTestCheckFunc( 35 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 36 | resource.TestCheckResourceAttr(resourceName, "notification_rules.#", "1"), 37 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.name", ruleName), 38 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.#", "1"), 39 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.0", "b2:ObjectCreated:*"), 40 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_enabled", "true"), 41 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.object_name_prefix", ""), 42 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.#", "1"), 43 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.target_type", "webhook"), 44 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.url", "https://example.com/webhook"), 45 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.hmac_sha256_signing_secret", ""), 46 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.#", "0"), 47 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_suspended", "false"), 48 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.suspension_reason", ""), 49 | ), 50 | }, 51 | }, 52 | }) 53 | } 54 | 55 | func TestAccResourceB2BucketNotificationRules_all(t *testing.T) { 56 | parentResourceName := "b2_bucket.test" 57 | resourceName := "b2_bucket_notification_rules.test" 58 | 59 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 60 | ruleName := acctest.RandomWithPrefix("test-b2-tfp") 61 | 62 | resource.Test(t, resource.TestCase{ 63 | PreCheck: func() { testAccPreCheck(t) }, 64 | ProviderFactories: providerFactories, 65 | Steps: []resource.TestStep{ 66 | { 67 | Config: testAccResourceB2BucketNotificationRulesConfig_all(bucketName, ruleName), 68 | Check: resource.ComposeTestCheckFunc( 69 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 70 | resource.TestCheckResourceAttr(resourceName, "notification_rules.#", "1"), 71 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.name", ruleName), 72 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.#", "2"), 73 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.0", "b2:ObjectCreated:*"), 74 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.1", "b2:ObjectDeleted:*"), 75 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_enabled", "false"), 76 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.object_name_prefix", "prefix/"), 77 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.#", "1"), 78 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.target_type", "webhook"), 79 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.url", "https://example.com/webhook"), 80 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.hmac_sha256_signing_secret", "sWhtNHMFntMPukWYacpMmJbrhsuylxTg"), 81 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.#", "2"), 82 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.0.name", "myCustomHeader1"), 83 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.0.value", "myCustomHeaderVal1"), 84 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.1.name", "myCustomHeader2"), 85 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.1.value", "myCustomHeaderVal2"), 86 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_suspended", "false"), 87 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.suspension_reason", ""), 88 | ), 89 | }, 90 | }, 91 | }) 92 | } 93 | 94 | func TestAccResourceB2BucketNotificationRules_update(t *testing.T) { 95 | parentResourceName := "b2_bucket.test" 96 | resourceName := "b2_bucket_notification_rules.test" 97 | 98 | bucketName := acctest.RandomWithPrefix("test-b2-tfp") 99 | ruleName := acctest.RandomWithPrefix("test-b2-tfp") 100 | 101 | resource.Test(t, resource.TestCase{ 102 | PreCheck: func() { testAccPreCheck(t) }, 103 | ProviderFactories: providerFactories, 104 | Steps: []resource.TestStep{ 105 | { 106 | Config: testAccResourceB2BucketNotificationRulesConfig_basic(bucketName, ruleName), 107 | }, 108 | { 109 | Config: testAccResourceB2BucketNotificationRulesConfig_all(bucketName, ruleName), 110 | Check: resource.ComposeTestCheckFunc( 111 | resource.TestCheckResourceAttrPair(resourceName, "bucket_id", parentResourceName, "bucket_id"), 112 | resource.TestCheckResourceAttr(resourceName, "notification_rules.#", "1"), 113 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.name", ruleName), 114 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.#", "2"), 115 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.0", "b2:ObjectCreated:*"), 116 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.event_types.1", "b2:ObjectDeleted:*"), 117 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_enabled", "false"), 118 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.object_name_prefix", "prefix/"), 119 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.#", "1"), 120 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.target_type", "webhook"), 121 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.url", "https://example.com/webhook"), 122 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.hmac_sha256_signing_secret", "sWhtNHMFntMPukWYacpMmJbrhsuylxTg"), 123 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.#", "2"), 124 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.0.name", "myCustomHeader1"), 125 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.0.value", "myCustomHeaderVal1"), 126 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.1.name", "myCustomHeader2"), 127 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.target_configuration.0.custom_headers.1.value", "myCustomHeaderVal2"), 128 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.is_suspended", "false"), 129 | resource.TestCheckResourceAttr(resourceName, "notification_rules.0.suspension_reason", ""), 130 | ), 131 | }, 132 | }, 133 | }) 134 | } 135 | 136 | func testAccResourceB2BucketNotificationRulesConfig_basic(bucketName string, ruleName string) string { 137 | return fmt.Sprintf(` 138 | resource "b2_bucket" "test" { 139 | bucket_name = "%s" 140 | bucket_type = "allPublic" 141 | } 142 | 143 | resource "b2_bucket_notification_rules" "test" { 144 | bucket_id = b2_bucket.test.id 145 | notification_rules { 146 | name = "%s" 147 | event_types = ["b2:ObjectCreated:*"] 148 | target_configuration { 149 | target_type = "webhook" 150 | url = "https://example.com/webhook" 151 | } 152 | } 153 | } 154 | `, bucketName, ruleName) 155 | } 156 | 157 | func testAccResourceB2BucketNotificationRulesConfig_all(bucketName string, ruleName string) string { 158 | return fmt.Sprintf(` 159 | resource "b2_bucket" "test" { 160 | bucket_name = "%s" 161 | bucket_type = "allPublic" 162 | } 163 | 164 | resource "b2_bucket_notification_rules" "test" { 165 | bucket_id = b2_bucket.test.id 166 | notification_rules { 167 | name = "%s" 168 | event_types = ["b2:ObjectCreated:*", "b2:ObjectDeleted:*"] 169 | is_enabled = false 170 | object_name_prefix = "prefix/" 171 | target_configuration { 172 | target_type = "webhook" 173 | url = "https://example.com/webhook" 174 | hmac_sha256_signing_secret = "sWhtNHMFntMPukWYacpMmJbrhsuylxTg" 175 | custom_headers { 176 | name = "myCustomHeader1" 177 | value = "myCustomHeaderVal1" 178 | } 179 | custom_headers { # optional 180 | name = "myCustomHeader2" 181 | value = "myCustomHeaderVal2" 182 | } 183 | } 184 | } 185 | } 186 | `, bucketName, ruleName) 187 | } 188 | -------------------------------------------------------------------------------- /b2/utils.go: -------------------------------------------------------------------------------- 1 | // #################################################################### 2 | // 3 | // File: b2/utils.go 4 | // 5 | // Copyright 2024 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | // #################################################################### 10 | 11 | package b2 12 | 13 | func If[T any](cond bool, vtrue, vfalse T) T { 14 | if cond { 15 | return vtrue 16 | } 17 | return vfalse 18 | } 19 | -------------------------------------------------------------------------------- /b2/validators.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: b2/validators.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package b2 12 | 13 | import ( 14 | "encoding/base64" 15 | "fmt" 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 17 | ) 18 | 19 | func validateBase64Key(i interface{}, k string) (warnings []string, errors []error) { 20 | v, ok := i.(string) 21 | if ok { 22 | decoded, err := base64.StdEncoding.DecodeString(v) 23 | if err == nil { 24 | // AES256 (which is the only supported algorithm for now) key should be 256 bits (32 bytes) 25 | if len(decoded) != 32 { 26 | errors = append(errors, fmt.Errorf("AES256 key should be 32 bytes, got %d bytes instead", 27 | len(decoded))) 28 | } 29 | } else { 30 | errors = append(errors, err) 31 | } 32 | } else { 33 | errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) 34 | } 35 | 36 | return warnings, errors 37 | } 38 | 39 | // StringLenExact returns a SchemaValidateFunc which tests if the provided value 40 | // is of type string and has given length 41 | func StringLenExact(length int) schema.SchemaValidateFunc { 42 | return func(i interface{}, k string) (warnings []string, errors []error) { 43 | v, ok := i.(string) 44 | if !ok { 45 | errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) 46 | return warnings, errors 47 | } 48 | 49 | if len(v) != length { 50 | errors = append(errors, fmt.Errorf("expected length of %s must be %d, got %s", k, length, v)) 51 | } 52 | 53 | return warnings, errors 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/data-sources/account_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_account_info Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 account info data source. 7 | --- 8 | 9 | # b2_account_info (Data Source) 10 | 11 | B2 account info data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Read-Only 19 | 20 | - `account_auth_token` (String, Sensitive) An authorization token to use with all calls, other than b2_authorize_account, that need an Authorization header. This authorization token is valid for at most 24 hours. 21 | - `account_id` (String) The identifier for the account. 22 | - `allowed` (List of Object) An object containing the capabilities of this auth token, and any restrictions on using it. (see [below for nested schema](#nestedatt--allowed)) 23 | - `api_url` (String) The base URL to use for all API calls except for uploading and downloading files. 24 | - `download_url` (String) The base URL to use for downloading files. 25 | - `id` (String) The ID of this resource. 26 | - `s3_api_url` (String) The base URL to use for S3-compatible API calls. 27 | 28 | 29 | ### Nested Schema for `allowed` 30 | 31 | Read-Only: 32 | 33 | - `bucket_id` (String) 34 | - `bucket_name` (String) 35 | - `capabilities` (Set of String) 36 | - `name_prefix` (String) 37 | -------------------------------------------------------------------------------- /docs/data-sources/application_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_application_key Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 application key data source. 7 | --- 8 | 9 | # b2_application_key (Data Source) 10 | 11 | B2 application key data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `key_name` (String) The name assigned when the key was created. 21 | 22 | ### Optional 23 | 24 | - `name_prefix` (String) When present, restricts access to files whose names start with the prefix. 25 | 26 | ### Read-Only 27 | 28 | - `application_key_id` (String) The ID of the key. 29 | - `bucket_id` (String) When present, restricts access to one bucket. 30 | - `capabilities` (Set of String) A set of strings, each one naming a capability the key has. 31 | - `id` (String) The ID of this resource. 32 | - `options` (Set of String) A list of application key options. 33 | -------------------------------------------------------------------------------- /docs/data-sources/bucket.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket data source. 7 | --- 8 | 9 | # b2_bucket (Data Source) 10 | 11 | B2 bucket data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_name` (String) The name of the bucket. 21 | 22 | ### Read-Only 23 | 24 | - `account_id` (String) Account ID that the bucket belongs to. 25 | - `bucket_id` (String) The ID of the bucket. 26 | - `bucket_info` (Map of String) User-defined information to be stored with the bucket. 27 | - `bucket_type` (String) The bucket type. Either 'allPublic', meaning that files in this bucket can be downloaded by anybody, or 'allPrivate'. 28 | - `cors_rules` (List of Object) The initial list of CORS rules for this bucket. (see [below for nested schema](#nestedatt--cors_rules)) 29 | - `default_server_side_encryption` (List of Object) The default server-side encryption settings of this bucket. (see [below for nested schema](#nestedatt--default_server_side_encryption)) 30 | - `file_lock_configuration` (List of Object) The default File Lock retention settings for this bucket. (see [below for nested schema](#nestedatt--file_lock_configuration)) 31 | - `id` (String) The ID of this resource. 32 | - `lifecycle_rules` (List of Object) The initial list of lifecycle rules for this bucket. (see [below for nested schema](#nestedatt--lifecycle_rules)) 33 | - `options` (Set of String) List of bucket options. 34 | - `revision` (Number) Bucket revision. 35 | 36 | 37 | ### Nested Schema for `cors_rules` 38 | 39 | Read-Only: 40 | 41 | - `allowed_headers` (List of String) 42 | - `allowed_operations` (List of String) 43 | - `allowed_origins` (List of String) 44 | - `cors_rule_name` (String) 45 | - `expose_headers` (List of String) 46 | - `max_age_seconds` (Number) 47 | 48 | 49 | 50 | ### Nested Schema for `default_server_side_encryption` 51 | 52 | Read-Only: 53 | 54 | - `algorithm` (String) 55 | - `mode` (String) 56 | 57 | 58 | 59 | ### Nested Schema for `file_lock_configuration` 60 | 61 | Read-Only: 62 | 63 | - `default_retention` (List of Object) (see [below for nested schema](#nestedobjatt--file_lock_configuration--default_retention)) 64 | - `is_file_lock_enabled` (Boolean) 65 | 66 | 67 | ### Nested Schema for `file_lock_configuration.default_retention` 68 | 69 | Read-Only: 70 | 71 | - `mode` (String) 72 | - `period` (List of Object) (see [below for nested schema](#nestedobjatt--file_lock_configuration--default_retention--period)) 73 | 74 | 75 | ### Nested Schema for `file_lock_configuration.default_retention.period` 76 | 77 | Read-Only: 78 | 79 | - `duration` (Number) 80 | - `unit` (String) 81 | 82 | 83 | 84 | 85 | 86 | ### Nested Schema for `lifecycle_rules` 87 | 88 | Read-Only: 89 | 90 | - `days_from_hiding_to_deleting` (Number) 91 | - `days_from_uploading_to_hiding` (Number) 92 | - `file_name_prefix` (String) 93 | -------------------------------------------------------------------------------- /docs/data-sources/bucket_file.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_file Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket file data source. 7 | --- 8 | 9 | # b2_bucket_file (Data Source) 10 | 11 | B2 bucket file data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. 21 | - `file_name` (String) The file name. 22 | 23 | ### Optional 24 | 25 | - `show_versions` (Boolean) Show all file versions. 26 | 27 | ### Read-Only 28 | 29 | - `file_versions` (List of Object) File versions. (see [below for nested schema](#nestedatt--file_versions)) 30 | - `id` (String) The ID of this resource. 31 | 32 | 33 | ### Nested Schema for `file_versions` 34 | 35 | Read-Only: 36 | 37 | - `action` (String) 38 | - `bucket_id` (String) 39 | - `content_md5` (String) 40 | - `content_sha1` (String) 41 | - `content_type` (String) 42 | - `file_id` (String) 43 | - `file_info` (Map of String) 44 | - `file_name` (String) 45 | - `server_side_encryption` (List of Object) (see [below for nested schema](#nestedobjatt--file_versions--server_side_encryption)) 46 | - `size` (Number) 47 | - `upload_timestamp` (Number) 48 | 49 | 50 | ### Nested Schema for `file_versions.server_side_encryption` 51 | 52 | Read-Only: 53 | 54 | - `algorithm` (String) 55 | - `mode` (String) 56 | -------------------------------------------------------------------------------- /docs/data-sources/bucket_file_signed_url.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_file_signed_url Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 signed URL for a bucket file data source. 7 | --- 8 | 9 | # b2_bucket_file_signed_url (Data Source) 10 | 11 | B2 signed URL for a bucket file data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. 21 | - `file_name` (String) The file name. 22 | 23 | ### Optional 24 | 25 | - `duration` (Number) The duration for which the presigned URL is valid. 26 | 27 | ### Read-Only 28 | 29 | - `id` (String) The ID of this resource. 30 | - `signed_url` (String) The signed URL for the given file. 31 | -------------------------------------------------------------------------------- /docs/data-sources/bucket_files.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_files Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket files data source. 7 | --- 8 | 9 | # b2_bucket_files (Data Source) 10 | 11 | B2 bucket files data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. 21 | 22 | ### Optional 23 | 24 | - `folder_name` (String) The folder name (B2 file name prefix). 25 | - `recursive` (Boolean) Recursive mode. 26 | - `show_versions` (Boolean) Show all file versions. 27 | 28 | ### Read-Only 29 | 30 | - `file_versions` (List of Object) File versions in the folder. (see [below for nested schema](#nestedatt--file_versions)) 31 | - `id` (String) The ID of this resource. 32 | 33 | 34 | ### Nested Schema for `file_versions` 35 | 36 | Read-Only: 37 | 38 | - `action` (String) 39 | - `bucket_id` (String) 40 | - `content_md5` (String) 41 | - `content_sha1` (String) 42 | - `content_type` (String) 43 | - `file_id` (String) 44 | - `file_info` (Map of String) 45 | - `file_name` (String) 46 | - `server_side_encryption` (List of Object) (see [below for nested schema](#nestedobjatt--file_versions--server_side_encryption)) 47 | - `size` (Number) 48 | - `upload_timestamp` (Number) 49 | 50 | 51 | ### Nested Schema for `file_versions.server_side_encryption` 52 | 53 | Read-Only: 54 | 55 | - `algorithm` (String) 56 | - `mode` (String) 57 | -------------------------------------------------------------------------------- /docs/data-sources/bucket_notification_rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_notification_rules Data Source - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket notification rules data source. 7 | --- 8 | 9 | # b2_bucket_notification_rules (Data Source) 10 | 11 | B2 bucket notification rules data source. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. 21 | 22 | ### Read-Only 23 | 24 | - `id` (String) The ID of this resource. 25 | - `notification_rules` (List of Object) An array of Event Notification Rules. (see [below for nested schema](#nestedatt--notification_rules)) 26 | 27 | 28 | ### Nested Schema for `notification_rules` 29 | 30 | Read-Only: 31 | 32 | - `event_types` (List of String) 33 | - `is_enabled` (Boolean) 34 | - `is_suspended` (Boolean) 35 | - `name` (String) 36 | - `object_name_prefix` (String) 37 | - `suspension_reason` (String) 38 | - `target_configuration` (List of Object) (see [below for nested schema](#nestedobjatt--notification_rules--target_configuration)) 39 | 40 | 41 | ### Nested Schema for `notification_rules.target_configuration` 42 | 43 | Read-Only: 44 | 45 | - `custom_headers` (List of Object) (see [below for nested schema](#nestedobjatt--notification_rules--target_configuration--custom_headers)) 46 | - `hmac_sha256_signing_secret` (String) 47 | - `target_type` (String) 48 | - `url` (String) 49 | 50 | 51 | ### Nested Schema for `notification_rules.target_configuration.custom_headers` 52 | 53 | Read-Only: 54 | 55 | - `name` (String) 56 | - `value` (String) 57 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "B2 Provider" 3 | description: |- 4 | 5 | --- 6 | # B2 Provider 7 | 8 | Terraform provider for [Backblaze B2](https://www.backblaze.com/b2/). 9 | 10 | The provider is written in go, but it uses official [B2 python SDK](https://github.com/Backblaze/b2-sdk-python) embedded into the binary. 11 | 12 | ## Example Usage 13 | ```terraform 14 | terraform { 15 | required_version = ">= 1.0.0" 16 | required_providers { 17 | b2 = { 18 | source = "Backblaze/b2" 19 | } 20 | } 21 | } 22 | 23 | provider "b2" { 24 | } 25 | 26 | resource "b2_application_key" "example_key" { 27 | key_name = "my-key" 28 | capabilities = ["readFiles"] 29 | } 30 | 31 | resource "b2_bucket" "example_bucket" { 32 | bucket_name = "my-b2-bucket" 33 | bucket_type = "allPublic" 34 | } 35 | ``` 36 | 37 | 38 | ## Schema 39 | 40 | ### Optional 41 | 42 | - `application_key` (String, Sensitive) B2 Application Key (B2_APPLICATION_KEY env). 43 | - `application_key_id` (String, Sensitive) B2 Application Key ID (B2_APPLICATION_KEY_ID env). 44 | - `endpoint` (String) B2 endpoint - the string 'production' or a custom B2 API URL (B2_ENDPOINT env). You should not need to set this unless you work at Backblaze. Defaults to `production`. 45 | -------------------------------------------------------------------------------- /docs/resources/application_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_application_key Resource - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 application key resource. 7 | --- 8 | 9 | # b2_application_key (Resource) 10 | 11 | B2 application key resource. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `capabilities` (Set of String) A set of strings, each one naming a capability the key has. **Modifying this attribute will force creation of a new resource.** 21 | - `key_name` (String) The name of the key. **Modifying this attribute will force creation of a new resource.** 22 | 23 | ### Optional 24 | 25 | - `bucket_id` (String) When present, restricts access to one bucket. **Modifying this attribute will force creation of a new resource.** 26 | - `name_prefix` (String) When present, restricts access to files whose names start with the prefix. Required when using `bucket_id`. **Modifying this attribute will force creation of a new resource.** 27 | 28 | ### Read-Only 29 | 30 | - `application_key` (String, Sensitive) The key. 31 | - `application_key_id` (String) The ID of the newly created key. 32 | - `id` (String) The ID of this resource. 33 | - `options` (Set of String) List of application key options. 34 | -------------------------------------------------------------------------------- /docs/resources/bucket.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket Resource - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket resource. 7 | --- 8 | 9 | # b2_bucket (Resource) 10 | 11 | B2 bucket resource. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_name` (String) The name of the bucket. **Modifying this attribute will force creation of a new resource.** 21 | - `bucket_type` (String) The bucket type. Either 'allPublic', meaning that files in this bucket can be downloaded by anybody, or 'allPrivate'. 22 | 23 | ### Optional 24 | 25 | - `bucket_info` (Map of String) User-defined information to be stored with the bucket. 26 | - `cors_rules` (Block List) The initial list of CORS rules for this bucket. (see [below for nested schema](#nestedblock--cors_rules)) 27 | - `default_server_side_encryption` (Block List, Max: 1) The default server-side encryption settings for this bucket. (see [below for nested schema](#nestedblock--default_server_side_encryption)) 28 | - `file_lock_configuration` (Block List) File lock enabled flag, and default retention settings. (see [below for nested schema](#nestedblock--file_lock_configuration)) 29 | - `lifecycle_rules` (Block List) The initial list of lifecycle rules for this bucket. (see [below for nested schema](#nestedblock--lifecycle_rules)) 30 | 31 | ### Read-Only 32 | 33 | - `account_id` (String) Account ID that the bucket belongs to. 34 | - `bucket_id` (String) The ID of the bucket. 35 | - `id` (String) The ID of this resource. 36 | - `options` (Set of String) List of bucket options. 37 | - `revision` (Number) Bucket revision. 38 | 39 | 40 | ### Nested Schema for `cors_rules` 41 | 42 | Required: 43 | 44 | - `allowed_operations` (List of String) A list specifying which operations the rule allows. 45 | - `allowed_origins` (List of String) A non-empty list specifying which origins the rule covers. 46 | - `cors_rule_name` (String) A name for humans to recognize the rule in a user interface. 47 | - `max_age_seconds` (Number) This specifies the maximum number of seconds that a browser may cache the response to a preflight request. 48 | 49 | Optional: 50 | 51 | - `allowed_headers` (List of String) If present, this is a list of headers that are allowed in a pre-flight OPTIONS's request's Access-Control-Request-Headers header value. 52 | - `expose_headers` (List of String) If present, this is a list of headers that may be exposed to an application inside the client. 53 | 54 | 55 | 56 | ### Nested Schema for `default_server_side_encryption` 57 | 58 | Optional: 59 | 60 | - `algorithm` (String) Server-side encryption algorithm. AES256 is the only one supported. 61 | - `mode` (String) Server-side encryption mode. 62 | 63 | 64 | 65 | ### Nested Schema for `file_lock_configuration` 66 | 67 | Optional: 68 | 69 | - `default_retention` (Block List, Max: 1) Default retention settings for files uploaded to this bucket. (see [below for nested schema](#nestedblock--file_lock_configuration--default_retention)) 70 | - `is_file_lock_enabled` (Boolean) If present, the boolean value specifies whether bucket is File Lock-enabled. Defaults to `false`. **Modifying this attribute will force creation of a new resource.** 71 | 72 | 73 | ### Nested Schema for `file_lock_configuration.default_retention` 74 | 75 | Required: 76 | 77 | - `mode` (String) Default retention mode (compliance|governance|none). 78 | 79 | Optional: 80 | 81 | - `period` (Block List, Max: 1) How long for to make files immutable. (see [below for nested schema](#nestedblock--file_lock_configuration--default_retention--period)) 82 | 83 | 84 | ### Nested Schema for `file_lock_configuration.default_retention.period` 85 | 86 | Required: 87 | 88 | - `duration` (Number) Duration. 89 | - `unit` (String) Unit for duration (days|years). 90 | 91 | 92 | 93 | 94 | 95 | ### Nested Schema for `lifecycle_rules` 96 | 97 | Required: 98 | 99 | - `file_name_prefix` (String) It specifies which files in the bucket it applies to. 100 | 101 | Optional: 102 | 103 | - `days_from_hiding_to_deleting` (Number) It says how long to keep file versions that are not the current version. 104 | - `days_from_uploading_to_hiding` (Number) It causes files to be hidden automatically after the given number of days. 105 | -------------------------------------------------------------------------------- /docs/resources/bucket_file_version.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_file_version Resource - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket file version resource. 7 | --- 8 | 9 | # b2_bucket_file_version (Resource) 10 | 11 | B2 bucket file version resource. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. **Modifying this attribute will force creation of a new resource.** 21 | - `file_name` (String) The name of the B2 file. **Modifying this attribute will force creation of a new resource.** 22 | - `source` (String) Path to the local file. **Modifying this attribute will force creation of a new resource.** 23 | 24 | ### Optional 25 | 26 | - `content_type` (String) Content type. If not set, it will be set based on the file extension. **Modifying this attribute will force creation of a new resource.** 27 | - `file_info` (Map of String) The custom information that is uploaded with the file. **Modifying this attribute will force creation of a new resource.** 28 | - `server_side_encryption` (Block List, Max: 1) Server-side encryption settings. **Modifying this attribute will force creation of a new resource.** (see [below for nested schema](#nestedblock--server_side_encryption)) 29 | 30 | ### Read-Only 31 | 32 | - `action` (String) One of 'start', 'upload', 'hide', 'folder', or other values added in the future. 33 | - `content_md5` (String) MD5 sum of the content. 34 | - `content_sha1` (String) SHA1 hash of the content. 35 | - `file_id` (String) The unique identifier for this version of this file. 36 | - `id` (String) The ID of this resource. 37 | - `size` (Number) The file size. 38 | - `upload_timestamp` (Number) This is a UTC time when this file was uploaded. 39 | 40 | 41 | ### Nested Schema for `server_side_encryption` 42 | 43 | Optional: 44 | 45 | - `algorithm` (String) Server-side encryption algorithm. AES256 is the only one supported. 46 | - `key` (Block List, Max: 1) Key used in SSE-C mode. (see [below for nested schema](#nestedblock--server_side_encryption--key)) 47 | - `mode` (String) Server-side encryption mode. 48 | 49 | 50 | ### Nested Schema for `server_side_encryption.key` 51 | 52 | Optional: 53 | 54 | - `key_id` (String) Key identifier stored in file info metadata. 55 | - `secret_b64` (String, Sensitive) Secret key value, in standard Base 64 encoding (RFC 4648). 56 | -------------------------------------------------------------------------------- /docs/resources/bucket_notification_rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "b2_bucket_notification_rules Resource - terraform-provider-b2" 4 | subcategory: "" 5 | description: |- 6 | B2 bucket notification rules resource. 7 | --- 8 | 9 | # b2_bucket_notification_rules (Resource) 10 | 11 | B2 bucket notification rules resource. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `bucket_id` (String) The ID of the bucket. **Modifying this attribute will force creation of a new resource.** 21 | - `notification_rules` (Block List, Min: 1) An array of Event Notification Rules. (see [below for nested schema](#nestedblock--notification_rules)) 22 | 23 | ### Read-Only 24 | 25 | - `id` (String) The ID of this resource. 26 | 27 | 28 | ### Nested Schema for `notification_rules` 29 | 30 | Required: 31 | 32 | - `event_types` (List of String) The list of event types for the event notification rule. 33 | - `name` (String) A name for the event notification rule. The name must be unique among the bucket's notification rules. 34 | - `target_configuration` (Block List, Min: 1, Max: 1) The target configuration for the event notification rule. (see [below for nested schema](#nestedblock--notification_rules--target_configuration)) 35 | 36 | Optional: 37 | 38 | - `is_enabled` (Boolean) Whether the event notification rule is enabled. Defaults to `true`. 39 | - `object_name_prefix` (String) Specifies which object(s) in the bucket the event notification rule applies to. 40 | 41 | Read-Only: 42 | 43 | - `is_suspended` (Boolean) Whether the event notification rule is suspended. 44 | - `suspension_reason` (String) A brief description of why the event notification rule was suspended. 45 | 46 | 47 | ### Nested Schema for `notification_rules.target_configuration` 48 | 49 | Required: 50 | 51 | - `target_type` (String) The type of the target configuration, currently "webhook" only. 52 | - `url` (String) The URL for the webhook. 53 | 54 | Optional: 55 | 56 | - `custom_headers` (Block List, Max: 10) When present, additional header name/value pairs to be sent on the webhook invocation. (see [below for nested schema](#nestedblock--notification_rules--target_configuration--custom_headers)) 57 | - `hmac_sha256_signing_secret` (String, Sensitive) The signing secret for use in verifying the X-Bz-Event-Notification-Signature. 58 | 59 | 60 | ### Nested Schema for `notification_rules.target_configuration.custom_headers` 61 | 62 | Required: 63 | 64 | - `name` (String) Name of the header. 65 | - `value` (String) Value of the header. 66 | -------------------------------------------------------------------------------- /examples/application_key/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | b2 = { 5 | source = "Backblaze/b2" 6 | } 7 | } 8 | } 9 | 10 | provider "b2" { 11 | } 12 | 13 | resource "b2_application_key" "example" { 14 | key_name = "test-b2-tfp-0000000000000000000" 15 | capabilities = ["readFiles"] 16 | } 17 | 18 | data "b2_application_key" "example" { 19 | key_name = b2_application_key.example.key_name 20 | } 21 | 22 | output "application_key" { 23 | value = data.b2_application_key.example 24 | } 25 | -------------------------------------------------------------------------------- /examples/bucket/example.txt: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /examples/bucket/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | b2 = { 5 | source = "Backblaze/b2" 6 | } 7 | } 8 | } 9 | 10 | provider "b2" { 11 | } 12 | 13 | resource "b2_bucket" "example" { 14 | bucket_name = "test-b2-tfp-0000000000000000000" 15 | bucket_type = "allPublic" 16 | } 17 | 18 | resource "b2_bucket_file_version" "example1" { 19 | bucket_id = b2_bucket.example.id 20 | file_name = "example.txt" 21 | source = "example.txt" 22 | } 23 | 24 | resource "b2_bucket_file_version" "example2" { 25 | bucket_id = b2_bucket_file_version.example1.bucket_id 26 | file_name = b2_bucket_file_version.example1.file_name 27 | source = b2_bucket_file_version.example1.source 28 | file_info = { 29 | description = "second version" 30 | } 31 | } 32 | 33 | resource "b2_bucket_file_version" "example3" { 34 | bucket_id = b2_bucket_file_version.example2.bucket_id 35 | file_name = "dir/example.txt" 36 | source = b2_bucket_file_version.example2.source 37 | server_side_encryption { 38 | mode = "SSE-B2" 39 | algorithm = "AES256" 40 | } 41 | } 42 | 43 | data "b2_bucket" "example" { 44 | bucket_name = b2_bucket.example.bucket_name 45 | depends_on = [ 46 | b2_bucket.example, 47 | ] 48 | } 49 | 50 | data "b2_bucket_file" "example" { 51 | bucket_id = b2_bucket_file_version.example2.bucket_id 52 | file_name = b2_bucket_file_version.example2.file_name 53 | show_versions = true 54 | depends_on = [ 55 | b2_bucket_file_version.example1, 56 | b2_bucket_file_version.example2, 57 | b2_bucket_file_version.example3, 58 | ] 59 | } 60 | 61 | data "b2_bucket_files" "example" { 62 | bucket_id = b2_bucket_file_version.example3.bucket_id 63 | depends_on = [ 64 | b2_bucket_file_version.example1, 65 | b2_bucket_file_version.example2, 66 | b2_bucket_file_version.example3, 67 | ] 68 | } 69 | 70 | output "bucket_example" { 71 | value = data.b2_bucket.example 72 | } 73 | 74 | output "bucket_file_example" { 75 | value = data.b2_bucket_file.example 76 | } 77 | 78 | output "bucket_files_example" { 79 | value = data.b2_bucket_files.example 80 | } 81 | -------------------------------------------------------------------------------- /examples/bucket_file_lock/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | b2 = { 5 | source = "Backblaze/b2" 6 | } 7 | } 8 | } 9 | 10 | provider "b2" { 11 | 12 | } 13 | 14 | resource "b2_bucket" "example" { 15 | bucket_name = "test-b2-lock" 16 | bucket_type = "allPublic" 17 | file_lock_configuration { 18 | is_file_lock_enabled = true 19 | default_retention { 20 | mode = "governance" 21 | period { 22 | duration = 7 23 | unit = "days" 24 | } 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /examples/bucket_notification_rules/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | b2 = { 5 | source = "Backblaze/b2" 6 | } 7 | } 8 | } 9 | 10 | provider "b2" { 11 | } 12 | 13 | resource "b2_bucket" "example" { 14 | bucket_name = "test-b2-tfp-0000000000000000000" 15 | bucket_type = "allPublic" 16 | } 17 | 18 | resource "b2_bucket_notification_rules" "example" { 19 | bucket_id = b2_bucket.example.id 20 | notification_rules { 21 | name = "${b2_bucket.example.bucket_name}-rule-1" 22 | event_types = [ 23 | "b2:ObjectCreated:*", 24 | "b2:ObjectDeleted:*", 25 | ] 26 | target_configuration { 27 | target_type = "webhook" 28 | url = "https://example.com/webhook" 29 | custom_headers { 30 | name = "myCustomHeader1" 31 | value = "myCustomHeaderVal1" 32 | } 33 | } 34 | } 35 | } 36 | 37 | data "b2_bucket" "example" { 38 | bucket_name = b2_bucket.example.bucket_name 39 | depends_on = [ 40 | b2_bucket.example, 41 | ] 42 | } 43 | 44 | data "b2_bucket_notification_rules" "example" { 45 | bucket_id = b2_bucket.example.bucket_id 46 | depends_on = [ 47 | b2_bucket_notification_rules.example, 48 | ] 49 | } 50 | 51 | output "bucket_example" { 52 | value = data.b2_bucket.example 53 | } 54 | 55 | output "bucket_notification_rules_example" { 56 | value = data.b2_bucket_notification_rules.example 57 | } 58 | -------------------------------------------------------------------------------- /examples/file_version_sse_c/main.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "encryption_key" { 3 | # value will be taken from env variable 'TF_VAR_encryption_key' 4 | description = "Encryption key" 5 | type = string 6 | sensitive = true 7 | } 8 | 9 | resource "b2_bucket" "test" { 10 | bucket_name = "!bucketname!" 11 | bucket_type = "allPublic" 12 | } 13 | 14 | resource "b2_bucket_file_version" "test" { 15 | bucket_id = b2_bucket.test.id 16 | file_name = "temp.bin" 17 | source = "!filename!" 18 | content_type = "octet/stream" 19 | file_info = { 20 | description = "the file" 21 | } 22 | server_side_encryption { 23 | mode = "SSE-C" 24 | algorithm = "AES256" 25 | key { 26 | secret_b64 = var.encryption_key 27 | key_id = "Identifier that will let client tools know which key to provide" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | b2 = { 5 | source = "Backblaze/b2" 6 | } 7 | } 8 | } 9 | 10 | provider "b2" { 11 | } 12 | 13 | resource "b2_application_key" "example_key" { 14 | key_name = "my-key" 15 | capabilities = ["readFiles"] 16 | } 17 | 18 | resource "b2_bucket" "example_bucket" { 19 | bucket_name = "my-b2-bucket" 20 | bucket_type = "allPublic" 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Backblaze/terraform-provider-b2 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require github.com/hashicorp/terraform-plugin-log v0.9.0 8 | 9 | require github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 10 | 11 | require ( 12 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 13 | github.com/agext/levenshtein v1.2.2 // indirect 14 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 15 | github.com/cloudflare/circl v1.3.7 // indirect 16 | github.com/fatih/color v1.16.0 // indirect 17 | github.com/golang/protobuf v1.5.4 // indirect 18 | github.com/google/go-cmp v0.6.0 // indirect 19 | github.com/hashicorp/errwrap v1.0.0 // indirect 20 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 21 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 22 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect 23 | github.com/hashicorp/go-hclog v1.6.3 // indirect 24 | github.com/hashicorp/go-multierror v1.1.1 // indirect 25 | github.com/hashicorp/go-plugin v1.6.2 // indirect 26 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 27 | github.com/hashicorp/go-uuid v1.0.3 // indirect 28 | github.com/hashicorp/go-version v1.7.0 // indirect 29 | github.com/hashicorp/hc-install v0.9.1 // indirect 30 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 31 | github.com/hashicorp/logutils v1.0.0 // indirect 32 | github.com/hashicorp/terraform-exec v0.22.0 // indirect 33 | github.com/hashicorp/terraform-json v0.24.0 // indirect 34 | github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect 35 | github.com/hashicorp/terraform-registry-address v0.2.4 // indirect 36 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 37 | github.com/hashicorp/yamux v0.1.1 // indirect 38 | github.com/mattn/go-colorable v0.1.13 // indirect 39 | github.com/mattn/go-isatty v0.0.20 // indirect 40 | github.com/mitchellh/copystructure v1.2.0 // indirect 41 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 42 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 43 | github.com/mitchellh/mapstructure v1.5.0 // indirect 44 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 45 | github.com/oklog/run v1.0.0 // indirect 46 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 47 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 48 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 49 | github.com/zclconf/go-cty v1.16.2 // indirect 50 | golang.org/x/crypto v0.36.0 // indirect 51 | golang.org/x/mod v0.22.0 // indirect 52 | golang.org/x/net v0.38.0 // indirect 53 | golang.org/x/sync v0.12.0 // indirect 54 | golang.org/x/sys v0.31.0 // indirect 55 | golang.org/x/text v0.23.0 // indirect 56 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 57 | google.golang.org/appengine v1.6.8 // indirect 58 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect 59 | google.golang.org/grpc v1.69.4 // indirect 60 | google.golang.org/protobuf v1.36.3 // indirect 61 | ) 62 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: main.go 4 | // 5 | // Copyright 2020 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | package main 12 | 13 | import ( 14 | "flag" 15 | "log" 16 | "os" 17 | 18 | "github.com/Backblaze/terraform-provider-b2/b2" 19 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 20 | ) 21 | 22 | var ( 23 | // these will be set by the goreleaser configuration 24 | // to appropriate values for the compiled binary 25 | version string = "dev" 26 | ) 27 | 28 | func main() { 29 | var debugMode bool 30 | 31 | flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") 32 | flag.Parse() 33 | 34 | pybindings, err := b2.GetBindings() 35 | if err != nil { 36 | log.Fatal(err.Error()) 37 | return 38 | } 39 | defer os.Remove(pybindings) 40 | 41 | opts := &plugin.ServeOpts{ProviderFunc: b2.New(version, pybindings), Debug: debugMode} 42 | plugin.Serve(opts) 43 | } 44 | -------------------------------------------------------------------------------- /python-bindings/GNUmakefile: -------------------------------------------------------------------------------- 1 | NAME=py-terraform-provider-b2 2 | DIR=b2_terraform 3 | EGG_INFO=${NAME}.egg-info 4 | SPEC=${NAME}.spec 5 | OS=$(shell python -c "import platform; print(platform.system())") 6 | STATICX?=1 7 | 8 | default: build 9 | 10 | .PHONY: deps format lint testacc clean build 11 | 12 | deps: 13 | @pip install -r requirements-dev.txt 14 | 15 | format: 16 | @black -Sl 100 ${DIR} 17 | 18 | lint: 19 | @black --check -Sl 100 ${DIR} 20 | @flake8 --ignore=E501,W503 ${DIR} 21 | @python ../scripts/check-headers.py '**/*.py' 22 | 23 | testacc: build 24 | 25 | clean: 26 | @rm -rf build dist ${EGG_INFO} 27 | 28 | build: 29 | @pyinstaller ${SPEC} 30 | ifeq ($(OS)$(STATICX), Linux1) 31 | @mv -f dist/py-terraform-provider-b2 dist/py-terraform-provider-b2-linked 32 | @staticx --strip --loglevel INFO dist/py-terraform-provider-b2-linked dist/py-terraform-provider-b2 33 | @rm -f dist/py-terraform-provider-b2-linked 34 | endif 35 | ifeq ($(OS), Windows) 36 | @mv -f dist/py-terraform-provider-b2.exe dist/py-terraform-provider-b2 37 | endif 38 | ifneq ($(origin CI), undefined) 39 | @echo "version=$(subst refs/tags/v,,${GITHUB_REF})" > "${GITHUB_OUTPUT}" 40 | endif 41 | 42 | install: build 43 | 44 | docs: build 45 | 46 | docs-lint: build 47 | 48 | all: deps lint build 49 | -------------------------------------------------------------------------------- /python-bindings/b2_terraform/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: python-bindings/b2_terraform/__init__.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | -------------------------------------------------------------------------------- /python-bindings/b2_terraform/arg_parser.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: python-bindings/b2_terraform/arg_parser.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | import argparse 12 | 13 | 14 | class ArgumentParser(argparse.ArgumentParser): 15 | def error(self, message): 16 | raise RuntimeError(message) 17 | -------------------------------------------------------------------------------- /python-bindings/b2_terraform/json_encoder.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: python-bindings/b2_terraform/json_encoder.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | import json 12 | 13 | 14 | class B2ProviderJsonEncoder(json.JSONEncoder): 15 | """ 16 | Makes it possible to serialize sets to json. 17 | """ 18 | 19 | def default(self, obj): 20 | if isinstance(obj, set): 21 | return list(obj) 22 | return super(B2ProviderJsonEncoder, self).default(obj) 23 | -------------------------------------------------------------------------------- /python-bindings/b2_terraform/terraform_structures.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: python-bindings/b2_terraform/terraform_structures.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | """ 12 | Store information about which data keys returned by B2 SDK should be passed on to terraform provider. 13 | Prevents error in terraform provider when API is extended with new fields. 14 | """ 15 | 16 | API_KEY_KEYS = { 17 | "application_key": None, 18 | "application_key_id": None, 19 | "bucket_id": "", 20 | "capabilities": None, 21 | "name_prefix": "", 22 | "options": None, 23 | "key_name": None, 24 | } 25 | 26 | BUCKET_SERVER_SIDE_ENCRYPTION = { 27 | "mode": None, 28 | "algorithm": None, 29 | } 30 | 31 | FILE_VERSION_SERVER_SIDE_ENCRYPTION = { 32 | "mode": None, 33 | "algorithm": None, 34 | # 'key' is not here as API does not return it 35 | } 36 | 37 | FILE_VERSION_KEYS = { 38 | "action": None, 39 | "content_md5": None, 40 | "content_sha1": None, 41 | "content_type": None, 42 | "bucket_id": None, 43 | "file_id": None, 44 | "file_info": None, 45 | "file_name": None, 46 | "size": None, 47 | "source": None, 48 | "server_side_encryption": FILE_VERSION_SERVER_SIDE_ENCRYPTION, 49 | "upload_timestamp": None, 50 | } 51 | 52 | FILE_KEYS = { 53 | "bucket_id": None, 54 | "file_name": None, 55 | "show_versions": None, 56 | "file_versions": FILE_VERSION_KEYS, 57 | } 58 | 59 | FILE_SIGNED_URL_KEYS = { 60 | "bucket_id": None, 61 | "file_name": None, 62 | "duration": None, 63 | "signed_url": None, 64 | } 65 | 66 | FILES_KEYS = { 67 | "bucket_id": None, 68 | "folder_name": None, 69 | "show_versions": None, 70 | "recursive": None, 71 | "file_versions": FILE_VERSION_KEYS, 72 | } 73 | 74 | NOTIFICATION_RULES = { 75 | "bucket_id": None, 76 | "notification_rules": { 77 | "event_types": None, 78 | "is_enabled": None, 79 | "name": None, 80 | "object_name_prefix": None, 81 | "target_configuration": { 82 | "target_type": None, 83 | "url": None, 84 | "custom_headers": None, 85 | "hmac_sha256_signing_secret": None, 86 | }, 87 | }, 88 | "is_suspended": None, 89 | "suspension_reason": None, 90 | } 91 | 92 | BUCKET_KEYS = { 93 | "bucket_id": None, 94 | "bucket_name": None, 95 | "bucket_type": None, 96 | "bucket_info": None, 97 | "cors_rules": { 98 | "cors_rule_name": None, 99 | "allowed_origins": None, 100 | "allowed_operations": None, 101 | "max_age_seconds": None, 102 | "allowed_headers": None, 103 | "expose_headers": None, 104 | }, 105 | "file_lock_configuration": { 106 | "is_file_lock_enabled": None, 107 | "default_retention": { 108 | "mode": None, 109 | "period": { 110 | "duration": None, 111 | "unit": None, 112 | }, 113 | }, 114 | }, 115 | "default_server_side_encryption": BUCKET_SERVER_SIDE_ENCRYPTION, 116 | "lifecycle_rules": { 117 | "file_name_prefix": None, 118 | "days_from_hiding_to_deleting": None, 119 | "days_from_uploading_to_hiding": None, 120 | }, 121 | "account_id": None, 122 | "options": None, 123 | "revision": None, 124 | } 125 | -------------------------------------------------------------------------------- /python-bindings/py-terraform-provider-b2.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | block_cipher = None 4 | 5 | a = Analysis(['b2_terraform/provider_tool.py'], 6 | pathex=['.'], 7 | binaries=[], 8 | datas=[], 9 | hiddenimports=[], 10 | hookspath=[], 11 | runtime_hooks=[], 12 | excludes=[], 13 | win_no_prefer_redirects=False, 14 | win_private_assemblies=False, 15 | cipher=block_cipher, 16 | noarchive=False) 17 | 18 | pyz = PYZ(a.pure, 19 | a.zipped_data, 20 | cipher=block_cipher) 21 | 22 | exe = EXE(pyz, 23 | a.scripts, 24 | a.binaries, 25 | a.zipfiles, 26 | a.datas, 27 | [], 28 | name='py-terraform-provider-b2', 29 | debug=False, 30 | bootloader_ignore_signals=False, 31 | strip=False, 32 | upx=True, 33 | upx_exclude=[], 34 | runtime_tmpdir=None, 35 | console=True) 36 | -------------------------------------------------------------------------------- /python-bindings/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | black~=24.8.0 4 | flake8~=7.1.1 5 | pyinstaller~=6.11.1 6 | patchelf-wrapper~=1.2 ; sys_platform == 'linux' 7 | staticx~=0.14.1 ; sys_platform == 'linux' 8 | -------------------------------------------------------------------------------- /python-bindings/requirements.txt: -------------------------------------------------------------------------------- 1 | b2sdk==2.8.0 2 | phx-class-registry==3.0.5 3 | pyhumps==1.6.1 4 | -------------------------------------------------------------------------------- /scripts/check-gofmt.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: scripts/check-gofmt.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | import subprocess 12 | import sys 13 | from glob import glob 14 | 15 | 16 | try: 17 | pattern = sys.argv[1] 18 | except IndexError: 19 | print('usage: python check-gofmt.py GLOB_PATTERN') 20 | sys.exit(2) 21 | 22 | ignored = sys.argv[1:] 23 | 24 | for file in glob(pattern, recursive=True): 25 | if file in ignored: 26 | continue 27 | output = subprocess.run(['gofmt', '-l', file], capture_output=True).stdout.strip() 28 | if output: 29 | print('Go formatter needs running on the file: {}'.format(file), file=sys.stderr) 30 | sys.exit(1) 31 | -------------------------------------------------------------------------------- /scripts/check-headers.py: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # 3 | # File: scripts/check-headers.py 4 | # 5 | # Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | # 7 | # License https://www.backblaze.com/using_b2_code.html 8 | # 9 | ###################################################################### 10 | 11 | import sys 12 | from glob import glob 13 | from itertools import islice 14 | 15 | 16 | try: 17 | pattern = sys.argv[1] 18 | except IndexError: 19 | print('usage: python check-headers.py GLOB_PATTERN') 20 | sys.exit(2) 21 | 22 | ignored = sys.argv[1:] 23 | 24 | for file in glob(pattern, recursive=True): 25 | if file in ignored: 26 | continue 27 | with open(file) as fd: 28 | head = ''.join(islice(fd, 9)) 29 | if 'All Rights Reserved' not in head: 30 | print( 31 | 'Missing "All Rights Reserved" in the header in: {}'.format(file), file=sys.stderr 32 | ) 33 | sys.exit(1) 34 | if file not in head: 35 | print('Wrong file name in the header in: {}'.format(file), file=sys.stderr) 36 | sys.exit(1) 37 | -------------------------------------------------------------------------------- /templates/index.md.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "B2 Provider" 3 | description: |- 4 | {{ .Description | plainmarkdown | trimspace | prefixlines " " }} 5 | --- 6 | # B2 Provider 7 | 8 | Terraform provider for [Backblaze B2](https://www.backblaze.com/b2/). 9 | 10 | The provider is written in go, but it uses official [B2 python SDK](https://github.com/Backblaze/b2-sdk-python) embedded into the binary. 11 | 12 | ## Example Usage 13 | {{tffile "examples/provider/provider.tf"}} 14 | 15 | {{ .SchemaMarkdown | trimspace }} 16 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Backblaze/terraform-provider-b2/tools 2 | 3 | go 1.24.1 4 | 5 | require github.com/hashicorp/terraform-plugin-docs v0.21.0 6 | 7 | require ( 8 | github.com/BurntSushi/toml v1.2.1 // indirect 9 | github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect 10 | github.com/Masterminds/goutils v1.1.1 // indirect 11 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 12 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 13 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 14 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 15 | github.com/armon/go-radix v1.0.0 // indirect 16 | github.com/bgentry/speakeasy v0.1.0 // indirect 17 | github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect 18 | github.com/cloudflare/circl v1.3.7 // indirect 19 | github.com/fatih/color v1.16.0 // indirect 20 | github.com/google/uuid v1.3.0 // indirect 21 | github.com/hashicorp/cli v1.1.7 // indirect 22 | github.com/hashicorp/errwrap v1.1.0 // indirect 23 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 24 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 25 | github.com/hashicorp/go-multierror v1.1.1 // indirect 26 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 27 | github.com/hashicorp/go-uuid v1.0.3 // indirect 28 | github.com/hashicorp/go-version v1.7.0 // indirect 29 | github.com/hashicorp/hc-install v0.9.1 // indirect 30 | github.com/hashicorp/terraform-exec v0.22.0 // indirect 31 | github.com/hashicorp/terraform-json v0.24.0 // indirect 32 | github.com/huandu/xstrings v1.3.3 // indirect 33 | github.com/imdario/mergo v0.3.15 // indirect 34 | github.com/mattn/go-colorable v0.1.14 // indirect 35 | github.com/mattn/go-isatty v0.0.20 // indirect 36 | github.com/mattn/go-runewidth v0.0.9 // indirect 37 | github.com/mitchellh/copystructure v1.2.0 // indirect 38 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 39 | github.com/posener/complete v1.2.3 // indirect 40 | github.com/shopspring/decimal v1.3.1 // indirect 41 | github.com/spf13/cast v1.5.0 // indirect 42 | github.com/yuin/goldmark v1.7.7 // indirect 43 | github.com/yuin/goldmark-meta v1.1.0 // indirect 44 | github.com/zclconf/go-cty v1.16.2 // indirect 45 | go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect 46 | golang.org/x/crypto v0.35.0 // indirect 47 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect 48 | golang.org/x/mod v0.22.0 // indirect 49 | golang.org/x/sys v0.30.0 // indirect 50 | golang.org/x/text v0.22.0 // indirect 51 | gopkg.in/yaml.v2 v2.3.0 // indirect 52 | gopkg.in/yaml.v3 v3.0.1 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /tools/main.go: -------------------------------------------------------------------------------- 1 | //#################################################################### 2 | // 3 | // File: tools/main.go 4 | // 5 | // Copyright 2021 Backblaze Inc. All Rights Reserved. 6 | // 7 | // License https://www.backblaze.com/using_b2_code.html 8 | // 9 | //#################################################################### 10 | 11 | //go:build tools 12 | 13 | package tools 14 | 15 | import ( 16 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 17 | ) 18 | --------------------------------------------------------------------------------