├── .github ├── CODE_OF_CONDUCT.md ├── SUPPORT.md ├── dependabot.yml └── workflows │ ├── release.yml │ └── terraform-integration-tests.yml ├── .gitignore ├── .go-version ├── .goreleaser.yml ├── CHANGELOG.md ├── GNUmakefile ├── LICENSE ├── README.md ├── cloudscale ├── config.go ├── datasource_cloudscale_custom_image.go ├── datasource_cloudscale_custom_image_test.go ├── datasource_cloudscale_floating_ip.go ├── datasource_cloudscale_floating_ip_test.go ├── datasource_cloudscale_load_balancer.go ├── datasource_cloudscale_load_balancer_health_monitor.go ├── datasource_cloudscale_load_balancer_health_monitor_test.go ├── datasource_cloudscale_load_balancer_listener.go ├── datasource_cloudscale_load_balancer_listener_test.go ├── datasource_cloudscale_load_balancer_pool.go ├── datasource_cloudscale_load_balancer_pool_member.go ├── datasource_cloudscale_load_balancer_pool_member_test.go ├── datasource_cloudscale_load_balancer_pool_test.go ├── datasource_cloudscale_load_balancer_test.go ├── datasource_cloudscale_network.go ├── datasource_cloudscale_network_test.go ├── datasource_cloudscale_objects_user.go ├── datasource_cloudscale_objects_user_test.go ├── datasource_cloudscale_server.go ├── datasource_cloudscale_server_group.go ├── datasource_cloudscale_server_group_test.go ├── datasource_cloudscale_server_test.go ├── datasource_cloudscale_subnet.go ├── datasource_cloudscale_subnet_test.go ├── datasource_cloudscale_volume.go ├── datasource_cloudscale_volume_test.go ├── datasources.go ├── provider.go ├── provider_test.go ├── resource_cloudscale_custom_image.go ├── resource_cloudscale_custom_image_test.go ├── resource_cloudscale_floating_ip.go ├── resource_cloudscale_floating_ip_test.go ├── resource_cloudscale_load_balancer.go ├── resource_cloudscale_load_balancer_health_monitor.go ├── resource_cloudscale_load_balancer_health_monitor_test.go ├── resource_cloudscale_load_balancer_listener.go ├── resource_cloudscale_load_balancer_listener_test.go ├── resource_cloudscale_load_balancer_pool.go ├── resource_cloudscale_load_balancer_pool_member.go ├── resource_cloudscale_load_balancer_pool_member_test.go ├── resource_cloudscale_load_balancer_pool_test.go ├── resource_cloudscale_load_balancer_test.go ├── resource_cloudscale_network.go ├── resource_cloudscale_network_test.go ├── resource_cloudscale_objects_user.go ├── resource_cloudscale_objects_user_test.go ├── resource_cloudscale_server.go ├── resource_cloudscale_server_group.go ├── resource_cloudscale_server_group_test.go ├── resource_cloudscale_server_test.go ├── resource_cloudscale_subnet.go ├── resource_cloudscale_subnet_test.go ├── resource_cloudscale_volume.go ├── resource_cloudscale_volume_test.go ├── resources.go ├── schema_type.go ├── sweeper_test.go ├── util.go ├── utils_test.go └── waitForStatus.go ├── docs ├── data-sources │ ├── custom_image.md │ ├── floating_ip.md │ ├── load_balancer.md │ ├── load_balancer_pool.md │ ├── load_balancer_pool_health_monitor.md │ ├── load_balancer_pool_listener.md │ ├── load_balancer_pool_member.md │ ├── network.md │ ├── objects_user.md │ ├── server.md │ ├── server_group.md │ ├── subnet.md │ └── volume.md ├── index.md └── resources │ ├── custom_image.md │ ├── floating_ip.md │ ├── load_balancer.md │ ├── load_balancer_health_monitor.md │ ├── load_balancer_listener.md │ ├── load_balancer_pool.md │ ├── load_balancer_pool_member.md │ ├── network.md │ ├── objects_user.md │ ├── server.md │ ├── server_group.md │ ├── subnet.md │ └── volume.md ├── examples ├── .gitignore └── main.tf ├── go.mod ├── go.sum └── main.go /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | HashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code. 4 | 5 | Please read the full text at https://www.hashicorp.com/community-guidelines 6 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Terraform is a mature project with a growing community. There are active, dedicated people willing to help you through various mediums. 4 | 5 | Take a look at those mediums listed at https://www.terraform.io/community.html 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Check for major GitHub Actions updates 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | 9 | # Check for direct Go Module updates 10 | - package-ecosystem: "gomod" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | allow: 15 | - dependency-type: "direct" 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (crazy-max/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - 'v*' 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - 22 | name: Checkout 23 | uses: actions/checkout@v4 24 | - 25 | name: Unshallow 26 | run: git fetch --prune --unshallow 27 | - 28 | name: Set up Go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version-file: 'go.mod' 32 | cache: false 33 | - 34 | name: Import GPG key 35 | uses: crazy-max/ghaction-import-gpg@v6 36 | id: import_gpg 37 | with: 38 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 39 | passphrase: ${{ secrets.PASSPHRASE }} 40 | - 41 | name: Run GoReleaser 42 | uses: goreleaser/goreleaser-action@v4 43 | with: 44 | # 'latest', 'nightly', or a semver 45 | version: '~> v1' 46 | args: release --rm-dist 47 | env: 48 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/terraform-integration-tests.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action runs your tests for each commit push and/or PR. Optionally 2 | # you can turn it on using a cron schedule for regular testing. 3 | # 4 | name: Terraform Integration Tests 5 | on: 6 | push: 7 | paths-ignore: 8 | - 'README.md' 9 | - 'CHANGELOG.md' 10 | # For systems with an upstream API that could drift unexpectedly (like most SaaS systems, etc.), 11 | # we recommend testing at a regular interval not necessarily tied to code changes. This will 12 | # ensure you are alerted to something breaking due to an API change, even if the code did not 13 | # change. 14 | schedule: 15 | - cron: '44 4 * * *' 16 | # Allow manual execution through the UI by collaborators 17 | workflow_dispatch: 18 | jobs: 19 | # ensure the code builds... 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 5 24 | steps: 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v4 28 | 29 | - name: Set up Go 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version-file: 'go.mod' 33 | cache: false 34 | id: go 35 | 36 | - name: Build 37 | run: | 38 | go build -v . 39 | 40 | # run acceptance tests in a matrix with Terraform core versions 41 | test: 42 | name: Matrix Test 43 | needs: build 44 | runs-on: ubuntu-latest 45 | timeout-minutes: 120 46 | 47 | strategy: 48 | fail-fast: true 49 | max-parallel: 1 50 | matrix: 51 | # list whatever Terraform versions here you would like to support 52 | runs: [ 53 | # test with 1.0 and the three most recent according to 54 | # https://github.com/hashicorp/terraform/releases 55 | {terraform: '1.0.0', testargs: "-run TestAccCloudscaleServer_Basic"}, 56 | {terraform: '1.9.0', testargs: "-run TestAccCloudscaleServer_Basic"}, 57 | {terraform: '1.10.0', testargs: "-run TestAccCloudscaleServer_Basic"}, 58 | {terraform: '1.11.0', testargs: "-run TestAccCloudscaleServer_Basic"}, 59 | {terraform: '', testargs: ""}, 60 | ] 61 | steps: 62 | 63 | - name: Check out code into the Go module directory 64 | uses: actions/checkout@v4 65 | 66 | - name: Set up Go 67 | uses: actions/setup-go@v5 68 | with: 69 | go-version-file: 'go.mod' 70 | cache: false 71 | id: go 72 | 73 | - name: TF acceptance tests 74 | timeout-minutes: 120 75 | env: 76 | TF_ACC: "1" 77 | TF_ACC_TERRAFORM_VERSION: ${{ matrix.runs.terraform }} 78 | 79 | # Set whatever additional acceptance test env vars here. You can 80 | # optionally use data from your repository secrets using the 81 | # following syntax: 82 | # SOME_VAR: ${{ secrets.SOME_VAR }} 83 | CLOUDSCALE_API_TOKEN: ${{ secrets.CLOUDSCALE_TOKEN }} 84 | TESTARGS: ${{ matrix.runs.testargs }} 85 | if: env.CLOUDSCALE_API_TOKEN != null 86 | 87 | run: | 88 | go test ./... -v $TESTARGS -timeout 120m 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | vendor 3 | terraform-provider-cloudscale 4 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.18.1 2 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | binary: '{{ .ProjectName }}_v{{ .Version }}' 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 is 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 | release: 51 | prerelease: auto 52 | # If you want to manually examine the release before its live, uncomment this line: 53 | # draft: true 54 | changelog: 55 | skip: true 56 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 5.0.0 2 | * :warning: **Breaking Change**: The default value for the `status` attribute 3 | of `cloudscale_server` is now `"running"` when no value is provided 4 | (i.e., missing in your .tf file). If your servers are intended to be in 5 | a state other than `"running"`, please explicitly set the appropriate state 6 | before upgrading to this version. 7 | * :warning: **Breaking Change**: The default timeout for server changes is 8 | now `1h` instead of `5m`. This change is necessary because changing the 9 | flavor of a GPU server with a scratch disk can take a significant 10 | amount of time if data is moved to a new host during the process. 11 | If you prefer to use the old timeout, you can now add the following 12 | block to your `cloudscale_server` configuration: 13 | ```hcl 14 | timeouts { 15 | update = "5m" 16 | } 17 | * Update go dependencies. 18 | 19 | ## 4.4.0 20 | * Use `import_source_format` again. This must be specified to import custom images in formats other than `raw`. 21 | See also [our blog](https://www.cloudscale.ch/en/news/2024/07/31/securing-qcow2-image-imports). 22 | There is no need to change this for existing imported images. 23 | * Update go dependencies. 24 | 25 | ## 4.3.0 26 | * Add `disable_dns_servers` to subnet. 27 | * Update go dependencies. 28 | 29 | ## 4.2.3 30 | * Update go dependencies. 31 | 32 | ## 4.2.2 33 | * Update go dependencies. 34 | 35 | ## 4.2.1 36 | * Update go dependencies. 37 | 38 | ## 4.2.0 39 | * Support for cloudscale.ch [Loads Balancers](https://www.cloudscale.ch/en/api/v1#load-balancers). 40 | * Ignore `import_source_format` as it has been deprecated in the cloudscale.ch API. 41 | You can remove the attribute from your Terraform file if you wish. The suggested 42 | in-place upgrades are a no-ops. 43 | 44 | ## 4.1.0 45 | * Add firmware_type to custom_image. 46 | * Update to go 1.18. 47 | 48 | ## 4.0.0 49 | * Implement tags for resources (#59) 50 | * Mark the keys attribute of `cloudscale_objects_user` as sensitive (#63) 51 | * Use consistent naming and usage of variables across all cloudscale.ch tools (#58) 52 | * Update to latest terraform-plugin-sdk to ensure compatibility with Terraform v1.1.x (#58) 53 | * Update to latest cloudscale-go-sdk (#58) 54 | * Update to latest terraform-plugin-sdk (#62) 55 | * :warning: **Breaking Change**: To be consistent with cloudscale.ch's other tools, the 56 | environment variable `CLOUDSCALE_TOKEN` has been renamed to `CLOUDSCALE_API_TOKEN`. 57 | Please adapt your environment accordingly. If you are configuring the token through 58 | some other means than an environment variable, you are not affected by this change. 59 | 60 | ## 3.2.0 61 | * Add data sources: 62 | - `cloudscale_server` 63 | - `cloudscale_server_group` 64 | - `cloudscale_volume` 65 | - `cloudscale_network` 66 | - `cloudscale_subnet` 67 | - `cloudscale_floating_ip` 68 | - `cloudscale_custom_image` 69 | - `cloudscale_objects_user` 70 | * Add terraform import for all resources (except Custom Images) 71 | * Allow updating the name of server groups. 72 | * Allow updating the PTR record (reverse DNS pointer) of Floating IPs. 73 | 74 | ## 3.1.0 75 | * Update to go 1.16 (#48) to support Apple silicon. 76 | 77 | ## 3.0.0 78 | * Upgrade terraform-plugin-sdk to v2 (#43) 79 | * Add Support for Custom Images (#44) 80 | * Add Options for SSH Host Keys (#45) 81 | * :warning: **Breaking Change**: Terraform versions older than 0.12 are no longer supported. 82 | 83 | ## 2.3.0 (October 19, 2020) 84 | * Allow creating Global Floating IPs (#34, #36) 85 | 86 | ## 2.2.0 (July 23, 2020) 87 | 88 | * **New Resource**: `cloudscale_objects_user` is now available (#29) 89 | * Allow creating unattached Floating IPs (#30) 90 | 91 | ## 2.1.2 (April 22, 2020) 92 | 93 | FEATURES: 94 | 95 | * Add Subnets and Addresses (#25) 96 | 97 | ## 2.1.1 (December 04, 2019) 98 | 99 | FEATURES: 100 | 101 | * Add Support for Networks (#20) 102 | * Add Password Option to Server (#21) 103 | 104 | ## 2.1.0 (November 20, 2019) 105 | 106 | FEATURES: 107 | 108 | * Support for Terraform 0.12.x 109 | * Add Zones/Regions to use with all resources 110 | 111 | ## 2.0.0 (July 12, 2019) 112 | 113 | FEATURES: 114 | 115 | * **New Resource**: `cloudscale_server_group` is now available (#16) 116 | 117 | BACKWARDS INCOMPATIBILITIES: 118 | 119 | * Implicit server groups are no longer supported. This means that you cannot 120 | just use `anti_affinity_with` anymore. 121 | 122 | ## 1.1.0 (April 11, 2019) 123 | 124 | FEATURES: 125 | 126 | * **New Resource**: `cloudscale_volume` is now available (#5) 127 | 128 | ENHANCEMENTS: 129 | * Added support for scaling servers (#13) 130 | * Added support for scaling root volumes (#14) 131 | 132 | IMPROVEMENTS: 133 | 134 | * Expose the first public/private IPv4 and IPv6 addresses as string attributes `public_ipv4`, 135 | `public_ipv6` and `private_ipv4` (#8) 136 | 137 | ## 1.0.1 (April 06, 2018) 138 | 139 | 140 | IMPROVEMENTS: 141 | 142 | * `resource_cloudscale_server`: Use documented defaults for `cloudscale_server` ([#1](https://github.com/terraform-providers/terraform-provider-aws/issues/1)) 143 | 144 | ## 1.0.0 (November 01, 2017) 145 | 146 | * Initial release of the cloudscale.ch provider 147 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | default: testacc 2 | 3 | sweep: 4 | @echo "WARNING: This will destroy infrastructure. Use only in development accounts." 5 | TF_ACC=1 go test ./... -v -sweep -timeout 10m 6 | 7 | # Run acceptance tests 8 | .PHONY: testacc 9 | testacc: 10 | TF_ACC=1 go test ./... -v -count=1 -parallel 2 $(TESTARGS) -timeout 120m 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 cloudscale.ch AG 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudscale.ch Terraform Provider 2 | 3 | - Website: https://www.terraform.io 4 | - [![Gitter chat](https://badges.gitter.im/hashicorp-terraform/Lobby.png)](https://gitter.im/hashicorp-terraform/Lobby) 5 | - Mailing list: [Google Groups](http://groups.google.com/group/terraform-tool) 6 | 7 | ## Using the Provider 8 | 9 | For detailed usage instructions and examples, please refer to the official documentation available 10 | at [Terraform Registry: cloudscale-ch/cloudscale](https://registry.terraform.io/providers/cloudscale-ch/cloudscale/latest). 11 | 12 | ## Developing the Provider 13 | 14 | Before you begin, make sure you have [Go](http://golang.org) installed on your machine. 15 | 16 | ### Compile the Provider 17 | 18 | Run the following command to compile the provider. The binary will be placed in your `$GOPATH/bin` directory. 19 | 20 | ```sh 21 | go install 22 | ``` 23 | 24 | To create builds for different platforms, you can use [goreleaser](https://goreleaser.com/): 25 | 26 | - **For goreleaser v1.x:** 27 | 28 | ```sh 29 | docker run -it --rm -v $PWD:/app --workdir=/app goreleaser/goreleaser:v1.26.2 release --snapshot --rm-dist --skip-sign 30 | ``` 31 | 32 | - **For goreleaser v2.x:** 33 | 34 | ```sh 35 | docker run -it --rm -v $PWD:/app --workdir=/app goreleaser/goreleaser:v2.1.0 release --snapshot --clean --skip=publish,sign 36 | ``` 37 | 38 | ### Testing Locally Compiled Driver 39 | 40 | To test a locally compiled driver, add the following to your `~/.terraformrc` file. 41 | This configuration directs Terraform to use your local `go/bin` directory for the cloudscale provider: 42 | 43 | ```hcl 44 | # Contents of ~/.terraformrc 45 | provider_installation { 46 | # Use go/bin as an overridden package directory 47 | # for the cloudscale-ch/cloudscale provider. This disables the version and checksum 48 | # verifications for this provider and forces Terraform to look for the 49 | # null provider plugin in the given directory. 50 | dev_overrides { 51 | "cloudscale-ch/cloudscale" = "/Users/[your-username]/go/bin" 52 | } 53 | 54 | # For all other providers, install them directly from their origin provider 55 | # registries as normal. If you omit this, Terraform will _only_ use 56 | # the dev_overrides block, and so no other providers will be available. 57 | direct {} 58 | } 59 | ``` 60 | 61 | *Remember to replace `[your-username]` with your actual username.* 62 | 63 | ### Running Acceptance Tests 64 | 65 | Acceptance tests create real resources and might incur costs. They also use a specific version of Terraform (see [Terraform CLI Installation Behaviors](https://www.terraform.io/plugin/sdkv2/testing/acceptance-tests#terraform-cli-installation-behaviors)). 66 | 67 | - **Run all tests:** 68 | 69 | ```sh 70 | make testacc 71 | ``` 72 | 73 | - **Run a subset of the tests (e.g., tests for subnet):** 74 | 75 | ```sh 76 | TESTARGS="-run TestAccCloudscaleSubnet" make testacc 77 | ``` 78 | 79 | ### Upgrading the cloudscale-go-sdk 80 | 81 | - **Upgrade to the latest version:** 82 | 83 | ```sh 84 | go get -u github.com/cloudscale-ch/cloudscale-go-sdk/v5 85 | go mod tidy 86 | ``` 87 | 88 | ### Working with Different Versions of the cloudscale-go-sdk 89 | 90 | If you want to work with a local version or a specific version of the cloudscale-go-sdk during development, use the 91 | following commands: 92 | 93 | - **Replace with a local version:** 94 | 95 | ```sh 96 | go mod edit -replace "github.com/cloudscale-ch/cloudscale-go-sdk/v4=../cloudscale-go-sdk/" 97 | go mod tidy 98 | git commit -m "drop: Use local version of cloudscale-go-sdk" 99 | ``` 100 | 101 | - **Pin to a specific commit:** 102 | 103 | ```sh 104 | go mod edit -replace "github.com/cloudscale-ch/cloudscale-go-sdk/v4=github.com/cloudscale-ch/cloudscale-go-sdk/v4@" 105 | go mod tidy 106 | git commit -m "drop: Pin specific commit of cloudscale-go-sdk" 107 | ``` 108 | 109 | - **Switch back to the upstream version:** 110 | 111 | ```sh 112 | go mod edit -dropreplace "github.com/cloudscale-ch/cloudscale-go-sdk/v4" 113 | go mod tidy 114 | ``` 115 | 116 | ## Releasing the Provider 117 | 118 | 1. Ensure the `CHANGELOG.md` is up-to-date. 119 | 1. Ensure the `.github/workflows/terraform-integration-tests.yml` tests the 3 most recent Terraform versions. 120 | 1. Create a new release [on GitHub](https://github.com/cloudscale-ch/terraform-provider-cloudscale/releases/new). 121 | Both the tag and release title must follow this pattern: `v<>`. 122 | Examples: `v42.43.44` or `v1.33.7-rc.1`. 123 | 1. It might take a moment until the release appears in the [Terraform registry](https://registry.terraform.io/providers/cloudscale-ch/cloudscale/latest). 124 | You can manually resync the provider when you are logged in to the registry. 125 | 126 | ## Developing the Documentation Website 127 | 128 | Use the Terraform [doc preview tool](https://registry.terraform.io/tools/doc-preview) to test markdown rendering. 129 | -------------------------------------------------------------------------------- /cloudscale/config.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" 6 | "golang.org/x/oauth2" 7 | ) 8 | 9 | type Config struct { 10 | Token string 11 | } 12 | 13 | func (c *Config) Client() (*cloudscale.Client, error) { 14 | tc := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource( 15 | &oauth2.Token{AccessToken: c.Token}, 16 | )) 17 | 18 | tc.Transport = logging.NewTransport("Cloudscale", tc.Transport) 19 | 20 | client := cloudscale.NewClient(tc) 21 | 22 | return client, nil 23 | } 24 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_custom_image.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleCustomImage() *schema.Resource { 11 | recordSchema := getCustomImageSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("custom images", recordSchema, getFetchFunc( 15 | listCustomImages, 16 | gatherCustomImageResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listCustomImages(d *schema.ResourceData, meta any) ([]cloudscale.CustomImage, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.CustomImages.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_custom_image_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleCustomImage_DS_Basic(t *testing.T) { 14 | var customImage cloudscale.CustomImage 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := customImageConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleCustomImageDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleCustomImageConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleCustomImageExists("data.cloudscale_custom_image.foo", &customImage), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_custom_image.basic.0", "id", &customImage.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_custom_image.foo", "id", &customImage.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_custom_image.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_custom_image.foo", "slug", "terra-0"), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_custom_image.foo", "user_data_handling", "pass-through"), 42 | resource.TestCheckResourceAttr( 43 | "data.cloudscale_custom_image.foo", "firmware_type", "bios"), 44 | resource.TestCheckResourceAttr( 45 | "data.cloudscale_custom_image.foo", "size_gb", "1"), 46 | resource.TestCheckResourceAttr( 47 | "data.cloudscale_custom_image.foo", "zone_slugs.#", "2"), 48 | resource.TestCheckResourceAttrSet( 49 | "data.cloudscale_custom_image.foo", "checksums.sha256"), 50 | resource.TestCheckResourceAttrSet( 51 | "data.cloudscale_custom_image.foo", "href"), 52 | ), 53 | }, 54 | { 55 | Config: config + testAccCheckCloudscaleCustomImageConfig_name_and_slug(name1, "terra-0"), 56 | Check: resource.ComposeTestCheckFunc( 57 | resource.TestCheckResourceAttr( 58 | "data.cloudscale_custom_image.foo", "name", name1), 59 | ), 60 | }, 61 | { 62 | Config: config + testAccCheckCloudscaleCustomImageConfig_name(name2), 63 | Check: resource.ComposeTestCheckFunc( 64 | resource.TestCheckResourceAttr( 65 | "data.cloudscale_custom_image.foo", "name", name2), 66 | resource.TestCheckResourceAttr( 67 | "data.cloudscale_custom_image.foo", "slug", "terra-1"), 68 | ), 69 | }, 70 | { 71 | Config: config + testAccCheckCloudscaleCustomImageConfig_name_and_slug(name2, "terra-1"), 72 | Check: resource.ComposeTestCheckFunc( 73 | resource.TestCheckResourceAttr( 74 | "data.cloudscale_custom_image.foo", "name", name2), 75 | resource.TestCheckResourceAttr( 76 | "data.cloudscale_custom_image.foo", "slug", "terra-1"), 77 | ), 78 | }, 79 | { 80 | Config: config + testAccCheckCloudscaleCustomImageConfig_name_and_slug(name1, "terra-1"), 81 | ExpectError: regexp.MustCompile(`Found zero custom images`), 82 | }, 83 | { 84 | 85 | Config: config + testAccCheckCloudscaleCustomImageConfig_id(), 86 | Check: resource.ComposeTestCheckFunc( 87 | resource.TestCheckResourceAttr( 88 | "cloudscale_custom_image.basic.0", "name", name1), 89 | resource.TestCheckResourceAttr( 90 | "data.cloudscale_custom_image.foo", "name", name1), 91 | resource.TestCheckResourceAttrPtr( 92 | "cloudscale_custom_image.basic.0", "id", &customImage.UUID), 93 | resource.TestCheckResourceAttrPtr( 94 | "data.cloudscale_custom_image.foo", "id", &customImage.UUID), 95 | ), 96 | }, 97 | { 98 | Config: config + "\n" + `data "cloudscale_custom_image" "foo" {}`, 99 | ExpectError: regexp.MustCompile(`Found \d+ custom images, expected one`), 100 | }, 101 | }, 102 | }) 103 | } 104 | 105 | func TestAccCloudscaleCustomImage_DS_NotExisting(t *testing.T) { 106 | resource.ParallelTest(t, resource.TestCase{ 107 | PreCheck: func() { testAccPreCheck(t) }, 108 | Providers: testAccProviders, 109 | Steps: []resource.TestStep{ 110 | { 111 | Config: testAccCheckCloudscaleCustomImageConfig_name("terraform-unknown"), 112 | ExpectError: regexp.MustCompile(`Found zero custom images`), 113 | }, 114 | }, 115 | }) 116 | } 117 | 118 | func testAccCheckCloudscaleCustomImageConfig_name(name string) string { 119 | return fmt.Sprintf(` 120 | data "cloudscale_custom_image" "foo" { 121 | name = "%s" 122 | } 123 | `, name) 124 | } 125 | 126 | func testAccCheckCloudscaleCustomImageConfig_name_and_slug(name, slug string) string { 127 | return fmt.Sprintf(` 128 | data "cloudscale_custom_image" "foo" { 129 | name = "%s" 130 | slug = "%s" 131 | } 132 | `, name, slug) 133 | } 134 | 135 | func testAccCheckCloudscaleCustomImageConfig_id() string { 136 | return fmt.Sprintf(` 137 | data "cloudscale_custom_image" "foo" { 138 | id = "${cloudscale_custom_image.basic.0.id}" 139 | } 140 | `) 141 | } 142 | 143 | func customImageConfig_baseline(count int, rInt int) string { 144 | return fmt.Sprintf(` 145 | resource "cloudscale_custom_image" "basic" { 146 | count = "%v" 147 | import_url = "%s" 148 | name = "terraform-%d-${count.index}" 149 | slug = "terra-${count.index}" 150 | user_data_handling = "pass-through" 151 | zone_slugs = ["lpg1", "rma1"] 152 | }`, count, smallImageDownloadURL, rInt) 153 | } 154 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_floating_ip.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleFloatingIP() *schema.Resource { 11 | recordSchema := getFloatingIPSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("Floating IPs", recordSchema, getFetchFunc( 15 | listFloatingIPs, 16 | gatherFloatingIPResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listFloatingIPs(d *schema.ResourceData, meta any) ([]cloudscale.FloatingIP, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.FloatingIPs.List(context.Background()) 25 | } -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceCloudscaleLoadBalancer() *schema.Resource { 10 | recordSchema := getLoadBalancerSchema(DATA_SOURCE) 11 | 12 | return &schema.Resource{ 13 | ReadContext: dataSourceResourceRead("load balancers", recordSchema, getFetchFunc( 14 | listLoadBalancers, 15 | gatherLoadBalancerResourceData, 16 | )), 17 | Schema: recordSchema, 18 | } 19 | } 20 | 21 | func listLoadBalancers(d *schema.ResourceData, meta any) ([]cloudscale.LoadBalancer, error) { 22 | client := meta.(*cloudscale.Client) 23 | return client.LoadBalancers.List(context.Background()) 24 | } 25 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_health_monitor.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceCloudscaleLoadBalancerHealthMonitor() *schema.Resource { 10 | recordSchema := getLoadBalancerHealthMonitorSchema(DATA_SOURCE) 11 | 12 | return &schema.Resource{ 13 | ReadContext: dataSourceResourceRead("load balancer health monitors", recordSchema, getFetchFunc( 14 | listLoadBalancerHealthMonitors, 15 | gatherLoadBalancerHealthMonitorResourceData, 16 | )), 17 | Schema: recordSchema, 18 | } 19 | } 20 | 21 | func listLoadBalancerHealthMonitors(d *schema.ResourceData, meta any) ([]cloudscale.LoadBalancerHealthMonitor, error) { 22 | client := meta.(*cloudscale.Client) 23 | return client.LoadBalancerHealthMonitors.List(context.Background()) 24 | } 25 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_health_monitor_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | "testing" 10 | ) 11 | 12 | func TestAccCloudscaleLoadBalancerHealthMonitor_DS_Basic(t *testing.T) { 13 | var loadBalancerHealthMonitor cloudscale.LoadBalancerHealthMonitor 14 | rInt := acctest.RandInt() 15 | 16 | config := loadBalancerConfig_baseline(2, rInt) + 17 | loadBalancerPoolConfig_baseline(2, rInt) + 18 | loadBalancerHealthMonitorConfig_baseline(2, rInt) 19 | 20 | resourceName1 := "cloudscale_load_balancer_health_monitor.basic.0" 21 | dataSourceName := "data.cloudscale_load_balancer_health_monitor.foo" 22 | 23 | resource.ParallelTest(t, resource.TestCase{ 24 | PreCheck: func() { testAccPreCheck(t) }, 25 | Providers: testAccProviders, 26 | CheckDestroy: testAccCheckCloudscaleLoadBalancerDestroy, 27 | Steps: []resource.TestStep{ 28 | { 29 | Config: config, 30 | }, 31 | { 32 | Config: config + testAccCheckCloudscaleLoadBalancerHealthMonitorConfig_id(), 33 | Check: resource.ComposeTestCheckFunc( 34 | testAccCheckCloudscaleLoadBalancerHealthMonitorExists(resourceName1, &loadBalancerHealthMonitor), 35 | resource.TestCheckResourceAttr( 36 | resourceName1, "delay_s", fmt.Sprintf("8%d", 0)), 37 | resource.TestCheckResourceAttr( 38 | dataSourceName, "delay_s", fmt.Sprintf("8%d", 0)), 39 | resource.TestCheckResourceAttrPtr( 40 | resourceName1, "id", &loadBalancerHealthMonitor.UUID), 41 | resource.TestCheckResourceAttrPtr( 42 | dataSourceName, "id", &loadBalancerHealthMonitor.UUID), 43 | ), 44 | }, 45 | { 46 | Config: config + `data "cloudscale_load_balancer_health_monitor" "foo" {}`, 47 | ExpectError: regexp.MustCompile(`Found \d+ load balancer health monitors, expected one`), 48 | }, 49 | }, 50 | }) 51 | } 52 | 53 | func testAccCheckCloudscaleLoadBalancerHealthMonitorConfig_id() string { 54 | return fmt.Sprintf(` 55 | data "cloudscale_load_balancer_health_monitor" "foo" { 56 | id = cloudscale_load_balancer_health_monitor.basic.0.id 57 | } 58 | `) 59 | } 60 | 61 | func loadBalancerHealthMonitorConfig_baseline(count int, rInt int) string { 62 | return fmt.Sprintf(` 63 | resource "cloudscale_load_balancer_health_monitor" "basic" { 64 | pool_uuid = cloudscale_load_balancer_pool.basic[count.index].id 65 | delay_s = "8${count.index}" 66 | up_threshold = 3 67 | down_threshold = 3 68 | timeout_s = 5 69 | count = %d 70 | type = "tcp" 71 | } 72 | `, count) 73 | } 74 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_listener.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceCloudscaleLoadBalancerListener() *schema.Resource { 10 | recordSchema := getLoadBalancerListenerSchema(DATA_SOURCE) 11 | 12 | return &schema.Resource{ 13 | ReadContext: dataSourceResourceRead("load balancer listeners", recordSchema, getFetchFunc( 14 | listLoadBalancerListeners, 15 | gatherLoadBalancerListenerResourceData, 16 | )), 17 | Schema: recordSchema, 18 | } 19 | } 20 | 21 | func listLoadBalancerListeners(d *schema.ResourceData, meta any) ([]cloudscale.LoadBalancerListener, error) { 22 | client := meta.(*cloudscale.Client) 23 | return client.LoadBalancerListeners.List(context.Background()) 24 | } 25 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_listener_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | "testing" 10 | ) 11 | 12 | func TestAccCloudscaleLoadBalancerListener_DS_Basic(t *testing.T) { 13 | var loadBalancerListener cloudscale.LoadBalancerListener 14 | rInt := acctest.RandInt() 15 | name1 := fmt.Sprintf("terraform-%d-lb-listener-0", rInt) 16 | name2 := fmt.Sprintf("terraform-%d-lb-listener-1", rInt) 17 | 18 | config := loadBalancerConfig_baseline(1, rInt) + 19 | loadBalancerPoolConfig_baseline(1, rInt) + 20 | loadBalancerListenerConfig_baseline(2, rInt) 21 | 22 | poolResourceName1 := "cloudscale_load_balancer_pool.basic.0" 23 | resourceName1 := "cloudscale_load_balancer_listener.basic.0" 24 | resourceName2 := "cloudscale_load_balancer_listener.basic.1" 25 | dataSourceName := "data.cloudscale_load_balancer_listener.foo" 26 | 27 | resource.ParallelTest(t, resource.TestCase{ 28 | PreCheck: func() { testAccPreCheck(t) }, 29 | Providers: testAccProviders, 30 | CheckDestroy: testAccCheckCloudscaleLoadBalancerDestroy, 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: config, 34 | }, 35 | { 36 | Config: config + testAccCheckCloudscaleLoadBalancerListenerConfig_name(name1), 37 | Check: resource.ComposeTestCheckFunc( 38 | testAccCheckCloudscaleLoadBalancerListenerExists(dataSourceName, &loadBalancerListener), 39 | resource.TestCheckResourceAttrPtr( 40 | resourceName1, "id", &loadBalancerListener.UUID), 41 | resource.TestCheckResourceAttrPtr( 42 | dataSourceName, "id", &loadBalancerListener.UUID), 43 | resource.TestCheckResourceAttr( 44 | dataSourceName, "name", name1), 45 | resource.TestCheckResourceAttrPair( 46 | dataSourceName, "name", resourceName1, "name"), 47 | resource.TestCheckResourceAttrPair( 48 | dataSourceName, "pool_uuid", resourceName1, "pool_uuid"), 49 | resource.TestCheckResourceAttrPair( 50 | dataSourceName, "pool_uuid", poolResourceName1, "id"), 51 | ), 52 | }, 53 | { 54 | Config: config + testAccCheckCloudscaleLoadBalancerListenerConfig_name(name2), 55 | Check: resource.ComposeTestCheckFunc( 56 | resource.TestCheckResourceAttr( 57 | dataSourceName, "name", name2), 58 | resource.TestCheckResourceAttrPair( 59 | dataSourceName, "name", resourceName2, "name"), 60 | ), 61 | }, 62 | { 63 | Config: config + testAccCheckCloudscaleLoadBalancerListenerConfig_id(), 64 | Check: resource.ComposeTestCheckFunc( 65 | resource.TestCheckResourceAttr( 66 | resourceName1, "name", name1), 67 | resource.TestCheckResourceAttr( 68 | dataSourceName, "name", name1), 69 | resource.TestCheckResourceAttrPtr( 70 | resourceName1, "id", &loadBalancerListener.UUID), 71 | resource.TestCheckResourceAttrPtr( 72 | dataSourceName, "id", &loadBalancerListener.UUID), 73 | ), 74 | }, 75 | { 76 | Config: config + `data "cloudscale_load_balancer_listener" "foo" {}`, 77 | ExpectError: regexp.MustCompile(`Found \d+ load balancer listeners, expected one`), 78 | }, 79 | }, 80 | }) 81 | } 82 | 83 | func testAccCheckCloudscaleLoadBalancerListenerConfig_name(name string) string { 84 | return fmt.Sprintf(` 85 | data "cloudscale_load_balancer_listener" "foo" { 86 | name = "%s" 87 | } 88 | `, name) 89 | } 90 | 91 | func testAccCheckCloudscaleLoadBalancerListenerConfig_id() string { 92 | return fmt.Sprintf(` 93 | data "cloudscale_load_balancer_listener" "foo" { 94 | id = cloudscale_load_balancer_listener.basic.0.id 95 | } 96 | `) 97 | } 98 | 99 | func loadBalancerListenerConfig_baseline(count int, rInt int) string { 100 | return fmt.Sprintf(` 101 | resource "cloudscale_load_balancer_listener" "basic" { 102 | count = %[1]d 103 | name = "terraform-%[2]d-lb-listener-${count.index}" 104 | pool_uuid = cloudscale_load_balancer_pool.basic.0.id 105 | protocol = "tcp" 106 | protocol_port = "8${count.index}" 107 | } 108 | `, count, rInt) 109 | } 110 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_pool.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceCloudscaleLoadBalancerPool() *schema.Resource { 10 | recordSchema := getLoadBalancerPoolSchema(DATA_SOURCE) 11 | 12 | return &schema.Resource{ 13 | ReadContext: dataSourceResourceRead("load balancer pools", recordSchema, getFetchFunc( 14 | listLoadBalancerPools, 15 | gatherLoadBalancerPoolResourceData, 16 | )), 17 | Schema: recordSchema, 18 | } 19 | } 20 | 21 | func listLoadBalancerPools(d *schema.ResourceData, meta any) ([]cloudscale.LoadBalancerPool, error) { 22 | client := meta.(*cloudscale.Client) 23 | return client.LoadBalancerPools.List(context.Background()) 24 | } 25 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_pool_member.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceCloudscaleLoadBalancerPoolMember() *schema.Resource { 10 | recordSchema := getLoadBalancerPoolMemberSchema(DATA_SOURCE) 11 | 12 | return &schema.Resource{ 13 | ReadContext: dataSourceResourceRead("load balancer pool members", recordSchema, getFetchFunc( 14 | listLoadBalancerPoolMembers, 15 | gatherLoadBalancerPoolMemberResourceData, 16 | )), 17 | Schema: recordSchema, 18 | } 19 | } 20 | 21 | func listLoadBalancerPoolMembers(d *schema.ResourceData, meta any) ([]cloudscale.LoadBalancerPoolMember, error) { 22 | client := meta.(*cloudscale.Client) 23 | poolId := d.Get("pool_uuid").(string) 24 | return client.LoadBalancerPoolMembers.List(context.Background(), poolId) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_pool_member_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | "testing" 10 | ) 11 | 12 | func TestAccCloudscaleLoadBalancerPoolMember_DS_Basic(t *testing.T) { 13 | var loadBalancerPoolMember cloudscale.LoadBalancerPoolMember 14 | rInt := acctest.RandInt() 15 | name1 := fmt.Sprintf("terraform-%d-lb-pool-member-0", rInt) 16 | name2 := fmt.Sprintf("terraform-%d-lb-pool-member-1", rInt) 17 | 18 | config := loadBalancerConfig_baseline(1, rInt) + 19 | loadBalancerPoolConfig_baseline(1, rInt) + 20 | loadBalancerPoolMemberConfig_baseline(2, rInt) 21 | 22 | poolResourceName1 := "cloudscale_load_balancer_pool.basic.0" 23 | resourceName1 := "cloudscale_load_balancer_pool_member.basic.0" 24 | resourceName2 := "cloudscale_load_balancer_pool_member.basic.1" 25 | dataSourceName := "data.cloudscale_load_balancer_pool_member.foo" 26 | 27 | resource.ParallelTest(t, resource.TestCase{ 28 | PreCheck: func() { testAccPreCheck(t) }, 29 | Providers: testAccProviders, 30 | CheckDestroy: testAccCheckCloudscaleLoadBalancerDestroy, 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: config, 34 | }, 35 | { 36 | Config: config + testAccCheckCloudscaleLoadBalancerPoolMemberConfig_name(name1), 37 | Check: resource.ComposeTestCheckFunc( 38 | testAccCheckCloudscaleLoadBalancerPoolMemberExists(dataSourceName, &loadBalancerPoolMember), 39 | resource.TestCheckResourceAttrPtr( 40 | resourceName1, "id", &loadBalancerPoolMember.UUID), 41 | resource.TestCheckResourceAttrPtr( 42 | dataSourceName, "id", &loadBalancerPoolMember.UUID), 43 | resource.TestCheckResourceAttr( 44 | dataSourceName, "name", name1), 45 | resource.TestCheckResourceAttrPair( 46 | dataSourceName, "name", resourceName1, "name"), 47 | resource.TestCheckResourceAttrPair( 48 | dataSourceName, "pool_uuid", resourceName1, "pool_uuid"), 49 | resource.TestCheckResourceAttrPair( 50 | dataSourceName, "pool_uuid", poolResourceName1, "id"), 51 | ), 52 | }, 53 | { 54 | Config: config + testAccCheckCloudscaleLoadBalancerPoolMemberConfig_name(name2), 55 | Check: resource.ComposeTestCheckFunc( 56 | resource.TestCheckResourceAttr( 57 | dataSourceName, "name", name2), 58 | resource.TestCheckResourceAttrPair( 59 | dataSourceName, "name", resourceName2, "name"), 60 | ), 61 | }, 62 | { 63 | Config: config + testAccCheckCloudscaleLoadBalancerPoolMemberConfig_id(), 64 | Check: resource.ComposeTestCheckFunc( 65 | resource.TestCheckResourceAttr( 66 | resourceName1, "name", name1), 67 | resource.TestCheckResourceAttr( 68 | dataSourceName, "name", name1), 69 | resource.TestCheckResourceAttrPtr( 70 | resourceName1, "id", &loadBalancerPoolMember.UUID), 71 | resource.TestCheckResourceAttrPtr( 72 | dataSourceName, "id", &loadBalancerPoolMember.UUID), 73 | ), 74 | }, 75 | { 76 | Config: config + testAccCheckCloudscaleLoadBalancerPoolMemberConfig_pool(), 77 | ExpectError: regexp.MustCompile(`Found \d+ load balancer pool members, expected one`), 78 | }, 79 | }, 80 | }) 81 | } 82 | 83 | func testAccCheckCloudscaleLoadBalancerPoolMemberConfig_name(name string) string { 84 | return fmt.Sprintf(` 85 | data "cloudscale_load_balancer_pool_member" "foo" { 86 | name = "%s" 87 | pool_uuid = cloudscale_load_balancer_pool.basic.0.id 88 | } 89 | `, name) 90 | } 91 | 92 | func testAccCheckCloudscaleLoadBalancerPoolMemberConfig_id() string { 93 | return fmt.Sprintf(` 94 | data "cloudscale_load_balancer_pool_member" "foo" { 95 | id = cloudscale_load_balancer_pool_member.basic.0.id 96 | pool_uuid = cloudscale_load_balancer_pool.basic.0.id 97 | } 98 | `) 99 | } 100 | 101 | func loadBalancerPoolMemberConfig_baseline(count int, rInt int) string { 102 | return fmt.Sprintf(` 103 | %s 104 | 105 | resource "cloudscale_load_balancer_pool_member" "basic" { 106 | count = %d 107 | name = "terraform-%d-lb-pool-member-${count.index}" 108 | pool_uuid = cloudscale_load_balancer_pool.basic.0.id 109 | protocol_port = 80 110 | address = "10.0.0.${count.index}" 111 | subnet_uuid = cloudscale_subnet.lb-subnet.id 112 | } 113 | `, testAccCloudscaleLoadBalancerSubnet(rInt), count, rInt) 114 | } 115 | 116 | func testAccCheckCloudscaleLoadBalancerPoolMemberConfig_pool() string { 117 | return fmt.Sprintf(` 118 | data "cloudscale_load_balancer_pool_member" "foo" { 119 | pool_uuid = cloudscale_load_balancer_pool.basic.0.id 120 | } 121 | `) 122 | } 123 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_pool_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | "testing" 10 | ) 11 | 12 | func TestAccCloudscaleLoadBalancerPool_DS_Basic(t *testing.T) { 13 | var loadBalancerPool cloudscale.LoadBalancerPool 14 | rInt := acctest.RandInt() 15 | name1 := fmt.Sprintf("terraform-%d-lb-pool-0", rInt) 16 | name2 := fmt.Sprintf("terraform-%d-lb-pool-1", rInt) 17 | 18 | config := loadBalancerConfig_baseline(2, rInt) + 19 | loadBalancerPoolConfig_baseline(2, rInt) 20 | 21 | lbResourceName1 := "cloudscale_load_balancer.basic.0" 22 | resourceName1 := "cloudscale_load_balancer_pool.basic.0" 23 | resourceName2 := "cloudscale_load_balancer_pool.basic.1" 24 | dataSourceName := "data.cloudscale_load_balancer_pool.foo" 25 | 26 | resource.ParallelTest(t, resource.TestCase{ 27 | PreCheck: func() { testAccPreCheck(t) }, 28 | Providers: testAccProviders, 29 | CheckDestroy: testAccCheckCloudscaleLoadBalancerDestroy, 30 | Steps: []resource.TestStep{ 31 | { 32 | Config: config, 33 | }, 34 | { 35 | Config: config + testAccCheckCloudscaleLoadBalancerPoolConfig_name(name1), 36 | Check: resource.ComposeTestCheckFunc( 37 | testAccCheckCloudscaleLoadBalancerPoolExists(dataSourceName, &loadBalancerPool), 38 | resource.TestCheckResourceAttrPtr( 39 | resourceName1, "id", &loadBalancerPool.UUID), 40 | resource.TestCheckResourceAttrPtr( 41 | dataSourceName, "id", &loadBalancerPool.UUID), 42 | resource.TestCheckResourceAttr( 43 | dataSourceName, "name", name1), 44 | resource.TestCheckResourceAttrPair( 45 | dataSourceName, "name", resourceName1, "name"), 46 | resource.TestCheckResourceAttrPair( 47 | dataSourceName, "load_balancer_uuid", resourceName1, "load_balancer_uuid"), 48 | resource.TestCheckResourceAttrPair( 49 | dataSourceName, "load_balancer_uuid", lbResourceName1, "id"), 50 | ), 51 | }, 52 | { 53 | Config: config + testAccCheckCloudscaleLoadBalancerPoolConfig_name(name2), 54 | Check: resource.ComposeTestCheckFunc( 55 | resource.TestCheckResourceAttr( 56 | dataSourceName, "name", name2), 57 | resource.TestCheckResourceAttrPair( 58 | dataSourceName, "name", resourceName2, "name"), 59 | ), 60 | }, 61 | { 62 | Config: config + testAccCheckCloudscaleLoadBalancerPoolConfig_id(), 63 | Check: resource.ComposeTestCheckFunc( 64 | resource.TestCheckResourceAttr( 65 | resourceName1, "name", name1), 66 | resource.TestCheckResourceAttr( 67 | dataSourceName, "name", name1), 68 | resource.TestCheckResourceAttrPtr( 69 | resourceName1, "id", &loadBalancerPool.UUID), 70 | resource.TestCheckResourceAttrPtr( 71 | dataSourceName, "id", &loadBalancerPool.UUID), 72 | ), 73 | }, 74 | { 75 | Config: config + "\n" + `data "cloudscale_load_balancer_pool" "foo" {}`, 76 | ExpectError: regexp.MustCompile(`Found \d+ load balancer pools, expected one`), 77 | }, 78 | }, 79 | }) 80 | } 81 | 82 | func loadBalancerPoolConfig_baseline(count int, rInt int) string { 83 | return fmt.Sprintf(` 84 | resource "cloudscale_load_balancer_pool" "basic" { 85 | count = %d 86 | name = "terraform-%d-lb-pool-${count.index}" 87 | algorithm = "round_robin" 88 | protocol = "tcp" 89 | load_balancer_uuid = cloudscale_load_balancer.basic[count.index].id 90 | }`, count, rInt) 91 | } 92 | 93 | func testAccCheckCloudscaleLoadBalancerPoolConfig_name(name string) string { 94 | return fmt.Sprintf(` 95 | data "cloudscale_load_balancer_pool" "foo" { 96 | name = "%s" 97 | } 98 | `, name) 99 | } 100 | 101 | func testAccCheckCloudscaleLoadBalancerPoolConfig_id() string { 102 | return fmt.Sprintf(` 103 | data "cloudscale_load_balancer_pool" "foo" { 104 | id = "${cloudscale_load_balancer_pool.basic.0.id}" 105 | } 106 | `) 107 | } 108 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_load_balancer_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | "testing" 10 | ) 11 | 12 | func TestAccCloudscaleLoadBalancer_DS_Basic(t *testing.T) { 13 | var loadBalancer cloudscale.LoadBalancer 14 | rInt := acctest.RandInt() 15 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 16 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 17 | 18 | config := loadBalancerConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleLoadBalancerDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleLoadBalancerExists("data.cloudscale_load_balancer.foo", &loadBalancer), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_load_balancer.basic.0", "id", &loadBalancer.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_load_balancer.foo", "id", &loadBalancer.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_load_balancer.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_load_balancer.foo", "zone_slug", "rma1"), 40 | ), 41 | }, 42 | { 43 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_name_and_zone(name1, "rma1"), 44 | Check: resource.ComposeTestCheckFunc( 45 | resource.TestCheckResourceAttr( 46 | "data.cloudscale_load_balancer.foo", "name", name1), 47 | ), 48 | }, 49 | { 50 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_name(name2), 51 | Check: resource.ComposeTestCheckFunc( 52 | resource.TestCheckResourceAttr( 53 | "data.cloudscale_load_balancer.foo", "name", name2), 54 | resource.TestCheckResourceAttr( 55 | "data.cloudscale_load_balancer.foo", "zone_slug", "rma1"), 56 | ), 57 | }, 58 | { 59 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_name_and_zone(name2, "rma1"), 60 | Check: resource.ComposeTestCheckFunc( 61 | resource.TestCheckResourceAttr( 62 | "data.cloudscale_load_balancer.foo", "name", name2), 63 | resource.TestCheckResourceAttr( 64 | "data.cloudscale_load_balancer.foo", "zone_slug", "rma1"), 65 | ), 66 | }, 67 | { 68 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_name_and_zone(name1, "lpg1"), 69 | ExpectError: regexp.MustCompile(`Found zero load balancers`), 70 | }, 71 | { 72 | 73 | Config: config + testAccCheckCloudscaleLoadBalancerConfig_id(), 74 | Check: resource.ComposeTestCheckFunc( 75 | resource.TestCheckResourceAttr( 76 | "cloudscale_load_balancer.basic.0", "name", name1), 77 | resource.TestCheckResourceAttr( 78 | "data.cloudscale_load_balancer.foo", "name", name1), 79 | resource.TestCheckResourceAttrPtr( 80 | "cloudscale_load_balancer.basic.0", "id", &loadBalancer.UUID), 81 | resource.TestCheckResourceAttrPtr( 82 | "data.cloudscale_load_balancer.foo", "id", &loadBalancer.UUID), 83 | ), 84 | }, 85 | { 86 | Config: config + "\n" + `data "cloudscale_load_balancer" "foo" {}`, 87 | ExpectError: regexp.MustCompile(`Found \d+ load balancers, expected one`), 88 | }, 89 | }, 90 | }) 91 | } 92 | 93 | func loadBalancerConfig_baseline(count int, rInt int) string { 94 | return fmt.Sprintf(` 95 | resource "cloudscale_load_balancer" "basic" { 96 | count = %d 97 | name = "terraform-%d-${count.index}" 98 | flavor_slug = "lb-standard" 99 | zone_slug = "rma1" 100 | }`, count, rInt) 101 | } 102 | 103 | func testAccCheckCloudscaleLoadBalancerConfig_name(name string) string { 104 | return fmt.Sprintf(` 105 | data "cloudscale_load_balancer" "foo" { 106 | name = "%s" 107 | } 108 | `, name) 109 | } 110 | 111 | func testAccCheckCloudscaleLoadBalancerConfig_name_and_zone(name, zone_slug string) string { 112 | return fmt.Sprintf(` 113 | data "cloudscale_load_balancer" "foo" { 114 | name = "%s" 115 | zone_slug = "%s" 116 | } 117 | `, name, zone_slug) 118 | } 119 | 120 | func testAccCheckCloudscaleLoadBalancerConfig_id() string { 121 | return fmt.Sprintf(` 122 | data "cloudscale_load_balancer" "foo" { 123 | id = "${cloudscale_load_balancer.basic.0.id}" 124 | } 125 | `) 126 | } 127 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_network.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleNetwork() *schema.Resource { 11 | recordSchema := getNetworkSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("networks", recordSchema, getFetchFunc( 15 | listNetworks, 16 | gatherNetworkResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listNetworks(d *schema.ResourceData, meta any) ([]cloudscale.Network, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.Networks.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_network_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleNetwork_DS_Basic(t *testing.T) { 14 | var network cloudscale.Network 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := networkConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleNetworkDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleNetworkConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleNetworkExists("data.cloudscale_network.foo", &network), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_network.basic.0", "id", &network.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_network.foo", "id", &network.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_network.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_network.foo", "mtu", "1500"), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_network.foo", "zone_slug", "rma1"), 42 | resource.TestCheckResourceAttrSet( 43 | "data.cloudscale_network.foo", "subnets.0.uuid"), 44 | resource.TestCheckResourceAttrSet( 45 | "data.cloudscale_network.foo", "subnets.0.cidr"), 46 | resource.TestCheckResourceAttr( 47 | "data.cloudscale_network.foo", "subnets.#", "1"), 48 | resource.TestCheckResourceAttrSet( 49 | "data.cloudscale_network.foo", "href"), 50 | ), 51 | }, 52 | { 53 | Config: config + testAccCheckCloudscaleNetworkConfig_name_and_zone(name1, "rma1"), 54 | Check: resource.ComposeTestCheckFunc( 55 | resource.TestCheckResourceAttr( 56 | "data.cloudscale_network.foo", "name", name1), 57 | ), 58 | }, 59 | { 60 | Config: config + testAccCheckCloudscaleNetworkConfig_name(name2), 61 | Check: resource.ComposeTestCheckFunc( 62 | resource.TestCheckResourceAttr( 63 | "data.cloudscale_network.foo", "name", name2), 64 | resource.TestCheckResourceAttr( 65 | "data.cloudscale_network.foo", "mtu", "1500"), 66 | resource.TestCheckResourceAttr( 67 | "data.cloudscale_network.foo", "zone_slug", "rma1"), 68 | resource.TestCheckResourceAttrSet( 69 | "data.cloudscale_network.foo", "subnets.0.cidr"), 70 | resource.TestCheckResourceAttrSet( 71 | "data.cloudscale_network.foo", "subnets.0.cidr"), 72 | resource.TestCheckResourceAttr( 73 | "data.cloudscale_network.foo", "subnets.#", "1"), 74 | ), 75 | }, 76 | { 77 | Config: config + testAccCheckCloudscaleNetworkConfig_name_and_zone(name2, "rma1"), 78 | Check: resource.ComposeTestCheckFunc( 79 | resource.TestCheckResourceAttr( 80 | "data.cloudscale_network.foo", "name", name2), 81 | resource.TestCheckResourceAttr( 82 | "data.cloudscale_network.foo", "zone_slug", "rma1"), 83 | ), 84 | }, 85 | { 86 | Config: config + testAccCheckCloudscaleNetworkConfig_name_and_zone(name1, "lpg1"), 87 | ExpectError: regexp.MustCompile(`Found zero networks`), 88 | }, 89 | { 90 | 91 | Config: config + testAccCheckCloudscaleNetworkConfig_id(), 92 | Check: resource.ComposeTestCheckFunc( 93 | resource.TestCheckResourceAttr( 94 | "cloudscale_network.basic.0", "name", name1), 95 | resource.TestCheckResourceAttr( 96 | "data.cloudscale_network.foo", "name", name1), 97 | resource.TestCheckResourceAttrPtr( 98 | "cloudscale_network.basic.0", "id", &network.UUID), 99 | resource.TestCheckResourceAttrPtr( 100 | "data.cloudscale_network.foo", "id", &network.UUID), 101 | ), 102 | }, 103 | { 104 | Config: config + "\n" + `data "cloudscale_network" "foo" {}`, 105 | ExpectError: regexp.MustCompile(`Found \d+ networks, expected one`), 106 | }, 107 | }, 108 | }) 109 | } 110 | 111 | func TestAccCloudscaleNetwork_DS_NotExisting(t *testing.T) { 112 | resource.ParallelTest(t, resource.TestCase{ 113 | PreCheck: func() { testAccPreCheck(t) }, 114 | Providers: testAccProviders, 115 | Steps: []resource.TestStep{ 116 | { 117 | Config: testAccCheckCloudscaleNetworkConfig_name("terraform-unknown"), 118 | ExpectError: regexp.MustCompile(`Found zero networks`), 119 | }, 120 | }, 121 | }) 122 | } 123 | 124 | func testAccCheckCloudscaleNetworkConfig_name(name string) string { 125 | return fmt.Sprintf(` 126 | data "cloudscale_network" "foo" { 127 | name = "%s" 128 | } 129 | `, name) 130 | } 131 | 132 | func testAccCheckCloudscaleNetworkConfig_name_and_zone(name, zone_slug string) string { 133 | return fmt.Sprintf(` 134 | data "cloudscale_network" "foo" { 135 | name = "%s" 136 | zone_slug = "%s" 137 | } 138 | `, name, zone_slug) 139 | } 140 | 141 | func testAccCheckCloudscaleNetworkConfig_id() string { 142 | return fmt.Sprintf(` 143 | data "cloudscale_network" "foo" { 144 | id = "${cloudscale_network.basic.0.id}" 145 | } 146 | `) 147 | } 148 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_objects_user.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleObjectsUser() *schema.Resource { 11 | recordSchema := getObjectsUserSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("Objects Users", recordSchema, getFetchFunc( 15 | listObjectsUsers, 16 | gatherObjectsUserResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listObjectsUsers(d *schema.ResourceData, meta any) ([]cloudscale.ObjectsUser, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.ObjectsUsers.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_objects_user_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleObjectsUser_DS_Basic(t *testing.T) { 14 | var objectsUser cloudscale.ObjectsUser 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := objectsUserConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleObjectsUserDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleObjectsUserConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleObjectsUserExists("data.cloudscale_objects_user.foo", &objectsUser), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_objects_user.basic.0", "id", &objectsUser.ID), 34 | resource.TestCheckResourceAttrPtr( 35 | "cloudscale_objects_user.basic.0", "user_id", &objectsUser.ID), 36 | resource.TestCheckResourceAttrPtr( 37 | "data.cloudscale_objects_user.foo", "id", &objectsUser.ID), 38 | resource.TestCheckResourceAttrPtr( 39 | "data.cloudscale_objects_user.foo", "user_id", &objectsUser.ID), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_objects_user.foo", "display_name", name1), 42 | resource.TestCheckResourceAttr( 43 | "data.cloudscale_objects_user.foo", "keys.#", "1"), 44 | resource.TestCheckResourceAttrSet( 45 | "data.cloudscale_objects_user.foo", "href"), 46 | ), 47 | }, 48 | { 49 | Config: config + testAccCheckCloudscaleObjectsUserConfig_name(name2), 50 | Check: resource.ComposeTestCheckFunc( 51 | resource.TestCheckResourceAttr( 52 | "data.cloudscale_objects_user.foo", "display_name", name2), 53 | ), 54 | }, 55 | { 56 | 57 | Config: config + testAccCheckCloudscaleObjectsUserConfig_id(), 58 | Check: resource.ComposeTestCheckFunc( 59 | resource.TestCheckResourceAttr( 60 | "cloudscale_objects_user.basic.0", "display_name", name1), 61 | resource.TestCheckResourceAttr( 62 | "data.cloudscale_objects_user.foo", "display_name", name1), 63 | resource.TestCheckResourceAttrPtr( 64 | "cloudscale_objects_user.basic.0", "id", &objectsUser.ID), 65 | resource.TestCheckResourceAttrPtr( 66 | "data.cloudscale_objects_user.foo", "id", &objectsUser.ID), 67 | ), 68 | }, 69 | { 70 | 71 | Config: config + testAccCheckCloudscaleObjectsUserConfig_user_id(), 72 | Check: resource.ComposeTestCheckFunc( 73 | resource.TestCheckResourceAttr( 74 | "cloudscale_objects_user.basic.0", "display_name", name1), 75 | resource.TestCheckResourceAttr( 76 | "data.cloudscale_objects_user.foo", "display_name", name1), 77 | resource.TestCheckResourceAttrPtr( 78 | "cloudscale_objects_user.basic.0", "id", &objectsUser.ID), 79 | resource.TestCheckResourceAttrPtr( 80 | "data.cloudscale_objects_user.foo", "id", &objectsUser.ID), 81 | ), 82 | }, 83 | { 84 | Config: config + "\n" + `data "cloudscale_objects_user" "foo" {}`, 85 | ExpectError: regexp.MustCompile(`Found \d+ Objects Users, expected one`), 86 | }, 87 | }, 88 | }) 89 | } 90 | 91 | func TestAccCloudscaleObjectsUser_DS_NotExisting(t *testing.T) { 92 | resource.ParallelTest(t, resource.TestCase{ 93 | PreCheck: func() { testAccPreCheck(t) }, 94 | Providers: testAccProviders, 95 | Steps: []resource.TestStep{ 96 | { 97 | Config: testAccCheckCloudscaleObjectsUserConfig_name("terraform-unknown"), 98 | ExpectError: regexp.MustCompile(`Found zero Objects Users`), 99 | }, 100 | }, 101 | }) 102 | } 103 | 104 | func testAccCheckCloudscaleObjectsUserConfig_name(display_name string) string { 105 | return fmt.Sprintf(` 106 | data "cloudscale_objects_user" "foo" { 107 | display_name = "%s" 108 | } 109 | `, display_name) 110 | } 111 | 112 | func testAccCheckCloudscaleObjectsUserConfig_id() string { 113 | return fmt.Sprintf(` 114 | data "cloudscale_objects_user" "foo" { 115 | id = "${cloudscale_objects_user.basic.0.id}" 116 | } 117 | `) 118 | } 119 | 120 | func testAccCheckCloudscaleObjectsUserConfig_user_id() string { 121 | return fmt.Sprintf(` 122 | data "cloudscale_objects_user" "foo" { 123 | user_id = "${cloudscale_objects_user.basic.0.user_id}" 124 | } 125 | `) 126 | } 127 | 128 | func objectsUserConfig_baseline(count int, rInt int) string { 129 | return fmt.Sprintf(` 130 | resource "cloudscale_objects_user" "basic" { 131 | count = "%v" 132 | display_name = "terraform-%d-${count.index}" 133 | }`, count, rInt) 134 | } 135 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_server.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleServer() *schema.Resource { 11 | recordSchema := getServerSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("servers", recordSchema, getFetchFunc( 15 | listServers, 16 | gatherServerResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listServers(d *schema.ResourceData, meta any) ([]cloudscale.Server, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.Servers.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_server_group.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleServerGroup() *schema.Resource { 11 | recordSchema := getServerGroupSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("server groups", recordSchema, getFetchFunc( 15 | listServerGroups, 16 | gatherServerGroupResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listServerGroups(d *schema.ResourceData, meta any) ([]cloudscale.ServerGroup, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.ServerGroups.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_server_group_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleServerGroup_DS_Basic(t *testing.T) { 14 | var serverGroup cloudscale.ServerGroup 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := serverGroupConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleServerGroupDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleServerGroupConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleServerGroupExists("data.cloudscale_server_group.foo", &serverGroup), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_server_group.basic.0", "id", &serverGroup.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_server_group.foo", "id", &serverGroup.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_server_group.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_server_group.foo", "type", "anti-affinity"), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_server_group.foo", "zone_slug", "rma1"), 42 | ), 43 | }, 44 | { 45 | Config: config + testAccCheckCloudscaleServerGroupConfig_name_and_zone(name1, "rma1"), 46 | Check: resource.ComposeTestCheckFunc( 47 | resource.TestCheckResourceAttr( 48 | "data.cloudscale_server_group.foo", "name", name1), 49 | ), 50 | }, 51 | { 52 | Config: config + testAccCheckCloudscaleServerGroupConfig_name(name2), 53 | Check: resource.ComposeTestCheckFunc( 54 | resource.TestCheckResourceAttr( 55 | "data.cloudscale_server_group.foo", "name", name2), 56 | resource.TestCheckResourceAttr( 57 | "data.cloudscale_server_group.foo", "type", "anti-affinity"), 58 | resource.TestCheckResourceAttr( 59 | "data.cloudscale_server_group.foo", "zone_slug", "rma1"), 60 | ), 61 | }, 62 | { 63 | Config: config + testAccCheckCloudscaleServerGroupConfig_name_and_zone(name2, "rma1"), 64 | Check: resource.ComposeTestCheckFunc( 65 | resource.TestCheckResourceAttr( 66 | "data.cloudscale_server_group.foo", "name", name2), 67 | resource.TestCheckResourceAttr( 68 | "data.cloudscale_server_group.foo", "zone_slug", "rma1"), 69 | ), 70 | }, 71 | { 72 | Config: config + testAccCheckCloudscaleServerGroupConfig_name_and_zone(name1, "lpg1"), 73 | ExpectError: regexp.MustCompile(`Found zero server groups`), 74 | }, 75 | { 76 | 77 | Config: config + testAccCheckCloudscaleServerGroupConfig_id(), 78 | Check: resource.ComposeTestCheckFunc( 79 | resource.TestCheckResourceAttr( 80 | "cloudscale_server_group.basic.0", "name", name1), 81 | resource.TestCheckResourceAttr( 82 | "data.cloudscale_server_group.foo", "name", name1), 83 | resource.TestCheckResourceAttrPtr( 84 | "cloudscale_server_group.basic.0", "id", &serverGroup.UUID), 85 | resource.TestCheckResourceAttrPtr( 86 | "data.cloudscale_server_group.foo", "id", &serverGroup.UUID), 87 | ), 88 | }, 89 | { 90 | Config: config + "\n" + `data "cloudscale_server_group" "foo" {}`, 91 | ExpectError: regexp.MustCompile(`Found \d+ server groups, expected one`), 92 | }, 93 | }, 94 | }) 95 | } 96 | 97 | func TestAccCloudscaleServerGroup_DS_NotExisting(t *testing.T) { 98 | resource.ParallelTest(t, resource.TestCase{ 99 | PreCheck: func() { testAccPreCheck(t) }, 100 | Providers: testAccProviders, 101 | Steps: []resource.TestStep{ 102 | { 103 | Config: testAccCheckCloudscaleServerGroupConfig_name("terraform-unknown"), 104 | ExpectError: regexp.MustCompile(`Found zero server groups`), 105 | }, 106 | }, 107 | }) 108 | } 109 | 110 | func serverGroupConfig_baseline(count int, rInt int) string { 111 | return fmt.Sprintf(` 112 | resource "cloudscale_server_group" "basic" { 113 | count = "%v" 114 | name = "terraform-%d-${count.index}" 115 | type = "anti-affinity" 116 | zone_slug = "rma1" 117 | }`, count, rInt) 118 | } 119 | 120 | func testAccCheckCloudscaleServerGroupConfig_name(name string) string { 121 | return fmt.Sprintf(` 122 | data "cloudscale_server_group" "foo" { 123 | name = "%s" 124 | } 125 | `, name) 126 | } 127 | 128 | func testAccCheckCloudscaleServerGroupConfig_name_and_zone(name, zone_slug string) string { 129 | return fmt.Sprintf(` 130 | data "cloudscale_server_group" "foo" { 131 | name = "%s" 132 | zone_slug = "%s" 133 | } 134 | `, name, zone_slug) 135 | } 136 | 137 | func testAccCheckCloudscaleServerGroupConfig_id() string { 138 | return fmt.Sprintf(` 139 | data "cloudscale_server_group" "foo" { 140 | id = "${cloudscale_server_group.basic.0.id}" 141 | } 142 | `) 143 | } 144 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_server_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleServer_DS_Basic(t *testing.T) { 14 | var server cloudscale.Server 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := serverConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleServerDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleServerConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleServerExists("data.cloudscale_server.foo", &server), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_server.basic.0", "id", &server.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_server.foo", "id", &server.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_server.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_server.foo", "status", "running"), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_server.foo", "flavor_slug", "flex-4-1"), 42 | resource.TestCheckResourceAttr( 43 | "data.cloudscale_server.foo", "image_slug", DefaultImageSlug), 44 | resource.TestCheckResourceAttr( 45 | "data.cloudscale_server.foo", "interfaces.0.type", "public"), 46 | resource.TestCheckResourceAttr( 47 | "data.cloudscale_server.foo", "ssh_host_keys.#", "4"), 48 | testAccCheckServerIp("data.cloudscale_server.foo"), 49 | resource.TestCheckResourceAttr( 50 | "data.cloudscale_server.foo", "zone_slug", "rma1"), 51 | resource.TestCheckResourceAttrSet( 52 | "data.cloudscale_server.foo", "href"), 53 | ), 54 | }, 55 | { 56 | Config: config + testAccCheckCloudscaleServerConfig_name_and_zone(name1, "rma1"), 57 | Check: resource.ComposeTestCheckFunc( 58 | resource.TestCheckResourceAttr( 59 | "data.cloudscale_server.foo", "name", name1), 60 | ), 61 | }, 62 | { 63 | Config: config + testAccCheckCloudscaleServerConfig_name(name2), 64 | Check: resource.ComposeTestCheckFunc( 65 | resource.TestCheckResourceAttr( 66 | "data.cloudscale_server.foo", "name", name2), 67 | resource.TestCheckResourceAttr( 68 | "data.cloudscale_server.foo", "zone_slug", "rma1"), 69 | ), 70 | }, 71 | { 72 | Config: config + testAccCheckCloudscaleServerConfig_name_and_zone(name2, "rma1"), 73 | Check: resource.ComposeTestCheckFunc( 74 | resource.TestCheckResourceAttr( 75 | "data.cloudscale_server.foo", "name", name2), 76 | resource.TestCheckResourceAttr( 77 | "data.cloudscale_server.foo", "zone_slug", "rma1"), 78 | ), 79 | }, 80 | { 81 | Config: config + testAccCheckCloudscaleServerConfig_name_and_zone(name1, "lpg1"), 82 | ExpectError: regexp.MustCompile(`Found zero servers`), 83 | }, 84 | { 85 | 86 | Config: config + testAccCheckCloudscaleServerConfig_id(), 87 | Check: resource.ComposeTestCheckFunc( 88 | resource.TestCheckResourceAttr( 89 | "cloudscale_server.basic.0", "name", name1), 90 | resource.TestCheckResourceAttr( 91 | "data.cloudscale_server.foo", "name", name1), 92 | resource.TestCheckResourceAttrPtr( 93 | "cloudscale_server.basic.0", "id", &server.UUID), 94 | resource.TestCheckResourceAttrPtr( 95 | "data.cloudscale_server.foo", "id", &server.UUID), 96 | ), 97 | }, 98 | { 99 | Config: config + "\n" + `data "cloudscale_server" "foo" {}`, 100 | ExpectError: regexp.MustCompile(`Found \d+ servers, expected one`), 101 | }, 102 | }, 103 | }) 104 | } 105 | 106 | func TestAccCloudscaleServer_DS_NotExisting(t *testing.T) { 107 | resource.ParallelTest(t, resource.TestCase{ 108 | PreCheck: func() { testAccPreCheck(t) }, 109 | Providers: testAccProviders, 110 | Steps: []resource.TestStep{ 111 | { 112 | Config: testAccCheckCloudscaleServerConfig_name("terraform-unknown"), 113 | ExpectError: regexp.MustCompile(`Found zero servers`), 114 | }, 115 | }, 116 | }) 117 | } 118 | 119 | func testAccCheckCloudscaleServerConfig_name(name string) string { 120 | return fmt.Sprintf(` 121 | data "cloudscale_server" "foo" { 122 | name = "%s" 123 | } 124 | `, name) 125 | } 126 | 127 | func testAccCheckCloudscaleServerConfig_name_and_zone(name, zone_slug string) string { 128 | return fmt.Sprintf(` 129 | data "cloudscale_server" "foo" { 130 | name = "%s" 131 | zone_slug = "%s" 132 | } 133 | `, name, zone_slug) 134 | } 135 | 136 | func testAccCheckCloudscaleServerConfig_id() string { 137 | return fmt.Sprintf(` 138 | data "cloudscale_server" "foo" { 139 | id = "${cloudscale_server.basic.0.id}" 140 | } 141 | `) 142 | } 143 | 144 | func serverConfig_baseline(count, rInt int) string { 145 | return fmt.Sprintf(` 146 | resource "cloudscale_server" "basic" { 147 | count = %d 148 | name = "terraform-%d-${count.index}" 149 | flavor_slug = "flex-4-1" 150 | allow_stopping_for_update = true 151 | image_slug = "%s" 152 | volume_size_gb = 10 153 | zone_slug = "rma1" 154 | ssh_keys = ["ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY=", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY="] 155 | }`, count, rInt, DefaultImageSlug) 156 | } 157 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_subnet.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleSubnet() *schema.Resource { 11 | recordSchema := getSubnetSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("subnets", recordSchema, getFetchFunc( 15 | listSubnets, 16 | gatherSubnetResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listSubnets(d *schema.ResourceData, meta any) ([]cloudscale.Subnet, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.Subnets.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_subnet_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleSubnet_DS_Basic(t *testing.T) { 14 | var subnet cloudscale.Subnet 15 | rInt := acctest.RandInt() 16 | uniqueInt := 1 17 | cidr1 := fmt.Sprintf("10.%d.0.0/24", uniqueInt) 18 | cidr2 := fmt.Sprintf("10.%d.1.0/24", uniqueInt) 19 | config := subnetConfig_baseline(2, rInt, uniqueInt) 20 | 21 | resource.ParallelTest(t, resource.TestCase{ 22 | PreCheck: func() { testAccPreCheck(t) }, 23 | Providers: testAccProviders, 24 | CheckDestroy: testAccCheckCloudscaleSubnetDestroy, 25 | Steps: []resource.TestStep{ 26 | { 27 | Config: config, 28 | }, 29 | { 30 | Config: config + testAccCheckCloudscaleSubnetConfig_cidr(cidr1), 31 | Check: resource.ComposeTestCheckFunc( 32 | testAccCheckCloudscaleSubnetExists("data.cloudscale_subnet.foo", &subnet), 33 | resource.TestCheckResourceAttrPtr( 34 | "cloudscale_subnet.multi-subnet.0", "id", &subnet.UUID), 35 | resource.TestCheckResourceAttrPtr( 36 | "data.cloudscale_subnet.foo", "id", &subnet.UUID), 37 | resource.TestCheckResourceAttr( 38 | "data.cloudscale_subnet.foo", "cidr", cidr1), 39 | resource.TestCheckResourceAttr( 40 | "data.cloudscale_subnet.foo", "gateway_address", ""), 41 | resource.TestCheckResourceAttr( 42 | "data.cloudscale_subnet.foo", "network_name", fmt.Sprintf(`terraform-%d-0`, rInt)), 43 | resource.TestCheckResourceAttrSet( 44 | "data.cloudscale_subnet.foo", "network_uuid"), 45 | resource.TestCheckResourceAttr( 46 | "data.cloudscale_subnet.foo", "dns_servers.#", "2"), 47 | ), 48 | }, 49 | { 50 | Config: config + testAccCheckCloudscaleSubnetConfig_cidr_and_gateway(cidr1, ""), 51 | Check: resource.ComposeTestCheckFunc( 52 | resource.TestCheckResourceAttr( 53 | "data.cloudscale_subnet.foo", "cidr", cidr1), 54 | ), 55 | }, 56 | { 57 | Config: config + testAccCheckCloudscaleSubnetConfig_cidr(cidr2), 58 | Check: resource.ComposeTestCheckFunc( 59 | resource.TestCheckResourceAttr( 60 | "data.cloudscale_subnet.foo", "cidr", cidr2), 61 | resource.TestCheckResourceAttr( 62 | "data.cloudscale_subnet.foo", "gateway_address", ""), 63 | ), 64 | }, 65 | { 66 | Config: config + testAccCheckCloudscaleSubnetConfig_cidr_and_gateway(cidr1, ""), 67 | Check: resource.ComposeTestCheckFunc( 68 | resource.TestCheckResourceAttr( 69 | "data.cloudscale_subnet.foo", "cidr", cidr1), 70 | resource.TestCheckResourceAttr( 71 | "data.cloudscale_subnet.foo", "gateway_address", ""), 72 | ), 73 | }, 74 | { 75 | Config: config + testAccCheckCloudscaleSubnetConfig_cidr_and_gateway(cidr1, "1.1.1.1"), 76 | ExpectError: regexp.MustCompile(`.*Found zero subnets.*`), 77 | }, 78 | { 79 | Config: config + testAccCheckCloudscaleSubnetConfig_id(), 80 | Check: resource.ComposeTestCheckFunc( 81 | resource.TestCheckResourceAttr( 82 | "cloudscale_subnet.multi-subnet.0", "cidr", cidr1), 83 | resource.TestCheckResourceAttr( 84 | "data.cloudscale_subnet.foo", "cidr", cidr1), 85 | resource.TestCheckResourceAttrPtr( 86 | "cloudscale_subnet.multi-subnet.0", "id", &subnet.UUID), 87 | resource.TestCheckResourceAttrPtr( 88 | "data.cloudscale_subnet.foo", "id", &subnet.UUID), 89 | ), 90 | }, 91 | { 92 | Config: config + testAccCheckCloudscaleSubnetConfig_network_uuid(), 93 | Check: resource.ComposeTestCheckFunc( 94 | resource.TestCheckResourceAttr( 95 | "data.cloudscale_subnet.foo", "cidr", cidr1), 96 | ), 97 | }, 98 | { 99 | Config: config + testAccCheckCloudscaleSubnetConfig_network_name(), 100 | Check: resource.ComposeTestCheckFunc( 101 | resource.TestCheckResourceAttr( 102 | "data.cloudscale_subnet.foo", "cidr", cidr1), 103 | ), 104 | }, 105 | { 106 | Config: config + "\n" + `data "cloudscale_subnet" "foo" {}`, 107 | ExpectError: regexp.MustCompile(`Found \d+ subnets, expected one`), 108 | }, 109 | }, 110 | }) 111 | } 112 | 113 | func TestAccCloudscaleSubnet_DS_NotExisting(t *testing.T) { 114 | resource.ParallelTest(t, resource.TestCase{ 115 | PreCheck: func() { testAccPreCheck(t) }, 116 | Providers: testAccProviders, 117 | Steps: []resource.TestStep{ 118 | { 119 | Config: testAccCheckCloudscaleSubnetConfig_cidr("terraform-unknown-subnet"), 120 | ExpectError: regexp.MustCompile(`.*Found zero subnets.*`), 121 | }, 122 | }, 123 | }) 124 | } 125 | 126 | func testAccCheckCloudscaleSubnetConfig_cidr(cidr string) string { 127 | return fmt.Sprintf(` 128 | data "cloudscale_subnet" "foo" { 129 | cidr = "%s" 130 | } 131 | `, cidr) 132 | } 133 | 134 | func testAccCheckCloudscaleSubnetConfig_cidr_and_gateway(cidr, gateway string) string { 135 | return fmt.Sprintf(` 136 | data "cloudscale_subnet" "foo" { 137 | cidr = "%s" 138 | gateway_address = "%s" 139 | } 140 | `, cidr, gateway) 141 | } 142 | 143 | func testAccCheckCloudscaleSubnetConfig_id() string { 144 | return fmt.Sprintf(` 145 | data "cloudscale_subnet" "foo" { 146 | id = "${cloudscale_subnet.multi-subnet.0.id}" 147 | } 148 | `) 149 | } 150 | 151 | func testAccCheckCloudscaleSubnetConfig_network_uuid() string { 152 | return ` 153 | data "cloudscale_subnet" "foo" { 154 | network_uuid = "${cloudscale_network.multi-net.0.id}" 155 | } 156 | ` 157 | } 158 | 159 | func testAccCheckCloudscaleSubnetConfig_network_name() string { 160 | return ` 161 | data "cloudscale_subnet" "foo" { 162 | network_name = "${cloudscale_network.multi-net.0.name}" 163 | } 164 | ` 165 | } 166 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_volume.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func dataSourceCloudscaleVolume() *schema.Resource { 11 | recordSchema := getVolumeSchema(DATA_SOURCE) 12 | 13 | return &schema.Resource{ 14 | ReadContext: dataSourceResourceRead("volumes", recordSchema, getFetchFunc( 15 | listVolumes, 16 | gatherVolumeResourceData, 17 | )), 18 | Schema: recordSchema, 19 | } 20 | } 21 | 22 | func listVolumes(d *schema.ResourceData, meta any) ([]cloudscale.Volume, error) { 23 | client := meta.(*cloudscale.Client) 24 | return client.Volumes.List(context.Background()) 25 | } 26 | -------------------------------------------------------------------------------- /cloudscale/datasource_cloudscale_volume_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccCloudscaleVolume_DS_Basic(t *testing.T) { 14 | var volume cloudscale.Volume 15 | rInt := acctest.RandInt() 16 | name1 := fmt.Sprintf("terraform-%d-0", rInt) 17 | name2 := fmt.Sprintf("terraform-%d-1", rInt) 18 | config := volumeConfig_baseline(2, rInt) 19 | 20 | resource.ParallelTest(t, resource.TestCase{ 21 | PreCheck: func() { testAccPreCheck(t) }, 22 | Providers: testAccProviders, 23 | CheckDestroy: testAccCheckCloudscaleVolumeDestroy, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: config, 27 | }, 28 | { 29 | Config: config + testAccCheckCloudscaleVolumeConfig_name(name1), 30 | Check: resource.ComposeTestCheckFunc( 31 | testAccCheckCloudscaleVolumeExists("data.cloudscale_volume.foo", &volume), 32 | resource.TestCheckResourceAttrPtr( 33 | "cloudscale_volume.basic.0", "id", &volume.UUID), 34 | resource.TestCheckResourceAttrPtr( 35 | "data.cloudscale_volume.foo", "id", &volume.UUID), 36 | resource.TestCheckResourceAttr( 37 | "data.cloudscale_volume.foo", "name", name1), 38 | resource.TestCheckResourceAttr( 39 | "data.cloudscale_volume.foo", "size_gb", "1"), 40 | resource.TestCheckResourceAttr( 41 | "data.cloudscale_volume.foo", "type", "ssd"), 42 | resource.TestCheckResourceAttr( 43 | "data.cloudscale_volume.foo", "zone_slug", "rma1"), 44 | resource.TestCheckResourceAttr( 45 | "data.cloudscale_volume.foo", "server_uuids.#", "0"), 46 | resource.TestCheckResourceAttrSet( 47 | "data.cloudscale_volume.foo", "href"), 48 | ), 49 | }, 50 | { 51 | Config: config + testAccCheckCloudscaleVolumeConfig_name_and_zone(name1, "rma1"), 52 | Check: resource.ComposeTestCheckFunc( 53 | resource.TestCheckResourceAttr( 54 | "data.cloudscale_volume.foo", "name", name1), 55 | ), 56 | }, 57 | { 58 | Config: config + testAccCheckCloudscaleVolumeConfig_name(name2), 59 | Check: resource.ComposeTestCheckFunc( 60 | resource.TestCheckResourceAttr( 61 | "data.cloudscale_volume.foo", "name", name2), 62 | resource.TestCheckResourceAttr( 63 | "data.cloudscale_volume.foo", "size_gb", "1"), 64 | resource.TestCheckResourceAttr( 65 | "data.cloudscale_volume.foo", "zone_slug", "rma1"), 66 | resource.TestCheckResourceAttr( 67 | "data.cloudscale_volume.foo", "server_uuids.#", "0"), 68 | ), 69 | }, 70 | { 71 | Config: config + testAccCheckCloudscaleVolumeConfig_name_and_zone(name2, "rma1"), 72 | Check: resource.ComposeTestCheckFunc( 73 | resource.TestCheckResourceAttr( 74 | "data.cloudscale_volume.foo", "name", name2), 75 | resource.TestCheckResourceAttr( 76 | "data.cloudscale_volume.foo", "zone_slug", "rma1"), 77 | ), 78 | }, 79 | { 80 | Config: config + testAccCheckCloudscaleVolumeConfig_name_and_zone(name1, "lpg1"), 81 | ExpectError: regexp.MustCompile(`.*Found zero volumes.*`), 82 | }, 83 | { 84 | 85 | Config: config + testAccCheckCloudscaleVolumeConfig_id(), 86 | Check: resource.ComposeTestCheckFunc( 87 | resource.TestCheckResourceAttr( 88 | "cloudscale_volume.basic.0", "name", name1), 89 | resource.TestCheckResourceAttr( 90 | "data.cloudscale_volume.foo", "name", name1), 91 | resource.TestCheckResourceAttrPtr( 92 | "cloudscale_volume.basic.0", "id", &volume.UUID), 93 | resource.TestCheckResourceAttrPtr( 94 | "data.cloudscale_volume.foo", "id", &volume.UUID), 95 | ), 96 | }, 97 | { 98 | Config: config + "\n" + `data "cloudscale_volume" "foo" {}`, 99 | ExpectError: regexp.MustCompile(`Found \d+ volumes, expected one`), 100 | }, 101 | }, 102 | }) 103 | } 104 | 105 | func TestAccCloudscaleVolume_DS_NotExisting(t *testing.T) { 106 | resource.ParallelTest(t, resource.TestCase{ 107 | PreCheck: func() { testAccPreCheck(t) }, 108 | Providers: testAccProviders, 109 | Steps: []resource.TestStep{ 110 | { 111 | Config: testAccCheckCloudscaleVolumeConfig_name("terraform-unknown-volume"), 112 | ExpectError: regexp.MustCompile(`Found zero volumes`), 113 | }, 114 | }, 115 | }) 116 | } 117 | 118 | func volumeConfig_baseline(count int, rInt int) string { 119 | return fmt.Sprintf(` 120 | resource "cloudscale_volume" "basic" { 121 | count = "%v" 122 | name = "terraform-%d-${count.index}" 123 | size_gb = 1 124 | type = "ssd" 125 | zone_slug = "rma1" 126 | }`, count, rInt) 127 | } 128 | 129 | func testAccCheckCloudscaleVolumeConfig_name(name string) string { 130 | return fmt.Sprintf(` 131 | data "cloudscale_volume" "foo" { 132 | name = "%s" 133 | } 134 | `, name) 135 | } 136 | 137 | func testAccCheckCloudscaleVolumeConfig_name_and_zone(name, zone_slug string) string { 138 | return fmt.Sprintf(` 139 | data "cloudscale_volume" "foo" { 140 | name = "%s" 141 | zone_slug = "%s" 142 | } 143 | `, name, zone_slug) 144 | } 145 | 146 | func testAccCheckCloudscaleVolumeConfig_id() string { 147 | return fmt.Sprintf(` 148 | data "cloudscale_volume" "foo" { 149 | id = "${cloudscale_volume.basic.0.id}" 150 | } 151 | `) 152 | } 153 | -------------------------------------------------------------------------------- /cloudscale/datasources.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | type ResourceDataRaw = map[string]any 11 | 12 | func fillResourceData(d *schema.ResourceData, map_ ResourceDataRaw) { 13 | for k, v := range map_ { 14 | if k != "id" { 15 | d.Set(k, v) 16 | } 17 | } 18 | } 19 | 20 | func dataSourceResourceRead( 21 | name string, 22 | sourceSchema map[string]*schema.Schema, 23 | fetchFunc func(d *schema.ResourceData, meta any) ([]ResourceDataRaw, error), 24 | ) schema.ReadContextFunc { 25 | return func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { 26 | resources, err := fetchFunc(d, meta) 27 | if err != nil { 28 | return diag.Errorf("Issue with fetching resources: %s", err) 29 | } 30 | var foundItems []map[string]any 31 | 32 | for _, m := range resources { 33 | match := true 34 | for key := range sourceSchema { 35 | if attr, ok := d.GetOk(key); ok { 36 | if m[key] != attr { 37 | match = false 38 | break 39 | } 40 | } 41 | } 42 | if match { 43 | foundItems = append(foundItems, m) 44 | } 45 | } 46 | if len(foundItems) > 1 { 47 | return diag.Errorf("Found %d %s, expected one", len(foundItems), name) 48 | } else if len(foundItems) == 0 { 49 | return diag.Errorf("Found zero %s", name) 50 | } 51 | item := foundItems[0] 52 | d.SetId(item["id"].(string)) 53 | delete(item, "id") 54 | fillResourceData(d, item) 55 | 56 | return nil 57 | } 58 | } 59 | 60 | func getFetchFunc[TResource any]( 61 | listFunc func(d *schema.ResourceData, meta any) ([]TResource, error), 62 | gatherFunc func(resource *TResource) ResourceDataRaw, 63 | ) func(d *schema.ResourceData, meta any) ([]ResourceDataRaw, error) { 64 | return func(d *schema.ResourceData, meta any) ([]ResourceDataRaw, error) { 65 | list, err := listFunc(d, meta) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | var rawItems []ResourceDataRaw 71 | for _, resource := range list { 72 | 73 | rawItems = append(rawItems, gatherFunc(&resource)) 74 | } 75 | return rawItems, nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cloudscale/provider.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | ) 6 | 7 | func Provider() *schema.Provider { 8 | return &schema.Provider{ 9 | Schema: map[string]*schema.Schema{ 10 | "token": { 11 | Type: schema.TypeString, 12 | Required: true, 13 | DefaultFunc: schema.EnvDefaultFunc("CLOUDSCALE_API_TOKEN", nil), 14 | Description: "The token for API operations.", 15 | }, 16 | }, 17 | 18 | DataSourcesMap: map[string]*schema.Resource{ 19 | "cloudscale_server": dataSourceCloudscaleServer(), 20 | "cloudscale_server_group": dataSourceCloudscaleServerGroup(), 21 | "cloudscale_volume": dataSourceCloudscaleVolume(), 22 | "cloudscale_network": dataSourceCloudscaleNetwork(), 23 | "cloudscale_subnet": dataSourceCloudscaleSubnet(), 24 | "cloudscale_floating_ip": dataSourceCloudscaleFloatingIP(), 25 | "cloudscale_objects_user": dataSourceCloudscaleObjectsUser(), 26 | "cloudscale_custom_image": dataSourceCloudscaleCustomImage(), 27 | "cloudscale_load_balancer": dataSourceCloudscaleLoadBalancer(), 28 | "cloudscale_load_balancer_pool": dataSourceCloudscaleLoadBalancerPool(), 29 | "cloudscale_load_balancer_pool_member": dataSourceCloudscaleLoadBalancerPoolMember(), 30 | "cloudscale_load_balancer_listener": dataSourceCloudscaleLoadBalancerListener(), 31 | "cloudscale_load_balancer_health_monitor": dataSourceCloudscaleLoadBalancerHealthMonitor(), 32 | }, 33 | 34 | ResourcesMap: map[string]*schema.Resource{ 35 | "cloudscale_server": resourceCloudscaleServer(), 36 | "cloudscale_server_group": resourceCloudscaleServerGroup(), 37 | "cloudscale_volume": resourceCloudscaleVolume(), 38 | "cloudscale_network": resourceCloudscaleNetwork(), 39 | "cloudscale_subnet": resourceCloudscaleSubnet(), 40 | "cloudscale_floating_ip": resourceCloudscaleFloatingIP(), 41 | "cloudscale_objects_user": resourceCloudscaleObjectsUser(), 42 | "cloudscale_custom_image": resourceCloudscaleCustomImage(), 43 | "cloudscale_load_balancer": resourceCloudscaleLoadBalancer(), 44 | "cloudscale_load_balancer_pool": resourceCloudscaleLoadBalancerPool(), 45 | "cloudscale_load_balancer_pool_member": resourceCloudscaleLoadBalancerPoolMembers(), 46 | "cloudscale_load_balancer_listener": resourceCloudscaleLoadBalancerListener(), 47 | "cloudscale_load_balancer_health_monitor": resourceCloudscaleLoadBalancerHealthMonitor(), 48 | }, 49 | ConfigureFunc: providerConfigureClient, 50 | } 51 | } 52 | 53 | func providerConfigureClient(d *schema.ResourceData) (any, error) { 54 | config := Config{ 55 | Token: d.Get("token").(string), 56 | } 57 | return config.Client() 58 | } 59 | -------------------------------------------------------------------------------- /cloudscale/provider_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "testing" 8 | "context" 9 | "strconv" 10 | 11 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 15 | ) 16 | 17 | var testAccProviders map[string]*schema.Provider 18 | var testAccProvider *schema.Provider 19 | 20 | func init() { 21 | testAccProvider = Provider() 22 | testAccProviders = map[string]*schema.Provider{ 23 | "cloudscale": testAccProvider, 24 | } 25 | } 26 | 27 | func TestProvider(t *testing.T) { 28 | if err := Provider().InternalValidate(); err != nil { 29 | t.Fatalf("err: %s", err) 30 | } 31 | } 32 | 33 | func TestProvider_impl(t *testing.T) { 34 | var _ *schema.Provider = Provider() 35 | } 36 | 37 | func testAccPreCheck(t *testing.T) { 38 | if v := os.Getenv("CLOUDSCALE_API_TOKEN"); v == "" { 39 | t.Fatal("CLOUDSCALE_API_TOKEN must be set for acceptance tests") 40 | } 41 | } 42 | 43 | func testTagsMatch(resource string) resource.TestCheckFunc { 44 | return func(s *terraform.State) error { 45 | rs, ok := s.RootModule().Resources[resource] 46 | if !ok { 47 | return fmt.Errorf("Not found: %s", resource) 48 | } 49 | 50 | attributes := rs.Primary.Attributes 51 | href, found := attributes["href"] 52 | if !found { 53 | return fmt.Errorf("No HREF found") 54 | } 55 | 56 | client := testAccProvider.Meta().(*cloudscale.Client) 57 | ctx := context.Background() 58 | req, err := client.NewRequest(ctx, http.MethodGet, href, nil) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | tagged := new(cloudscale.TaggedResource) 64 | err = client.Do(ctx, req, tagged) 65 | if err != nil { 66 | return err 67 | } 68 | in_state := attributes["tags.%"] 69 | actual := strconv.Itoa(len(tagged.Tags)) 70 | if in_state != actual { 71 | return fmt.Errorf("State has %s tags, API has %s tags", in_state, actual) 72 | } 73 | 74 | return nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_floating_ip.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const floatingIPHumanName = "Floating IP" 13 | 14 | var ( 15 | resourceFloatingIPRead = getReadOperation(floatingIPHumanName, getGenericResourceIdentifierFromSchema, readFloatingIP, gatherFloatingIPResourceData) 16 | resourceFloatingIPUpdate = getUpdateOperation(floatingIPHumanName, getGenericResourceIdentifierFromSchema, updateFloatingIP, resourceFloatingIPRead, gatherFloatingIPUpdateRequest) 17 | resourceFloatingIPDelete = getDeleteOperation(floatingIPHumanName, deleteFloatingIP) 18 | ) 19 | 20 | func resourceCloudscaleFloatingIP() *schema.Resource { 21 | return &schema.Resource{ 22 | Create: resourceFloatingIPCreate, 23 | Read: resourceFloatingIPRead, 24 | Update: resourceFloatingIPUpdate, 25 | Delete: resourceFloatingIPDelete, 26 | 27 | Importer: &schema.ResourceImporter{ 28 | StateContext: schema.ImportStatePassthroughContext, 29 | }, 30 | Schema: getFloatingIPSchema(RESOURCE), 31 | } 32 | } 33 | 34 | func getFloatingIPSchema(t SchemaType) map[string]*schema.Schema { 35 | m := map[string]*schema.Schema{ 36 | "ip_version": { 37 | Type: schema.TypeInt, 38 | Required: t.isResource(), 39 | Optional: t.isDataSource(), 40 | ForceNew: true, 41 | }, 42 | "server": { 43 | Type: schema.TypeString, 44 | Optional: t.isResource(), 45 | Computed: t.isDataSource(), 46 | }, 47 | "load_balancer": { 48 | Type: schema.TypeString, 49 | Optional: t.isResource(), 50 | Computed: t.isDataSource(), 51 | }, 52 | "region_slug": { 53 | Type: schema.TypeString, 54 | Optional: true, 55 | Computed: true, 56 | ForceNew: true, 57 | }, 58 | "type": { 59 | Type: schema.TypeString, 60 | Optional: true, 61 | Computed: true, 62 | ForceNew: true, 63 | }, 64 | "reverse_ptr": { 65 | Type: schema.TypeString, 66 | Optional: true, 67 | Computed: true, 68 | }, 69 | "prefix_length": { 70 | Type: schema.TypeInt, 71 | ForceNew: true, 72 | Optional: t.isResource(), 73 | Computed: true, 74 | }, 75 | "network": { 76 | Type: schema.TypeString, 77 | Optional: t.isDataSource(), 78 | Computed: true, 79 | }, 80 | "next_hop": { 81 | Type: schema.TypeString, 82 | Computed: true, 83 | }, 84 | "href": { 85 | Type: schema.TypeString, 86 | Computed: true, 87 | }, 88 | "tags": &TagsSchema, 89 | } 90 | if t.isDataSource() { 91 | m["id"] = &schema.Schema{ 92 | Type: schema.TypeString, 93 | Optional: true, 94 | } 95 | } 96 | return m 97 | } 98 | 99 | func resourceFloatingIPCreate(d *schema.ResourceData, meta any) error { 100 | client := meta.(*cloudscale.Client) 101 | 102 | opts := &cloudscale.FloatingIPCreateRequest{ 103 | IPVersion: d.Get("ip_version").(int), 104 | } 105 | 106 | if attr, ok := d.GetOk("server"); ok { 107 | opts.Server = attr.(string) 108 | } 109 | if attr, ok := d.GetOk("load_balancer"); ok { 110 | opts.LoadBalancer = attr.(string) 111 | } 112 | 113 | if attr, ok := d.GetOk("prefix_length"); ok { 114 | opts.PrefixLength = attr.(int) 115 | } 116 | 117 | if attr, ok := d.GetOk("reverse_ptr"); ok { 118 | opts.ReversePointer = attr.(string) 119 | } 120 | 121 | if attr, ok := d.GetOk("region_slug"); ok { 122 | opts.Region = attr.(string) 123 | } 124 | 125 | if attr, ok := d.GetOk("type"); ok { 126 | opts.Type = attr.(string) 127 | } 128 | opts.Tags = CopyTags(d) 129 | 130 | log.Printf("[DEBUG] FloatingIP create configuration: %#v", opts) 131 | 132 | floatingIP, err := client.FloatingIPs.Create(context.Background(), opts) 133 | if err != nil { 134 | return fmt.Errorf("Error creating FloatingIP: %s", err) 135 | } 136 | 137 | d.SetId(floatingIP.IP()) 138 | 139 | err = resourceFloatingIPRead(d, meta) 140 | if err != nil { 141 | return fmt.Errorf("Error reading the floating IP (%s): %s", d.Id(), err) 142 | } 143 | return nil 144 | } 145 | 146 | func gatherFloatingIPResourceData(floatingIP *cloudscale.FloatingIP) ResourceDataRaw { 147 | m := make(map[string]any) 148 | m["id"] = floatingIP.IP() 149 | m["href"] = floatingIP.HREF 150 | m["ip_version"] = floatingIP.IPVersion 151 | m["prefix_length"] = floatingIP.PrefixLength() 152 | m["network"] = floatingIP.Network 153 | m["next_hop"] = floatingIP.NextHop 154 | m["reverse_ptr"] = floatingIP.ReversePointer 155 | m["type"] = floatingIP.Type 156 | m["tags"] = floatingIP.Tags 157 | if floatingIP.Server != nil { 158 | m["server"] = floatingIP.Server.UUID 159 | } else { 160 | m["server"] = nil 161 | } 162 | if floatingIP.LoadBalancer != nil { 163 | m["load_balancer"] = floatingIP.LoadBalancer.UUID 164 | } else { 165 | m["load_balancer"] = nil 166 | } 167 | if floatingIP.Region != nil { 168 | m["region_slug"] = floatingIP.Region.Slug 169 | } 170 | 171 | return m 172 | } 173 | 174 | func readFloatingIP(rId GenericResourceIdentifier, meta any) (*cloudscale.FloatingIP, error) { 175 | client := meta.(*cloudscale.Client) 176 | return client.FloatingIPs.Get(context.Background(), rId.Id) 177 | 178 | } 179 | 180 | func updateFloatingIP(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.FloatingIPUpdateRequest) error { 181 | client := meta.(*cloudscale.Client) 182 | return client.FloatingIPs.Update(context.Background(), rId.Id, updateRequest) 183 | } 184 | 185 | func gatherFloatingIPUpdateRequest(d *schema.ResourceData) []*cloudscale.FloatingIPUpdateRequest { 186 | requests := make([]*cloudscale.FloatingIPUpdateRequest, 0) 187 | 188 | for _, attribute := range []string{"server", "load_balancer", "tags", "reverse_ptr"} { 189 | if d.HasChange(attribute) { 190 | log.Printf("[INFO] Attribute %s changed", attribute) 191 | opts := &cloudscale.FloatingIPUpdateRequest{} 192 | requests = append(requests, opts) 193 | 194 | if attribute == "reverse_ptr" { 195 | opts.ReversePointer = d.Get(attribute).(string) 196 | } else if attribute == "server" || attribute == "load_balancer" { 197 | serverUUID := d.Get("server").(string) 198 | if serverUUID != "" { 199 | log.Printf("[INFO] Assigning the Floating IP %s to the Server %s", d.Id(), serverUUID) 200 | opts.Server = serverUUID 201 | } 202 | loadBalancerUUID := d.Get("load_balancer").(string) 203 | if loadBalancerUUID != "" { 204 | log.Printf("[INFO] Assigning the Floating IP %s to the LB %s", d.Id(), loadBalancerUUID) 205 | opts.LoadBalancer = loadBalancerUUID 206 | } 207 | } else if attribute == "tags" { 208 | opts.Tags = CopyTags(d) 209 | } 210 | } 211 | } 212 | return requests 213 | } 214 | 215 | func deleteFloatingIP(d *schema.ResourceData, meta any) error { 216 | client := meta.(*cloudscale.Client) 217 | id := d.Id() 218 | return client.FloatingIPs.Delete(context.Background(), id) 219 | } 220 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_load_balancer_listener.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | "log" 9 | ) 10 | 11 | const listenerHumanName = "load balancer listener" 12 | 13 | var ( 14 | resourceCloudscaleLoadBalancerListenerRead = getReadOperation(listenerHumanName, getGenericResourceIdentifierFromSchema, readLoadBalancerListener, gatherLoadBalancerListenerResourceData) 15 | resourceCloudscaleLoadBalancerListenerUpdate = getUpdateOperation(listenerHumanName, getGenericResourceIdentifierFromSchema, updateLoadBalancerListener, resourceCloudscaleLoadBalancerListenerRead, gatherLoadBalancerListenerUpdateRequest) 16 | resourceCloudscaleLoadBalancerListenerDelete = getDeleteOperation(listenerHumanName, deleteLoadBalancerListener) 17 | ) 18 | 19 | func resourceCloudscaleLoadBalancerListener() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: resourceCloudscaleLoadBalancerListenerCreate, 22 | Read: resourceCloudscaleLoadBalancerListenerRead, 23 | Update: resourceCloudscaleLoadBalancerListenerUpdate, 24 | Delete: resourceCloudscaleLoadBalancerListenerDelete, 25 | 26 | Importer: &schema.ResourceImporter{ 27 | StateContext: schema.ImportStatePassthroughContext, 28 | }, 29 | Schema: getLoadBalancerListenerSchema(RESOURCE), 30 | } 31 | } 32 | 33 | func getLoadBalancerListenerSchema(t SchemaType) map[string]*schema.Schema { 34 | m := map[string]*schema.Schema{ 35 | "name": { 36 | Type: schema.TypeString, 37 | Required: t.isResource(), 38 | Optional: t.isDataSource(), 39 | }, 40 | "href": { 41 | Type: schema.TypeString, 42 | Computed: true, 43 | }, 44 | "pool_uuid": { 45 | Type: schema.TypeString, 46 | Optional: true, 47 | Computed: true, 48 | ForceNew: true, 49 | }, 50 | "pool_name": { 51 | Type: schema.TypeString, 52 | Computed: true, 53 | }, 54 | "pool_href": { 55 | Type: schema.TypeString, 56 | Computed: true, 57 | }, 58 | "protocol": { 59 | Type: schema.TypeString, 60 | Required: t.isResource(), 61 | Computed: t.isDataSource(), 62 | }, 63 | "protocol_port": { 64 | Type: schema.TypeInt, 65 | Required: t.isResource(), 66 | Computed: t.isDataSource(), 67 | }, 68 | "timeout_client_data_ms": { 69 | Type: schema.TypeInt, 70 | Optional: true, 71 | Computed: true, 72 | }, 73 | "timeout_member_connect_ms": { 74 | Type: schema.TypeInt, 75 | Optional: true, 76 | Computed: true, 77 | }, 78 | "timeout_member_data_ms": { 79 | Type: schema.TypeInt, 80 | Optional: true, 81 | Computed: true, 82 | }, 83 | "allowed_cidrs": { 84 | Type: schema.TypeList, 85 | Elem: &schema.Schema{Type: schema.TypeString}, 86 | Optional: true, 87 | Computed: true, 88 | }, 89 | "tags": &TagsSchema, 90 | } 91 | if t.isDataSource() { 92 | m["id"] = &schema.Schema{ 93 | Type: schema.TypeString, 94 | Optional: true, 95 | } 96 | } 97 | return m 98 | } 99 | 100 | func resourceCloudscaleLoadBalancerListenerCreate(d *schema.ResourceData, meta any) error { 101 | client := meta.(*cloudscale.Client) 102 | 103 | opts := &cloudscale.LoadBalancerListenerRequest{ 104 | Name: d.Get("name").(string), 105 | Protocol: d.Get("protocol").(string), 106 | ProtocolPort: d.Get("protocol_port").(int), 107 | } 108 | 109 | if attr, ok := d.GetOk("pool_uuid"); ok { 110 | opts.Pool = attr.(string) 111 | } 112 | 113 | if attr, ok := d.GetOk("timeout_client_data_ms"); ok { 114 | opts.TimeoutClientDataMS = attr.(int) 115 | } 116 | if attr, ok := d.GetOk("timeout_member_connect_ms"); ok { 117 | opts.TimeoutMemberConnectMS = attr.(int) 118 | } 119 | if attr, ok := d.GetOk("timeout_member_data_ms"); ok { 120 | opts.TimeoutMemberDataMS = attr.(int) 121 | } 122 | 123 | allowedCIDRs := d.Get("allowed_cidrs").([]any) 124 | s := make([]string, len(allowedCIDRs)) 125 | for i := range allowedCIDRs { 126 | s[i] = allowedCIDRs[i].(string) 127 | } 128 | opts.AllowedCIDRs = s 129 | 130 | opts.Tags = CopyTags(d) 131 | 132 | log.Printf("[DEBUG] LoadBalancerListener create configuration: %#v", opts) 133 | 134 | loadBalancerListener, err := client.LoadBalancerListeners.Create(context.Background(), opts) 135 | if err != nil { 136 | return fmt.Errorf("Error creating LoadBalancerListener: %s", err) 137 | } 138 | 139 | d.SetId(loadBalancerListener.UUID) 140 | 141 | log.Printf("[INFO] LoadBalancerListener ID: %s", d.Id()) 142 | err = resourceCloudscaleLoadBalancerListenerRead(d, meta) 143 | if err != nil { 144 | return fmt.Errorf("Error reading the load balancer listener (%s): %s", d.Id(), err) 145 | } 146 | return nil 147 | } 148 | 149 | func gatherLoadBalancerListenerResourceData(loadbalancerlistener *cloudscale.LoadBalancerListener) ResourceDataRaw { 150 | m := make(map[string]any) 151 | m["id"] = loadbalancerlistener.UUID 152 | m["href"] = loadbalancerlistener.HREF 153 | m["name"] = loadbalancerlistener.Name 154 | if loadbalancerlistener.Pool != nil { 155 | m["pool_uuid"] = loadbalancerlistener.Pool.UUID 156 | m["pool_name"] = loadbalancerlistener.Pool.Name 157 | m["pool_href"] = loadbalancerlistener.Pool.HREF 158 | } else { 159 | m["pool_uuid"] = nil 160 | m["pool_name"] = nil 161 | m["pool_href"] = nil 162 | } 163 | m["protocol"] = loadbalancerlistener.Protocol 164 | m["protocol_port"] = loadbalancerlistener.ProtocolPort 165 | m["timeout_client_data_ms"] = loadbalancerlistener.TimeoutClientDataMS 166 | m["timeout_member_connect_ms"] = loadbalancerlistener.TimeoutMemberConnectMS 167 | m["timeout_member_data_ms"] = loadbalancerlistener.TimeoutMemberDataMS 168 | m["allowed_cidrs"] = loadbalancerlistener.AllowedCIDRs 169 | m["tags"] = loadbalancerlistener.Tags 170 | return m 171 | } 172 | 173 | func readLoadBalancerListener(rId GenericResourceIdentifier, meta any) (*cloudscale.LoadBalancerListener, error) { 174 | client := meta.(*cloudscale.Client) 175 | return client.LoadBalancerListeners.Get(context.Background(), rId.Id) 176 | } 177 | 178 | func updateLoadBalancerListener(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.LoadBalancerListenerRequest) error { 179 | client := meta.(*cloudscale.Client) 180 | return client.LoadBalancerListeners.Update(context.Background(), rId.Id, updateRequest) 181 | } 182 | 183 | func gatherLoadBalancerListenerUpdateRequest(d *schema.ResourceData) []*cloudscale.LoadBalancerListenerRequest { 184 | requests := make([]*cloudscale.LoadBalancerListenerRequest, 0) 185 | 186 | for _, attribute := range []string{ 187 | "name", "protocol", "protocol_port", 188 | "timeout_client_data_ms", "timeout_member_connect_ms", "timeout_member_data_ms", 189 | "allowed_cidrs", 190 | "tags", 191 | } { 192 | if d.HasChange(attribute) { 193 | log.Printf("[INFO] Attribute %s changed", attribute) 194 | opts := &cloudscale.LoadBalancerListenerRequest{} 195 | requests = append(requests, opts) 196 | 197 | if attribute == "name" { 198 | opts.Name = d.Get(attribute).(string) 199 | } else if attribute == "protocol_port" { 200 | opts.ProtocolPort = d.Get(attribute).(int) 201 | } else if attribute == "protocol" { 202 | opts.Protocol = d.Get(attribute).(string) 203 | } else if attribute == "timeout_client_data_ms" { 204 | opts.TimeoutClientDataMS = d.Get(attribute).(int) 205 | } else if attribute == "timeout_member_connect_ms" { 206 | opts.TimeoutMemberConnectMS = d.Get(attribute).(int) 207 | } else if attribute == "timeout_member_data_ms" { 208 | opts.TimeoutMemberDataMS = d.Get(attribute).(int) 209 | } else if attribute == "allowed_cidrs" { 210 | allowedCIDRs := d.Get("allowed_cidrs").([]any) 211 | s := make([]string, len(allowedCIDRs)) 212 | for i := range allowedCIDRs { 213 | s[i] = allowedCIDRs[i].(string) 214 | } 215 | opts.AllowedCIDRs = s 216 | } else if attribute == "tags" { 217 | opts.Tags = CopyTags(d) 218 | } 219 | } 220 | } 221 | return requests 222 | } 223 | 224 | func deleteLoadBalancerListener(d *schema.ResourceData, meta any) error { 225 | client := meta.(*cloudscale.Client) 226 | id := d.Id() 227 | return client.LoadBalancerListeners.Delete(context.Background(), id) 228 | } 229 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_load_balancer_pool.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | "log" 9 | ) 10 | 11 | const poolHumanName = "load balancer pool" 12 | 13 | var ( 14 | resourceCloudscaleLoadBalancerPoolRead = getReadOperation(poolHumanName, getGenericResourceIdentifierFromSchema, readLoadBalancerPool, gatherLoadBalancerPoolResourceData) 15 | resourceCloudscaleLoadBalancerPoolUpdate = getUpdateOperation(poolHumanName, getGenericResourceIdentifierFromSchema, updateLoadBalancerPool, resourceCloudscaleLoadBalancerPoolRead, gatherLoadBalancerPoolUpdateRequest) 16 | resourceCloudscaleLoadBalancerPoolDelete = getDeleteOperation(poolHumanName, deleteLoadBalancerPool) 17 | ) 18 | 19 | func resourceCloudscaleLoadBalancerPool() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: resourceCloudscaleLoadBalancerPoolCreate, 22 | Read: resourceCloudscaleLoadBalancerPoolRead, 23 | Update: resourceCloudscaleLoadBalancerPoolUpdate, 24 | Delete: resourceCloudscaleLoadBalancerPoolDelete, 25 | 26 | Importer: &schema.ResourceImporter{ 27 | StateContext: schema.ImportStatePassthroughContext, 28 | }, 29 | Schema: getLoadBalancerPoolSchema(RESOURCE), 30 | } 31 | } 32 | 33 | func getLoadBalancerPoolSchema(t SchemaType) map[string]*schema.Schema { 34 | m := map[string]*schema.Schema{ 35 | "name": { 36 | Type: schema.TypeString, 37 | Required: t.isResource(), 38 | Optional: t.isDataSource(), 39 | }, 40 | "href": { 41 | Type: schema.TypeString, 42 | Computed: true, 43 | }, 44 | "load_balancer_uuid": { 45 | Type: schema.TypeString, 46 | Required: t.isResource(), 47 | Optional: t.isDataSource(), 48 | ForceNew: true, 49 | }, 50 | "load_balancer_name": { 51 | Type: schema.TypeString, 52 | Computed: true, 53 | }, 54 | "load_balancer_href": { 55 | Type: schema.TypeString, 56 | Computed: true, 57 | }, 58 | "algorithm": { 59 | Type: schema.TypeString, 60 | Required: t.isResource(), 61 | Computed: t.isDataSource(), 62 | ForceNew: true, 63 | }, 64 | "protocol": { 65 | Type: schema.TypeString, 66 | Required: t.isResource(), 67 | Computed: t.isDataSource(), 68 | ForceNew: true, 69 | }, 70 | "tags": &TagsSchema, 71 | } 72 | if t.isDataSource() { 73 | m["id"] = &schema.Schema{ 74 | Type: schema.TypeString, 75 | Optional: true, 76 | } 77 | } 78 | return m 79 | } 80 | 81 | func resourceCloudscaleLoadBalancerPoolCreate(d *schema.ResourceData, meta any) error { 82 | client := meta.(*cloudscale.Client) 83 | 84 | opts := &cloudscale.LoadBalancerPoolRequest{ 85 | Name: d.Get("name").(string), 86 | LoadBalancer: d.Get("load_balancer_uuid").(string), 87 | Algorithm: d.Get("algorithm").(string), 88 | Protocol: d.Get("protocol").(string), 89 | } 90 | 91 | opts.Tags = CopyTags(d) 92 | 93 | log.Printf("[DEBUG] LoadBalancerPool create configuration: %#v", opts) 94 | 95 | loadBalancerPool, err := client.LoadBalancerPools.Create(context.Background(), opts) 96 | if err != nil { 97 | return fmt.Errorf("Error creating LoadBalancerPool: %s", err) 98 | } 99 | 100 | d.SetId(loadBalancerPool.UUID) 101 | 102 | log.Printf("[INFO] LoadBalancerPool ID: %s", d.Id()) 103 | err = resourceCloudscaleLoadBalancerPoolRead(d, meta) 104 | if err != nil { 105 | return fmt.Errorf("Error reading the load balancer pool (%s): %s", d.Id(), err) 106 | } 107 | return nil 108 | } 109 | 110 | func gatherLoadBalancerPoolResourceData(loadbalancerpool *cloudscale.LoadBalancerPool) ResourceDataRaw { 111 | m := make(map[string]any) 112 | m["id"] = loadbalancerpool.UUID 113 | m["href"] = loadbalancerpool.HREF 114 | m["name"] = loadbalancerpool.Name 115 | m["load_balancer_uuid"] = loadbalancerpool.LoadBalancer.UUID 116 | m["load_balancer_name"] = loadbalancerpool.LoadBalancer.Name 117 | m["load_balancer_href"] = loadbalancerpool.LoadBalancer.HREF 118 | m["algorithm"] = loadbalancerpool.Algorithm 119 | m["protocol"] = loadbalancerpool.Protocol 120 | m["tags"] = loadbalancerpool.Tags 121 | return m 122 | } 123 | 124 | func readLoadBalancerPool(rId GenericResourceIdentifier, meta any) (*cloudscale.LoadBalancerPool, error) { 125 | client := meta.(*cloudscale.Client) 126 | return client.LoadBalancerPools.Get(context.Background(), rId.Id) 127 | } 128 | 129 | func updateLoadBalancerPool(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.LoadBalancerPoolRequest) error { 130 | client := meta.(*cloudscale.Client) 131 | return client.LoadBalancerPools.Update(context.Background(), rId.Id, updateRequest) 132 | } 133 | 134 | func gatherLoadBalancerPoolUpdateRequest(d *schema.ResourceData) []*cloudscale.LoadBalancerPoolRequest { 135 | requests := make([]*cloudscale.LoadBalancerPoolRequest, 0) 136 | 137 | for _, attribute := range []string{"name", "tags"} { 138 | if d.HasChange(attribute) { 139 | log.Printf("[INFO] Attribute %s changed", attribute) 140 | opts := &cloudscale.LoadBalancerPoolRequest{} 141 | requests = append(requests, opts) 142 | 143 | if attribute == "name" { 144 | opts.Name = d.Get(attribute).(string) 145 | } else if attribute == "tags" { 146 | opts.Tags = CopyTags(d) 147 | } 148 | } 149 | } 150 | return requests 151 | } 152 | 153 | func deleteLoadBalancerPool(d *schema.ResourceData, meta any) error { 154 | client := meta.(*cloudscale.Client) 155 | id := d.Id() 156 | return client.LoadBalancerPools.Delete(context.Background(), id) 157 | } 158 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_network.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const networkHumanName = "network" 13 | 14 | var ( 15 | resourceCloudscaleNetworkRead = getReadOperation(networkHumanName, getGenericResourceIdentifierFromSchema, readNetwork, gatherNetworkResourceData) 16 | resourceCloudscaleNetworkUpdate = getUpdateOperation(networkHumanName, getGenericResourceIdentifierFromSchema, updateNetwork, resourceCloudscaleNetworkRead, gatherNetworkUpdateRequest) 17 | resourceCloudscaleNetworkDelete = getDeleteOperation(networkHumanName, deleteNetwork) 18 | ) 19 | 20 | func resourceCloudscaleNetwork() *schema.Resource { 21 | return &schema.Resource{ 22 | Create: resourceCloudscaleNetworkCreate, 23 | Read: resourceCloudscaleNetworkRead, 24 | Update: resourceCloudscaleNetworkUpdate, 25 | Delete: resourceCloudscaleNetworkDelete, 26 | 27 | Importer: &schema.ResourceImporter{ 28 | StateContext: schema.ImportStatePassthroughContext, 29 | }, 30 | Schema: getNetworkSchema(RESOURCE), 31 | } 32 | } 33 | 34 | func getNetworkSchema(t SchemaType) map[string]*schema.Schema { 35 | m := map[string]*schema.Schema{ 36 | "name": { 37 | Type: schema.TypeString, 38 | Required: t.isResource(), 39 | Optional: t.isDataSource(), 40 | Computed: t.isDataSource(), 41 | }, 42 | "zone_slug": { 43 | Type: schema.TypeString, 44 | Optional: true, 45 | Computed: true, 46 | ForceNew: true, 47 | }, 48 | "mtu": { 49 | Type: schema.TypeInt, 50 | Optional: t.isResource(), 51 | Computed: true, 52 | }, 53 | "subnets": { 54 | Type: schema.TypeList, 55 | Elem: &schema.Resource{ 56 | Schema: map[string]*schema.Schema{ 57 | "href": { 58 | Type: schema.TypeString, 59 | Computed: true, 60 | }, 61 | "uuid": { 62 | Type: schema.TypeString, 63 | Computed: true, 64 | }, 65 | "cidr": { 66 | Type: schema.TypeString, 67 | Computed: true, 68 | }, 69 | }, 70 | }, 71 | Computed: true, 72 | }, 73 | "href": { 74 | Type: schema.TypeString, 75 | Computed: true, 76 | }, 77 | "tags": &TagsSchema, 78 | } 79 | if t.isDataSource() { 80 | m["id"] = &schema.Schema{ 81 | Type: schema.TypeString, 82 | Optional: true, 83 | } 84 | } else { 85 | m["auto_create_ipv4_subnet"] = &schema.Schema{ 86 | Type: schema.TypeBool, 87 | Optional: true, 88 | ForceNew: true, 89 | } 90 | } 91 | return m 92 | } 93 | 94 | func resourceCloudscaleNetworkCreate(d *schema.ResourceData, meta any) error { 95 | client := meta.(*cloudscale.Client) 96 | 97 | opts := &cloudscale.NetworkCreateRequest{ 98 | Name: d.Get("name").(string), 99 | } 100 | 101 | if attr, ok := d.GetOk("zone_slug"); ok { 102 | opts.Zone = attr.(string) 103 | } 104 | if attr, ok := d.GetOk("mtu"); ok { 105 | opts.MTU = attr.(int) 106 | } 107 | if attr, ok := d.GetOkExists("auto_create_ipv4_subnet"); ok { 108 | val := attr.(bool) 109 | opts.AutoCreateIPV4Subnet = &val 110 | } 111 | opts.Tags = CopyTags(d) 112 | 113 | log.Printf("[DEBUG] Network create configuration: %#v", opts) 114 | 115 | network, err := client.Networks.Create(context.Background(), opts) 116 | if err != nil { 117 | return fmt.Errorf("Error creating network: %s", err) 118 | } 119 | 120 | d.SetId(network.UUID) 121 | 122 | log.Printf("[INFO] Network ID %s", d.Id()) 123 | err = resourceCloudscaleNetworkRead(d, meta) 124 | if err != nil { 125 | return fmt.Errorf("Error reading the network (%s): %s", d.Id(), err) 126 | } 127 | return nil 128 | } 129 | 130 | func gatherNetworkResourceData(network *cloudscale.Network) ResourceDataRaw { 131 | m := make(map[string]any) 132 | m["id"] = network.UUID 133 | m["href"] = network.HREF 134 | m["name"] = network.Name 135 | m["mtu"] = network.MTU 136 | m["zone_slug"] = network.Zone.Slug 137 | 138 | subnets := make([]map[string]any, 0, len(network.Subnets)) 139 | for _, subnet := range network.Subnets { 140 | g := make(map[string]any) 141 | g["uuid"] = subnet.UUID 142 | g["cidr"] = subnet.CIDR 143 | g["href"] = subnet.HREF 144 | subnets = append(subnets, g) 145 | } 146 | m["subnets"] = subnets 147 | m["tags"] = network.Tags 148 | return m 149 | } 150 | 151 | func readNetwork(rId GenericResourceIdentifier, meta any) (*cloudscale.Network, error) { 152 | client := meta.(*cloudscale.Client) 153 | return client.Networks.Get(context.Background(), rId.Id) 154 | } 155 | 156 | func updateNetwork(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.NetworkUpdateRequest) error { 157 | client := meta.(*cloudscale.Client) 158 | return client.Networks.Update(context.Background(), rId.Id, updateRequest) 159 | } 160 | 161 | func gatherNetworkUpdateRequest(d *schema.ResourceData) []*cloudscale.NetworkUpdateRequest { 162 | requests := make([]*cloudscale.NetworkUpdateRequest, 0) 163 | 164 | for _, attribute := range []string{"name", "mtu", "tags"} { 165 | if d.HasChange(attribute) { 166 | log.Printf("[INFO] Attribute %s changed", attribute) 167 | opts := &cloudscale.NetworkUpdateRequest{} 168 | requests = append(requests, opts) 169 | 170 | if attribute == "name" { 171 | opts.Name = d.Get(attribute).(string) 172 | } else if attribute == "mtu" { 173 | opts.MTU = d.Get(attribute).(int) 174 | } else if attribute == "tags" { 175 | opts.Tags = CopyTags(d) 176 | } 177 | } 178 | } 179 | return requests 180 | } 181 | 182 | func deleteNetwork(d *schema.ResourceData, meta any) error { 183 | client := meta.(*cloudscale.Client) 184 | id := d.Id() 185 | return client.Networks.Delete(context.Background(), id) 186 | } 187 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_objects_user.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | "log" 9 | ) 10 | 11 | const objectsUserHumanName = "Objects User" 12 | 13 | var ( 14 | resourceCloudscaleObjectsUserRead = getReadOperation(objectsUserHumanName, getGenericResourceIdentifierFromSchema, readObjectsUser, gatherObjectsUserResourceData) 15 | resourceCloudscaleObjectsUserUpdate = getUpdateOperation(objectsUserHumanName, getGenericResourceIdentifierFromSchema, updateObjectsUser, resourceCloudscaleObjectsUserRead, gatherObjectsUserUpdateRequest) 16 | resourceCloudscaleObjectsUserDelete = getDeleteOperation(objectsUserHumanName, deleteObjectsUser) 17 | ) 18 | 19 | func resourceCloudscaleObjectsUser() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: resourceCloudscaleObjectsUserCreate, 22 | Read: resourceCloudscaleObjectsUserRead, 23 | Update: resourceCloudscaleObjectsUserUpdate, 24 | Delete: resourceCloudscaleObjectsUserDelete, 25 | 26 | Importer: &schema.ResourceImporter{ 27 | StateContext: schema.ImportStatePassthroughContext, 28 | }, 29 | Schema: getObjectsUserSchema(RESOURCE), 30 | } 31 | } 32 | 33 | func getObjectsUserSchema(t SchemaType) map[string]*schema.Schema { 34 | m := map[string]*schema.Schema{ 35 | "display_name": { 36 | Type: schema.TypeString, 37 | Required: t.isResource(), 38 | Optional: t.isDataSource(), 39 | }, 40 | "href": { 41 | Type: schema.TypeString, 42 | Computed: true, 43 | }, 44 | "user_id": { 45 | Type: schema.TypeString, 46 | Optional: t.isDataSource(), 47 | Computed: true, 48 | }, 49 | "keys": { 50 | Type: schema.TypeList, 51 | Elem: &schema.Resource{ 52 | Schema: map[string]*schema.Schema{ 53 | "access_key": { 54 | Type: schema.TypeString, 55 | Computed: true, 56 | }, 57 | "secret_key": { 58 | Type: schema.TypeString, 59 | Computed: true, 60 | }, 61 | }, 62 | }, 63 | Computed: true, 64 | Sensitive: true, 65 | }, 66 | "tags": &TagsSchema, 67 | } 68 | if t.isDataSource() { 69 | m["id"] = &schema.Schema{ 70 | Type: schema.TypeString, 71 | Optional: true, 72 | } 73 | } 74 | return m 75 | } 76 | 77 | func resourceCloudscaleObjectsUserCreate(d *schema.ResourceData, meta any) error { 78 | client := meta.(*cloudscale.Client) 79 | 80 | opts := &cloudscale.ObjectsUserRequest{ 81 | DisplayName: d.Get("display_name").(string), 82 | } 83 | opts.Tags = CopyTags(d) 84 | 85 | objectsUser, err := client.ObjectsUsers.Create(context.Background(), opts) 86 | if err != nil { 87 | return fmt.Errorf("Error creating objects user: %s", err) 88 | } 89 | 90 | d.SetId(objectsUser.ID) 91 | 92 | log.Printf("[INFO] Objects user ID %s", d.Id()) 93 | 94 | err = resourceCloudscaleObjectsUserRead(d, meta) 95 | if err != nil { 96 | return fmt.Errorf("Error reading the objects user (%s): %s", d.Id(), err) 97 | } 98 | return nil 99 | } 100 | 101 | func gatherObjectsUserResourceData(objectsUser *cloudscale.ObjectsUser) ResourceDataRaw { 102 | m := make(map[string]any) 103 | m["id"] = objectsUser.ID 104 | m["href"] = objectsUser.HREF 105 | m["user_id"] = objectsUser.ID 106 | m["display_name"] = objectsUser.DisplayName 107 | m["tags"] = objectsUser.Tags 108 | 109 | keys := make([]map[string]string, 0, len(objectsUser.Keys)) 110 | for _, keyEntry := range objectsUser.Keys { 111 | g := map[string]string{} 112 | g["secret_key"] = keyEntry["secret_key"] 113 | g["access_key"] = keyEntry["access_key"] 114 | keys = append(keys, g) 115 | } 116 | m["keys"] = keys 117 | 118 | return m 119 | } 120 | 121 | func readObjectsUser(rId GenericResourceIdentifier, meta any) (*cloudscale.ObjectsUser, error) { 122 | client := meta.(*cloudscale.Client) 123 | return client.ObjectsUsers.Get(context.Background(), rId.Id) 124 | } 125 | 126 | func updateObjectsUser(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.ObjectsUserRequest) error { 127 | client := meta.(*cloudscale.Client) 128 | return client.ObjectsUsers.Update(context.Background(), rId.Id, updateRequest) 129 | } 130 | 131 | func gatherObjectsUserUpdateRequest(d *schema.ResourceData) []*cloudscale.ObjectsUserRequest { 132 | requests := make([]*cloudscale.ObjectsUserRequest, 0) 133 | 134 | for _, attribute := range []string{"display_name", "tags"} { 135 | if d.HasChange(attribute) { 136 | log.Printf("[INFO] Attribute %s changed", attribute) 137 | opts := &cloudscale.ObjectsUserRequest{} 138 | requests = append(requests, opts) 139 | if attribute == "display_name" { 140 | opts.DisplayName = d.Get(attribute).(string) 141 | } else if attribute == "tags" { 142 | opts.Tags = CopyTags(d) 143 | } 144 | } 145 | } 146 | return requests 147 | } 148 | 149 | func deleteObjectsUser(d *schema.ResourceData, meta any) error { 150 | client := meta.(*cloudscale.Client) 151 | id := d.Id() 152 | return client.ObjectsUsers.Delete(context.Background(), id) 153 | } 154 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_server_group.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const serverGroupHumanName = "server group" 13 | 14 | var ( 15 | resourceCloudscaleServerGroupRead = getReadOperation(serverGroupHumanName, getGenericResourceIdentifierFromSchema, readServerGroup, gatherServerGroupResourceData) 16 | resourceCloudscaleServerGroupUpdate = getUpdateOperation(serverGroupHumanName, getGenericResourceIdentifierFromSchema, updateServerGroup, resourceCloudscaleServerGroupRead, gatherServerGroupUpdateRequest) 17 | resourceCloudscaleServerGroupDelete = getDeleteOperation(serverGroupHumanName, deleteServerGroup) 18 | ) 19 | 20 | func resourceCloudscaleServerGroup() *schema.Resource { 21 | return &schema.Resource{ 22 | Create: resourceCloudscaleServerGroupCreate, 23 | Read: resourceCloudscaleServerGroupRead, 24 | Update: resourceCloudscaleServerGroupUpdate, 25 | Delete: resourceCloudscaleServerGroupDelete, 26 | 27 | Importer: &schema.ResourceImporter{ 28 | StateContext: schema.ImportStatePassthroughContext, 29 | }, 30 | Schema: getServerGroupSchema(RESOURCE), 31 | } 32 | } 33 | 34 | func getServerGroupSchema(t SchemaType) map[string]*schema.Schema { 35 | m := map[string]*schema.Schema{ 36 | "name": { 37 | Type: schema.TypeString, 38 | Required: t.isResource(), 39 | Optional: t.isDataSource(), 40 | }, 41 | "type": { 42 | Type: schema.TypeString, 43 | Required: t.isResource(), 44 | Computed: t.isDataSource(), 45 | ForceNew: true, 46 | }, 47 | "zone_slug": { 48 | Type: schema.TypeString, 49 | Optional: true, 50 | Computed: true, 51 | ForceNew: true, 52 | }, 53 | "href": { 54 | Type: schema.TypeString, 55 | Computed: true, 56 | }, 57 | "tags": &TagsSchema, 58 | } 59 | if t.isDataSource() { 60 | m["id"] = &schema.Schema{ 61 | Type: schema.TypeString, 62 | Optional: true, 63 | } 64 | } 65 | return m 66 | } 67 | 68 | func resourceCloudscaleServerGroupCreate(d *schema.ResourceData, meta any) error { 69 | client := meta.(*cloudscale.Client) 70 | 71 | opts := &cloudscale.ServerGroupRequest{ 72 | Name: d.Get("name").(string), 73 | Type: d.Get("type").(string), 74 | } 75 | 76 | if attr, ok := d.GetOk("zone_slug"); ok { 77 | opts.Zone = attr.(string) 78 | } 79 | opts.Tags = CopyTags(d) 80 | 81 | log.Printf("[DEBUG] ServerGroup create configuration: %#v", opts) 82 | 83 | serverGroup, err := client.ServerGroups.Create(context.Background(), opts) 84 | if err != nil { 85 | return fmt.Errorf("Error creating server group: %s", err) 86 | } 87 | 88 | d.SetId(serverGroup.UUID) 89 | 90 | log.Printf("[INFO] ServerGroup ID %s", d.Id()) 91 | 92 | err = resourceCloudscaleServerGroupRead(d, meta) 93 | if err != nil { 94 | return fmt.Errorf("Error reading the server group (%s): %s", d.Id(), err) 95 | } 96 | return nil 97 | } 98 | 99 | func gatherServerGroupResourceData(serverGroup *cloudscale.ServerGroup) ResourceDataRaw { 100 | m := make(map[string]any) 101 | m["id"] = serverGroup.UUID 102 | m["href"] = serverGroup.HREF 103 | m["name"] = serverGroup.Name 104 | m["type"] = serverGroup.Type 105 | m["zone_slug"] = serverGroup.Zone.Slug 106 | m["tags"] = serverGroup.Tags 107 | return m 108 | } 109 | 110 | func readServerGroup(rId GenericResourceIdentifier, meta any) (*cloudscale.ServerGroup, error) { 111 | client := meta.(*cloudscale.Client) 112 | return client.ServerGroups.Get(context.Background(), rId.Id) 113 | } 114 | 115 | func updateServerGroup(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.ServerGroupRequest) error { 116 | client := meta.(*cloudscale.Client) 117 | return client.ServerGroups.Update(context.Background(), rId.Id, updateRequest) 118 | } 119 | 120 | func gatherServerGroupUpdateRequest(d *schema.ResourceData) []*cloudscale.ServerGroupRequest { 121 | requests := make([]*cloudscale.ServerGroupRequest, 0) 122 | 123 | for _, attribute := range []string{"name", "tags"} { 124 | if d.HasChange(attribute) { 125 | log.Printf("[INFO] Attribute %s changed", attribute) 126 | opts := &cloudscale.ServerGroupRequest{} 127 | requests = append(requests, opts) 128 | 129 | if attribute == "name" { 130 | opts.Name = d.Get(attribute).(string) 131 | } else if attribute == "tags" { 132 | opts.Tags = CopyTags(d) 133 | } 134 | } 135 | } 136 | return requests 137 | } 138 | 139 | func deleteServerGroup(d *schema.ResourceData, meta any) error { 140 | client := meta.(*cloudscale.Client) 141 | id := d.Id() 142 | return client.ServerGroups.Delete(context.Background(), id) 143 | } 144 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_subnet.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | const subnetHumanName = "subnet" 14 | 15 | var ( 16 | resourceCloudscaleSubnetRead = getReadOperation(subnetHumanName, getGenericResourceIdentifierFromSchema, readSubnet, gatherSubnetResourceData) 17 | resourceCloudscaleSubnetUpdate = getUpdateOperation(subnetHumanName, getGenericResourceIdentifierFromSchema, updateSubnet, resourceCloudscaleSubnetRead, gatherSubnetUpdateRequests) 18 | resourceCloudscaleSubnetDelete = getDeleteOperation(subnetHumanName, deleteSubnet) 19 | ) 20 | 21 | func resourceCloudscaleSubnet() *schema.Resource { 22 | return &schema.Resource{ 23 | Create: resourceCloudscaleSubnetCreate, 24 | Read: resourceCloudscaleSubnetRead, 25 | Update: resourceCloudscaleSubnetUpdate, 26 | Delete: resourceCloudscaleSubnetDelete, 27 | 28 | Importer: &schema.ResourceImporter{ 29 | StateContext: schema.ImportStatePassthroughContext, 30 | }, 31 | Schema: getSubnetSchema(RESOURCE), 32 | } 33 | } 34 | 35 | func getSubnetSchema(t SchemaType) map[string]*schema.Schema { 36 | m := map[string]*schema.Schema{ 37 | "cidr": { 38 | Type: schema.TypeString, 39 | Required: t.isResource(), 40 | Optional: t.isDataSource(), 41 | ForceNew: true, 42 | }, 43 | "network_uuid": { 44 | Type: schema.TypeString, 45 | Optional: true, 46 | ForceNew: true, 47 | Computed: true, 48 | }, 49 | "gateway_address": { 50 | Type: schema.TypeString, 51 | Computed: true, 52 | Optional: true, 53 | }, 54 | "dns_servers": { 55 | Type: schema.TypeList, 56 | Elem: &schema.Schema{Type: schema.TypeString}, 57 | Computed: true, 58 | Optional: t.isResource(), 59 | }, 60 | "network_name": { 61 | Type: schema.TypeString, 62 | Computed: true, 63 | Optional: t.isDataSource(), 64 | }, 65 | "href": { 66 | Type: schema.TypeString, 67 | Computed: true, 68 | }, 69 | "network_href": { 70 | Type: schema.TypeString, 71 | Computed: true, 72 | }, 73 | "tags": &TagsSchema, 74 | } 75 | if t.isDataSource() { 76 | m["id"] = &schema.Schema{ 77 | Type: schema.TypeString, 78 | Optional: true, 79 | } 80 | } else { 81 | m["disable_dns_servers"] = &schema.Schema{ 82 | Type: schema.TypeBool, 83 | Optional: true, 84 | ConflictsWith: []string{"dns_servers"}, 85 | } 86 | } 87 | return m 88 | } 89 | 90 | func resourceCloudscaleSubnetCreate(d *schema.ResourceData, meta any) error { 91 | client := meta.(*cloudscale.Client) 92 | 93 | opts := &cloudscale.SubnetCreateRequest{ 94 | CIDR: d.Get("cidr").(string), 95 | } 96 | 97 | if attr, ok := d.GetOk("network_uuid"); ok { 98 | opts.Network = attr.(string) 99 | } 100 | if attr, ok := d.GetOk("gateway_address"); ok { 101 | opts.GatewayAddress = attr.(string) 102 | } 103 | 104 | disableDnsServers := d.Get("disable_dns_servers").(bool) 105 | if disableDnsServers { 106 | opts.DNSServers = &[]string{} 107 | } else { 108 | if dnsServersRaw, ok := d.GetOk("dns_servers"); ok { 109 | dnsServers := dnsServersRaw.([]interface{}) 110 | dnsServersStr := make([]string, len(dnsServers)) 111 | for i := range dnsServers { 112 | dnsServersStr[i] = dnsServers[i].(string) 113 | } 114 | opts.DNSServers = &dnsServersStr 115 | } else { 116 | opts.DNSServers = &cloudscale.UseCloudscaleDefaults 117 | } 118 | } 119 | 120 | opts.Tags = CopyTags(d) 121 | 122 | log.Printf("[DEBUG] Subnet create configuration: %#v", opts) 123 | 124 | subnet, err := client.Subnets.Create(context.Background(), opts) 125 | if err != nil { 126 | return fmt.Errorf("Error creating subnet: %s", err) 127 | } 128 | 129 | d.SetId(subnet.UUID) 130 | 131 | log.Printf("[INFO] Subnet ID %s", d.Id()) 132 | 133 | err = resourceCloudscaleSubnetRead(d, meta) 134 | if err != nil { 135 | return fmt.Errorf("Error reading the subnet (%s): %s", d.Id(), err) 136 | } 137 | 138 | return nil 139 | } 140 | 141 | func gatherSubnetResourceData(subnet *cloudscale.Subnet) ResourceDataRaw { 142 | m := make(map[string]any) 143 | m["id"] = subnet.UUID 144 | m["href"] = subnet.HREF 145 | m["cidr"] = subnet.CIDR 146 | m["network_href"] = subnet.Network.HREF 147 | m["network_uuid"] = subnet.Network.UUID 148 | m["network_name"] = subnet.Network.Name 149 | m["gateway_address"] = subnet.GatewayAddress 150 | m["dns_servers"] = subnet.DNSServers 151 | m["tags"] = subnet.Tags 152 | return m 153 | } 154 | 155 | func readSubnet(rId GenericResourceIdentifier, meta any) (*cloudscale.Subnet, error) { 156 | client := meta.(*cloudscale.Client) 157 | return client.Subnets.Get(context.Background(), rId.Id) 158 | } 159 | 160 | func updateSubnet(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.SubnetUpdateRequest) error { 161 | client := meta.(*cloudscale.Client) 162 | return client.Subnets.Update(context.Background(), rId.Id, updateRequest) 163 | } 164 | 165 | func gatherSubnetUpdateRequests(d *schema.ResourceData) []*cloudscale.SubnetUpdateRequest { 166 | requests := make([]*cloudscale.SubnetUpdateRequest, 0) 167 | 168 | for _, attribute := range []string{"gateway_address", "dns_servers", "tags", "disable_dns_servers"} { 169 | if d.HasChange(attribute) { 170 | log.Printf("[INFO] Attribute %s changed", attribute) 171 | opts := &cloudscale.SubnetUpdateRequest{} 172 | requests = append(requests, opts) 173 | 174 | if attribute == "gateway_address" { 175 | opts.GatewayAddress = d.Get(attribute).(string) 176 | } else if attribute == "dns_servers" || attribute == "disable_dns_servers" { 177 | disableDnsServers := d.Get("disable_dns_servers").(bool) 178 | if disableDnsServers { 179 | opts.DNSServers = &[]string{} 180 | } else { 181 | if dnsServersRaw, ok := d.GetOk("dns_servers"); ok { 182 | dnsServers := dnsServersRaw.([]interface{}) 183 | dnsServersStr := make([]string, len(dnsServers)) 184 | for i := range dnsServers { 185 | dnsServersStr[i] = dnsServers[i].(string) 186 | } 187 | opts.DNSServers = &dnsServersStr 188 | } else { 189 | opts.DNSServers = &cloudscale.UseCloudscaleDefaults 190 | } 191 | } 192 | } else if attribute == "tags" { 193 | opts.Tags = CopyTags(d) 194 | } 195 | } 196 | } 197 | return requests 198 | } 199 | 200 | func deleteSubnet(d *schema.ResourceData, meta any) error { 201 | client := meta.(*cloudscale.Client) 202 | id := d.Id() 203 | // sending the next request immediately can cause errors, since the port cleanup process is still ongoing 204 | time.Sleep(5 * time.Second) 205 | return client.Subnets.Delete(context.Background(), id) 206 | } 207 | -------------------------------------------------------------------------------- /cloudscale/resource_cloudscale_volume.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | const volumeHumanName = "volume" 13 | 14 | var ( 15 | resourceCloudscaleVolumeRead = getReadOperation(volumeHumanName, getGenericResourceIdentifierFromSchema, readVolume, gatherVolumeResourceData) 16 | resourceCloudscaleVolumeUpdate = getUpdateOperation(volumeHumanName, getGenericResourceIdentifierFromSchema, updateVolume, resourceCloudscaleVolumeRead, gatherVolumeUpdateRequests) 17 | resourceCloudscaleVolumeDelete = getDeleteOperation(volumeHumanName, deleteVolume) 18 | ) 19 | 20 | func resourceCloudscaleVolume() *schema.Resource { 21 | return &schema.Resource{ 22 | Create: resourceCloudscaleVolumeCreate, 23 | Read: resourceCloudscaleVolumeRead, 24 | Update: resourceCloudscaleVolumeUpdate, 25 | Delete: resourceCloudscaleVolumeDelete, 26 | 27 | Importer: &schema.ResourceImporter{ 28 | StateContext: schema.ImportStatePassthroughContext, 29 | }, 30 | Schema: getVolumeSchema(RESOURCE), 31 | } 32 | } 33 | 34 | func getVolumeSchema(t SchemaType) map[string]*schema.Schema { 35 | m := map[string]*schema.Schema{ 36 | "name": { 37 | Type: schema.TypeString, 38 | Required: t.isResource(), 39 | Optional: t.isDataSource(), 40 | }, 41 | "size_gb": { 42 | Type: schema.TypeInt, 43 | Required: t.isResource(), 44 | Computed: t.isDataSource(), 45 | }, 46 | "type": { 47 | Type: schema.TypeString, 48 | Optional: true, 49 | ForceNew: true, 50 | }, 51 | "zone_slug": { 52 | Type: schema.TypeString, 53 | Optional: true, 54 | Computed: true, 55 | ForceNew: true, 56 | }, 57 | "server_uuids": { 58 | Type: schema.TypeList, 59 | Elem: &schema.Schema{Type: schema.TypeString}, 60 | Optional: t.isResource(), 61 | Computed: t.isDataSource(), 62 | }, 63 | "href": { 64 | Type: schema.TypeString, 65 | Computed: true, 66 | }, 67 | "tags": &TagsSchema, 68 | } 69 | if t.isDataSource() { 70 | m["id"] = &schema.Schema{ 71 | Type: schema.TypeString, 72 | Optional: true, 73 | } 74 | } 75 | return m 76 | } 77 | 78 | func resourceCloudscaleVolumeCreate(d *schema.ResourceData, meta any) error { 79 | client := meta.(*cloudscale.Client) 80 | 81 | opts := &cloudscale.VolumeRequest{ 82 | Name: d.Get("name").(string), 83 | SizeGB: d.Get("size_gb").(int), 84 | Type: d.Get("type").(string), 85 | } 86 | 87 | serverUUIDs := d.Get("server_uuids").([]any) 88 | s := make([]string, len(serverUUIDs)) 89 | 90 | for i := range serverUUIDs { 91 | s[i] = serverUUIDs[i].(string) 92 | } 93 | 94 | if attr, ok := d.GetOk("zone_slug"); ok { 95 | opts.Zone = attr.(string) 96 | } 97 | 98 | opts.ServerUUIDs = &s 99 | opts.Tags = CopyTags(d) 100 | 101 | log.Printf("[DEBUG] Volume create configuration: %#v", opts) 102 | 103 | volume, err := client.Volumes.Create(context.Background(), opts) 104 | if err != nil { 105 | return fmt.Errorf("Error creating volume: %s", err) 106 | } 107 | 108 | d.SetId(volume.UUID) 109 | 110 | log.Printf("[INFO] Volume ID %s", d.Id()) 111 | 112 | err = resourceCloudscaleVolumeRead(d, meta) 113 | if err != nil { 114 | return fmt.Errorf("Error reading the volume (%s): %s", d.Id(), err) 115 | } 116 | return nil 117 | } 118 | 119 | func gatherVolumeResourceData(volume *cloudscale.Volume) ResourceDataRaw { 120 | m := make(map[string]any) 121 | m["id"] = volume.UUID 122 | m["href"] = volume.HREF 123 | m["name"] = volume.Name 124 | m["size_gb"] = volume.SizeGB 125 | m["type"] = volume.Type 126 | m["zone_slug"] = volume.Zone.Slug 127 | m["server_uuids"] = volume.ServerUUIDs 128 | m["tags"] = volume.Tags 129 | return m 130 | } 131 | 132 | func readVolume(rId GenericResourceIdentifier, meta any) (*cloudscale.Volume, error) { 133 | client := meta.(*cloudscale.Client) 134 | return client.Volumes.Get(context.Background(), rId.Id) 135 | } 136 | 137 | func updateVolume(rId GenericResourceIdentifier, meta any, updateRequest *cloudscale.VolumeRequest) error { 138 | client := meta.(*cloudscale.Client) 139 | return client.Volumes.Update(context.Background(), rId.Id, updateRequest) 140 | } 141 | 142 | func gatherVolumeUpdateRequests(d *schema.ResourceData) []*cloudscale.VolumeRequest { 143 | requests := make([]*cloudscale.VolumeRequest, 0) 144 | 145 | for _, attribute := range []string{"name", "size_gb", "server_uuids", "tags"} { 146 | if d.HasChange(attribute) { 147 | log.Printf("[INFO] Attribute %s changed", attribute) 148 | opts := &cloudscale.VolumeRequest{} 149 | requests = append(requests, opts) 150 | 151 | if attribute == "server_uuids" { 152 | serverUUIDs := d.Get("server_uuids").([]any) 153 | s := make([]string, len(serverUUIDs)) 154 | 155 | for i := range serverUUIDs { 156 | s[i] = serverUUIDs[i].(string) 157 | } 158 | opts.ServerUUIDs = &s 159 | } else if attribute == "name" { 160 | opts.Name = d.Get(attribute).(string) 161 | } else if attribute == "size_gb" { 162 | opts.SizeGB = d.Get(attribute).(int) 163 | } else if attribute == "tags" { 164 | opts.Tags = CopyTags(d) 165 | } 166 | } 167 | } 168 | return requests 169 | } 170 | 171 | func deleteVolume(d *schema.ResourceData, meta any) error { 172 | client := meta.(*cloudscale.Client) 173 | id := d.Id() 174 | return client.Volumes.Delete(context.Background(), id) 175 | } 176 | -------------------------------------------------------------------------------- /cloudscale/resources.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 6 | "log" 7 | ) 8 | 9 | func getReadOperation[TResource any, TResourceID any]( 10 | resourceHumanName string, 11 | idFunc func(d *schema.ResourceData) TResourceID, 12 | readFunc func(rID TResourceID, meta any) (*TResource, error), 13 | gatherFunc func(resource *TResource) ResourceDataRaw, 14 | ) schema.ReadFunc { 15 | return func(d *schema.ResourceData, meta any) error { 16 | rId := idFunc(d) 17 | resource, err := readFunc(rId, meta) 18 | 19 | if err != nil { 20 | return CheckDeleted(d, err, fmt.Sprintf("Error retrieving %s (%v)", resourceHumanName, rId)) 21 | } 22 | 23 | fillResourceData(d, gatherFunc(resource)) 24 | return nil 25 | } 26 | } 27 | 28 | func getUpdateOperation[TResourceID any, TRequest any]( 29 | resourceHumanName string, 30 | idFunc func(d *schema.ResourceData) TResourceID, 31 | updateFunc func(rId TResourceID, meta any, updateRequest *TRequest) error, 32 | resourceReadFunc schema.ReadFunc, 33 | gatherRequestsFunc func(d *schema.ResourceData) []*TRequest, 34 | ) schema.UpdateFunc { 35 | return func(d *schema.ResourceData, meta any) error { 36 | rId := idFunc(d) 37 | updateRequests := gatherRequestsFunc(d) 38 | for _, request := range updateRequests { 39 | err := updateFunc(rId, meta, request) 40 | if err != nil { 41 | return fmt.Errorf("error updating the %s (%s) status (%s)", resourceHumanName, d.Id(), err) 42 | } 43 | } 44 | return resourceReadFunc(d, meta) 45 | } 46 | } 47 | 48 | func getDeleteOperation( 49 | resourceHumanName string, 50 | deleteFunc func(d *schema.ResourceData, meta any) error, 51 | ) schema.DeleteFunc { 52 | return func(d *schema.ResourceData, meta any) error { 53 | log.Printf("[INFO] Deleting %s: %s", resourceHumanName, d.Id()) 54 | err := deleteFunc(d, meta) 55 | 56 | if err != nil { 57 | return CheckDeleted(d, err, fmt.Sprintf("Error deleting %s", resourceHumanName)) 58 | } 59 | return nil 60 | } 61 | } 62 | 63 | type GenericResourceIdentifier struct { 64 | Id string 65 | } 66 | 67 | func getGenericResourceIdentifierFromSchema(d *schema.ResourceData) GenericResourceIdentifier { 68 | return GenericResourceIdentifier{Id: d.Id()} 69 | } 70 | -------------------------------------------------------------------------------- /cloudscale/schema_type.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | type SchemaType int 4 | 5 | const ( 6 | RESOURCE SchemaType = iota 7 | DATA_SOURCE 8 | ) 9 | 10 | func (t SchemaType) isDataSource() bool { 11 | switch t { 12 | case RESOURCE: 13 | return false 14 | case DATA_SOURCE: 15 | return true 16 | } 17 | panic("unknown SchemaType") 18 | } 19 | 20 | func (t SchemaType) isResource() bool { 21 | switch t { 22 | case RESOURCE: 23 | return true 24 | case DATA_SOURCE: 25 | return false 26 | } 27 | panic("unknown SchemaType") 28 | } 29 | -------------------------------------------------------------------------------- /cloudscale/sweeper_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 9 | ) 10 | 11 | func TestMain(m *testing.M) { 12 | resource.TestMain(m) 13 | } 14 | 15 | func sharedConfigForRegion(region string) (any, error) { 16 | if os.Getenv("CLOUDSCALE_API_TOKEN") == "" { 17 | return nil, fmt.Errorf("empty CLOUDSCALE_API_TOKEN") 18 | } 19 | 20 | config := Config{ 21 | Token: os.Getenv("CLOUDSCALE_API_TOKEN"), 22 | } 23 | 24 | // configures a default client for the region, using the above env vars 25 | client, err := config.Client() 26 | if err != nil { 27 | return nil, fmt.Errorf("error getting cloudscale client") 28 | } 29 | 30 | return client, nil 31 | } 32 | -------------------------------------------------------------------------------- /cloudscale/util.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudscale-ch/cloudscale-go-sdk/v5" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | "net/http" 8 | ) 9 | 10 | var ( 11 | TagsSchema schema.Schema = schema.Schema{ 12 | Type: schema.TypeMap, 13 | Elem: &schema.Schema{ 14 | Type: schema.TypeString, 15 | }, 16 | Optional: true, 17 | }; 18 | ) 19 | 20 | func CopyTags(d *schema.ResourceData) *cloudscale.TagMap { 21 | newTags := make(cloudscale.TagMap) 22 | 23 | for k, v := range d.Get("tags").(map[string]any) { 24 | newTags[k] = v.(string) 25 | } 26 | 27 | return &newTags 28 | } 29 | 30 | // CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, 31 | // sets the resource ID to the empty string instead of throwing an error. 32 | func CheckDeleted(d *schema.ResourceData, err error, msg string) error { 33 | errorResponse, ok := err.(*cloudscale.ErrorResponse) 34 | if ok && errorResponse.StatusCode == http.StatusNotFound { 35 | d.SetId("") 36 | return nil 37 | } 38 | return fmt.Errorf("%s %s: %s", msg, d.Id(), err) 39 | } 40 | -------------------------------------------------------------------------------- /cloudscale/utils_test.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 7 | ) 8 | 9 | var ( 10 | testAccCheckCloudscaleCustomImageExists = getTestAccCheckCloudscaleResourceExistsFunc(customImageHumanName, getId, readCustomImage) 11 | testAccCheckCloudscaleFloatingIPExists = getTestAccCheckCloudscaleResourceExistsFunc(floatingIPHumanName, getId, readFloatingIP) 12 | testAccCheckCloudscaleLoadBalancerExists = getTestAccCheckCloudscaleResourceExistsFunc(loadBalancerHumanName, getId, readLoadBalancer) 13 | testAccCheckCloudscaleLoadBalancerHealthMonitorExists = getTestAccCheckCloudscaleResourceExistsFunc(healthMonitorHumanName, getId, readLoadBalancerHealthMonitor) 14 | testAccCheckCloudscaleLoadBalancerListenerExists = getTestAccCheckCloudscaleResourceExistsFunc(listenerHumanName, getId, readLoadBalancerListener) 15 | testAccCheckCloudscaleLoadBalancerPoolExists = getTestAccCheckCloudscaleResourceExistsFunc(poolHumanName, getId, readLoadBalancerPool) 16 | testAccCheckCloudscaleLoadBalancerPoolMemberExists = getTestAccCheckCloudscaleResourceExistsFunc(poolMemberHumanName, getPoolId, readLoadBalancerPoolMember) 17 | testAccCheckCloudscaleNetworkExists = getTestAccCheckCloudscaleResourceExistsFunc(networkHumanName, getId, readNetwork) 18 | testAccCheckCloudscaleObjectsUserExists = getTestAccCheckCloudscaleResourceExistsFunc(objectsUserHumanName, getId, readObjectsUser) 19 | testAccCheckCloudscaleServerExists = getTestAccCheckCloudscaleResourceExistsFunc(serverHumanName, getId, readServer) 20 | testAccCheckCloudscaleServerGroupExists = getTestAccCheckCloudscaleResourceExistsFunc(serverGroupHumanName, getId, readServerGroup) 21 | testAccCheckCloudscaleSubnetExists = getTestAccCheckCloudscaleResourceExistsFunc(subnetHumanName, getId, readSubnet) 22 | testAccCheckCloudscaleVolumeExists = getTestAccCheckCloudscaleResourceExistsFunc(volumeHumanName, getId, readVolume) 23 | ) 24 | 25 | func getId(rs *terraform.ResourceState) GenericResourceIdentifier { 26 | return GenericResourceIdentifier{ 27 | Id: rs.Primary.ID, 28 | } 29 | } 30 | 31 | func getPoolId(rs *terraform.ResourceState) LoadBalancerPoolMemberResourceIdentifier { 32 | return LoadBalancerPoolMemberResourceIdentifier{ 33 | Id: rs.Primary.ID, 34 | PoolID: rs.Primary.Attributes["pool_uuid"], 35 | } 36 | } 37 | 38 | func getTestAccCheckCloudscaleResourceExistsFunc[TResource any, TResourceID any]( 39 | resourceType string, 40 | idFunc func(d *terraform.ResourceState) TResourceID, 41 | readFunc func(rId TResourceID, meta any, 42 | ) (*TResource, error)) func(n string, resource *TResource) resource.TestCheckFunc { 43 | return func( 44 | n string, 45 | resource *TResource, 46 | ) resource.TestCheckFunc { 47 | return func(s *terraform.State) error { 48 | rs, ok := s.RootModule().Resources[n] 49 | if !ok { 50 | return fmt.Errorf("not found: %s", n) 51 | } 52 | if rs.Primary.ID == "" { 53 | return fmt.Errorf("no %s ID is set", resourceType) 54 | } 55 | 56 | resourceId := idFunc(rs) 57 | retrievedResource, err := readFunc(resourceId, testAccProvider.Meta()) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | *resource = *retrievedResource 64 | 65 | return nil 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cloudscale/waitForStatus.go: -------------------------------------------------------------------------------- 1 | package cloudscale 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 5 | "math" 6 | "time" 7 | ) 8 | 9 | func waitForStatus( 10 | pending []string, 11 | target string, 12 | timeout *time.Duration, 13 | refreshFunc resource.StateRefreshFunc, 14 | ) (any, error) { 15 | if timeout == nil { 16 | defaultTimeout := 5 * time.Minute 17 | timeout = &(defaultTimeout) 18 | } 19 | 20 | stateConf := &resource.StateChangeConf{ 21 | Pending: pending, 22 | Target: []string{target}, 23 | Refresh: refreshFunc, 24 | Timeout: *timeout, 25 | Delay: 10 * time.Second, 26 | MinTimeout: 3 * time.Second, 27 | NotFoundChecks: math.MaxInt32, 28 | } 29 | 30 | return stateConf.WaitForState() 31 | } 32 | -------------------------------------------------------------------------------- /docs/data-sources/custom_image.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_custom_image" 3 | --- 4 | 5 | # cloudscale\_custom\_image 6 | 7 | Get information on a cloudscale.ch custom image. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_custom_image" "your_image" { 13 | name = "Your Distro 42.42" 14 | } 15 | 16 | # Create a Server using the custom image 17 | resource "cloudscale_server" "your_server" { 18 | name = "your-server" 19 | flavor_slug = "flex-8-4" 20 | image_uuid = "${data.cloudscale_custom_image.your_image.id}" 21 | volume_size_gb = 16 22 | zone_slug = "rma1" 23 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 24 | 25 | // If your image does not print complete SSH host keys to console during initial boot in the following format 26 | // enable the option below. 27 | // 28 | // -----BEGIN SSH HOST KEY KEYS----- 29 | // ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIdoMOxHQZwxnthOnUpd0Wl7TPRsJdj5KvW9YdE3Pbk 30 | // [... more keys ...] 31 | // -----END SSH HOST KEY KEYS----- 32 | // 33 | //skip_waiting_for_ssh_host_keys = true 34 | } 35 | ``` 36 | 37 | ## Argument Reference 38 | 39 | The following arguments can be used to look up a custom image: 40 | 41 | * `id` - (Optional) The UUID of a custom image. 42 | * `name` - (Optional) The human-readable name of a custom image. 43 | * `slug` - (Optional) A string identifying a custom image. 44 | 45 | ## Attributes Reference 46 | 47 | In addition to the arguments listed above, the following computed attributes are exported: 48 | 49 | * `href` - The cloudscale.ch API URL of the current resource. 50 | * `size_gb` - The size in GB of the custom image. 51 | * `checksums` - The checksums of the custom image as map. 52 | * `user_data_handling` - How user_data will be handled when creating a server. Options include `pass-through` and `extend-cloud-config`. 53 | * `firmware_type` - The firmware type that will be used for servers created with the custom image. Options include `bios` and `uefi`. 54 | * `zone_slugs` - The zones in which the custom image will be available. Options include `lpg1` and `rma1`. 55 | -------------------------------------------------------------------------------- /docs/data-sources/floating_ip.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_floating_ip" 3 | --- 4 | 5 | # cloudscale\_floating\_ip 6 | 7 | Provides access to cloudscale.ch Floating IP that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_floating_ip" "web-worker01-vip" { 13 | network = "192.0.2.42/32" 14 | } 15 | 16 | data "cloudscale_floating_ip" "web-worker01-net" { 17 | reverse_ptr = "vip.web-worker01.example.com" 18 | ip_version = 6 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments can be used to look up a Floating IP: 25 | 26 | * `id` - (Optional) The network IP of the floating IP, e.g. `192.0.2.0` of the network `192.0.2.0/24`. 27 | * `network` - (Optional) The CIDR notation of the Floating IP address or network, e.g. `192.0.2.123/32`. 28 | * `reverse_ptr` - (Optional) The PTR record (reverse DNS pointer) in case of a single Floating IP address. 29 | * `ip_version` - (Optional) `4` or `6`, for an IPv4 or IPv6 address or network respectively. 30 | * `region_slug` - (Optional) The slug of the region in which a Regional Floating IP is assigned. 31 | * `type` - (Optional) Options include `regional` and `global`. 32 | 33 | ## Attributes Reference 34 | 35 | In addition to the arguments listed above, the following computed attributes are exported: 36 | 37 | * `href` - The cloudscale.ch API URL of the current resource. 38 | * `next_hop` - The IP address of the server that your Floating IP is currently assigned to. 39 | * `server` - The UUID of the server that your Floating IP is currently assigned to. 40 | * `load_balancer` - The UUID of the load balancer that your Floating IP is currently assigned to. 41 | * `prefix_length` - The prefix length of a Floating IP (e.g. /128 or /56, as an integer). 42 | -------------------------------------------------------------------------------- /docs/data-sources/load_balancer.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer" 3 | --- 4 | 5 | # cloudscale\_load\_balancer 6 | 7 | Provides access to cloudscale.ch load balancers that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_load_balancer" "lb1" { 13 | name = "web-lb1" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a load balancer: 20 | 21 | * `id` - (Optional) The UUID of the load balancer. 22 | * `zone_slug` - (Optional) The slug of the zone in which the load balancer exists. Options include `lpg1` and `rma1`. 23 | 24 | ## Attributes Reference 25 | 26 | In addition to the arguments listed above, the following computed attributes are exported: 27 | 28 | * `href` - The cloudscale.ch API URL of the current resource. 29 | * `flavor_slug` - The slug (name) of the flavor to use for the new load balancer. Possible values can be found in our [API documentation](https://www.cloudscale.ch/en/api/v1#load-balancer-flavors). 30 | * `name` - Name of the new load balancer. 31 | * `status` - The current status of the load balancer. 32 | * `vip_addresses` - A list of VIP address objects. This attributes needs to be specified if the load balancer should be assigned a VIP address in a subnet on a private network. If the VIP address should be created on the public network, this attribute should be omitted. Each VIP address object has the following attributes: 33 | * `version` - The IP version, either `4` or `6`. 34 | * `subnet_href` - The cloudscale.ch API URL of the subnet the VIP address is part of. 35 | * `subnet_uuid` - The UUID of the subnet this VIP address should be part of. 36 | * `subnet_cidr` - The cidr of the subnet the VIP address is part of. 37 | * `address` - An VIP address that has been assigned to this load balancer. -------------------------------------------------------------------------------- /docs/data-sources/load_balancer_pool.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_pool" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_pool 6 | 7 | Provides access to cloudscale.ch load balancer pools that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_load_balancer_pool" "pool" { 13 | name = "web-lb1-pool" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a load balancer pool: 20 | 21 | * `id` - (Optional) The UUID of the load balancer pool. 22 | * `name` - (Optional) Name of the load balancer pool. 23 | * `load_balancer_uuid` - (Optional) The load balancer of the pool. 24 | 25 | ## Attributes Reference 26 | 27 | In addition to the arguments listed above, the following computed attributes are exported: 28 | 29 | * `href` - The cloudscale.ch API URL of the current resource. 30 | * `load_balancer_name` - The load balancer name of the pool. 31 | * `load_balancer_href` - The cloudscale.ch API URL of the pool's load balancer. 32 | * `algorithm` - The algorithm according to which the incoming traffic is distributed between the pool members. Options include `"round_robin"`, `"least_connections"` and `"source_ip"`. 33 | * `protocol` - The protocol used for traffic between the load balancer and the pool members. Options include: `"tcp"`, `"proxy"` and `"proxyv2"`. 34 | -------------------------------------------------------------------------------- /docs/data-sources/load_balancer_pool_health_monitor.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_health_monitor" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_health\_monitor 6 | 7 | Provides access to cloudscale.ch load balancer health monitors that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_load_balancer_health_monitor" "lb1-health-monitor" { 13 | id = "d38ed4f8-6a8d-4b3d-a2ff-87e53e4434e1" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a load balancer listener: 20 | 21 | * `id` - (Optional) The UUID of the load balancer health monitor. 22 | * `pool_uuid` - (Optional) The UUID of the pool this health monitor belongs to. 23 | 24 | ## Attributes Reference 25 | 26 | In addition to the arguments listed above, the following computed attributes are exported: 27 | 28 | * `href` - The cloudscale.ch API URL of the current resource. 29 | * `pool_href` = The cloudscale.ch API URL of the listener's load balancer pool. 30 | * `pool_name` = The load balancer pool name of the listener. 31 | * `delay_s` - The delay between two successive checks in seconds. 32 | * `timeout_s` - The maximum time allowed for an individual check in seconds. 33 | * `up_threshold` - The number of checks that need to be successful before the `monitor_status` of a pool member changes to `"up"`. 34 | * `down_threshold` - The number of checks that need to fail before the `monitor_status` of a pool member changes to `"down"`. 35 | * `type` - The type of the health monitor. Options include: `"ping"`, `"tcp"`, `"http"`, `"https"` and `"tls-hello"`. 36 | * `http_expected_codes` - The HTTP status codes allowed for a check to be considered successful. Can either be a list of status codes, for example `["200", "202"]`, or a list containing a single range, for example `["200-204"]`. 37 | * `http_method` - The HTTP method used for the check. Options include `"CONNECT"`, `"DELETE"`, `"GET"`, `"HEAD"`, `"OPTIONS"`, `"PATCH"`, `"POST"`, `"PUT"` and `"TRACE"`. 38 | * `http_url_path` - The URL used for the check. 39 | * `http_version` - The HTTP version used for the check. Options include `"1.0"` and `"1.1"`. 40 | * `http_host` - The server name in the HTTP Host: header used for the check. -------------------------------------------------------------------------------- /docs/data-sources/load_balancer_pool_listener.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_listener" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_listener 6 | 7 | Provides access to cloudscale.ch load balancer listeners that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_load_balancer_listener" "listener" { 13 | name = "web-lb1-listener" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a load balancer listener: 20 | 21 | * `id` - (Optional) The UUID of the load balancer listener. 22 | * `name` - (Optional) Name of the load balancer listener. 23 | * `pool_uuid` - (Optional) The UUID of the pool this listener belongs to. 24 | 25 | ## Attributes Reference 26 | 27 | In addition to the arguments listed above, the following computed attributes are exported: 28 | 29 | * `href` - The cloudscale.ch API URL of the current resource. 30 | * `pool_href` = The cloudscale.ch API URL of the listener's load balancer pool. 31 | * `pool_name` = The load balancer pool name of the listener. 32 | * `protocol` = The protocol used for receiving traffic. Options include `"tcp"`. 33 | * `protocol_port` = The port on which traffic is received. 34 | * `timeout_client_data_ms` = Client inactivity timeout in milliseconds. 35 | * `timeout_member_connect_ms` = Pool member connection timeout in milliseconds. 36 | * `timeout_member_data_ms` = Pool member inactivity timeout in milliseconds. 37 | * `allowed_cidrs` = Restrict the allowed source IPs for this listener. `[]` means that any source IP is allowed. If the list is non-empty, traffic from source IPs not included is denied. 38 | -------------------------------------------------------------------------------- /docs/data-sources/load_balancer_pool_member.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_pool_member" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_pool\_member 6 | 7 | Provides access to cloudscale.ch load balancer pool members that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_load_balancer_pool_member" "member" { 13 | pool_uuid = "4ffda9cc-7ba5-4193-a104-0d377fb84c96" # required! 14 | name = "web-lb1-pool-member-2" 15 | } 16 | ``` 17 | 18 | ## Argument Reference 19 | 20 | The following arguments can be used to look up a load balancer pool member: 21 | 22 | * `pool_uuid` - (Required) The UUID of the pool this member belongs to. 23 | * `id` - (Optional) The UUID of the load balancer pool. 24 | 25 | ## Attributes Reference 26 | 27 | In addition to the arguments listed above, the following computed attributes are exported: 28 | 29 | * `href` - The cloudscale.ch API URL of the current resource. 30 | * `enabled` - Pool member will not receive traffic if `false`. 31 | * `pool_name` - The load balancer name of the pool. 32 | * `pool_href` - The cloudscale.ch API URL of the pool's load balancer. 33 | * `protocol_port` - The port to which actual traffic is sent. 34 | * `monitor_port` - The port to which health monitor checks are sent. If not specified, `protocol_port` will be used. 35 | * `address` - The IP address to which traffic is sent. 36 | * `subnet_uuid` - The subnet UUID of the address must be specified here. 37 | * `subnet_cidr` - The CIDR of the member's address subnet. 38 | * `subnet_href` - The cloudscale.ch API URL of the member's address subnet. 39 | * `monitor_status` - The status of the pool's health monitor check for this member. Can be `"up"`, `"down"`, `"changing"`, `"no_monitor"` and `"unknown"`. -------------------------------------------------------------------------------- /docs/data-sources/network.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_network" 3 | --- 4 | 5 | # cloudscale\_network 6 | 7 | Provides access to cloudscale.ch private networks that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_network" "privnet" { 13 | name = "privnet" 14 | } 15 | 16 | # Add a server with two interfaces: 17 | # - one attached to the public network 18 | # - one attached to the private network "privnet" 19 | resource "cloudscale_server" "gw" { 20 | name = "gateway" 21 | zone_slug = "lpg1" 22 | flavor_slug = "flex-8-4" 23 | image_slug = "debian-11" 24 | volume_size_gb = 20 25 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 26 | interfaces { 27 | type = "public" 28 | } 29 | interfaces { 30 | type = "private" 31 | network_uuid = data.cloudscale_network.privnet.id 32 | } 33 | } 34 | ``` 35 | 36 | ## Argument Reference 37 | 38 | The following arguments can be used to look up a network: 39 | 40 | * `id` - (Optional) The UUID of a network. 41 | * `name` - (Optional) The name of a network. 42 | * `zone_slug` - (Optional) The zone slug of a network. Options include `lpg1` and `rma1`. 43 | 44 | 45 | ## Attributes Reference 46 | 47 | In addition to the arguments listed above, the following computed attributes are exported: 48 | 49 | * `href` - The cloudscale.ch API URL of the current network. 50 | * `mtu` - The MTU size for the network. 51 | * `subnets` - A list of subnet objects that are used in this network. Each subnet object has the following attributes: 52 | * `cidr` - The CIDR notation of the subnet. 53 | * `href` - The cloudscale.ch API URL of this subnet. 54 | * `uuid` - The UUID of this subnet. 55 | -------------------------------------------------------------------------------- /docs/data-sources/objects_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_objects_user" 3 | --- 4 | 5 | # cloudscale\_objects\_user 6 | 7 | Provides access to cloudscale.ch private networks that are not managed by terraform. 8 | 9 | **Hint**: When using this data source, your Terraform state will contain 10 | sensitive data, namely the Objects User secret key. Hence you should treat the 11 | Terraform state the same way as you treat the secret key itself. For more 12 | information, see here. 13 | 14 | ## Example Usage 15 | 16 | ```hcl 17 | data "cloudscale_objects_user" "basic" { 18 | display_name = "donald_knuth" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments can be used to look up an Objects User: 25 | 26 | * `id` - (Optional) The unique identifier of the Objects User. 27 | * `display_name` - (Optional) The display name of the Objects User. 28 | * `user_id` - (Optional) The unique identifier of the Objects User. (Exactly the same as `id`) 29 | 30 | ## Attributes Reference 31 | 32 | In addition to the arguments listed above, the following computed attributes are exported: 33 | 34 | * `href` - The cloudscale.ch API URL of the resource. 35 | * `keys` - A list of key objects containing the access and secret key associated with the Objects User. Currently, only one key object is returned. Each key object has the following attributes: 36 | * `access_key` - The S3 access key of the Objects User. 37 | * `secret_key` - The S3 secret key of the Objects User. 38 | -------------------------------------------------------------------------------- /docs/data-sources/server.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_server" 3 | --- 4 | 5 | # cloudscale\_server 6 | 7 | Provides access to cloudscale.ch servers that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_server" "web-worker01" { 13 | name = "web-worker01" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a server: 20 | 21 | * `id` - (Optional) The UUID of a server. 22 | * `name` - (Optional) Name of the server. 23 | * `zone_slug` - (Optional) The slug of the zone in which the server exists. Options include `lpg1` and `rma1`. 24 | 25 | ## Attributes Reference 26 | 27 | In addition to the arguments listed above, the following computed attributes are exported: 28 | 29 | * `id` - The UUID of this server. 30 | * `href` - The cloudscale.ch API URL of the current resource. 31 | * `ssh_fingerprints` - A list of SSH host key fingerprints (strings) of this server. 32 | * `ssh_host_keys` - A list of SSH host keys (strings) of this server. 33 | * `flavor_slug` - The slug (name) of the flavor used by this server. 34 | * `image_slug` - The slug (name) of the image (or custom image) used by the server. 35 | * `volumes` - A list of volume objects attached to this server. Each volume object has three attributes: 36 | * `device_path` - The path (string) to the volume on your server (e.g. `/dev/vda`) 37 | * `size_gb` - The size (int) of the volume in GB. Typically matches `volume_size_gb` or `bulk_volume_size_gb`. 38 | * `type` - A string. Either `ssd` or `bulk`. 39 | * `public_ipv4_address` - The first `public` IPv4 address of this server. The returned IP address may be `""` if the server does not have a public IPv4. 40 | * `private_ipv4_address` - The first `private` IPv4 address of this server. The returned IP address may be `""` if the server does not have private networking enabled. 41 | * `public_ipv6_address` - The first `public` IPv6 address of this server. The returned IP address may be `""` if the server does not have a public IPv6. 42 | * `interfaces` - A list of interface objects attached to this server. Each interface object has the following attributes: 43 | * `network_name` - The name of the network the interface is attached to. 44 | * `network_href` - The cloudscale.ch API URL of the network the interface is attached to. 45 | * `type` - Either `public` or `private`. Public interfaces are connected to the Internet, while private interfaces are not. 46 | * `addresses` - A list of address objects: 47 | * `gateway` - An IPv4 or IPv6 address that represents the default gateway for this interface. 48 | * `prefix_length` - The prefix length for this IP address, typically 24 for IPv4 and 128 for IPv6. 49 | * `reverse_ptr` - The PTR record (reverse DNS pointer) for this IP address. If you use an FQDN as your server name it will automatically be used here. 50 | * `version` - The IP version, either `4` or `6`. 51 | * `subnet_cidr` - The cidr of the subnet the address is part of. 52 | * `subnet_href` - The cloudscale.ch API URL of the subnet the address is part of. 53 | * `status` - The state of a server. 54 | -------------------------------------------------------------------------------- /docs/data-sources/server_group.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_server_group" 3 | --- 4 | 5 | # cloudscale\_server\_group 6 | 7 | Provides access to cloudscale.ch server group that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_server_group" "web-worker-group" { 13 | name = "web-worker-group" 14 | } 15 | 16 | # Create three new servers in that group 17 | resource "cloudscale_server" "web-worker01" { 18 | count = 3 19 | name = "web-worker${count.index}" 20 | flavor_slug = "flex-8-4" 21 | image_slug = "debian-11" 22 | server_group_ids = [data.cloudscale_server_group.web-worker-group.id] 23 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 24 | } 25 | ``` 26 | 27 | ## Argument Reference 28 | 29 | The following arguments can be used to look up a server group: 30 | 31 | * `id` - (Optional) The UUID of a server group. 32 | * `name` - (Optional) Name of the server group. 33 | * `zone_slug` - (Optional) The slug of the zone in which the server group exists. Options include `lpg1` and `rma1`. 34 | 35 | ## Attributes Reference 36 | 37 | In addition to the arguments listed above, the following computed attributes are exported: 38 | 39 | * `href` - The cloudscale.ch API URL of the current resource. 40 | * `type` - The type of the server group can currently only be `"anti-affinity"`. 41 | -------------------------------------------------------------------------------- /docs/data-sources/subnet.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_subnet" 3 | --- 4 | 5 | # cloudscale\_subnet 6 | 7 | Provides access to cloudscale.ch subnets that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_subnet" "privnet-subnet" { 13 | cidr = "10.11.12.0/24" 14 | } 15 | 16 | # Create a server with fixed IP address 17 | resource "cloudscale_server" "fixed" { 18 | name = "fix" 19 | zone_slug = "lpg1" 20 | flavor_slug = "flex-4-1" 21 | image_slug = "debian-11" 22 | interfaces { 23 | type = "public" 24 | } 25 | interfaces { 26 | type = "private" 27 | addresses { 28 | subnet_uuid = "${data.cloudscale_subnet.privnet-subnet.id}" 29 | address = "10.11.12.13" 30 | } 31 | } 32 | volume_size_gb = 10 33 | ssh_keys = ["ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY=", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY="] 34 | } 35 | ``` 36 | 37 | ## Argument Reference 38 | 39 | The following arguments can be used to look up a subnet: 40 | 41 | * `id` - (Optional) The UUID of the subnet. 42 | * `cidr` - (Optional) The address range in CIDR notation. 43 | * `network_uuid` - (Optional) The network UUID of the subnet. 44 | * `network_name` - (Optional) The network name of the subnet. 45 | * `gateway_address` - (Optional) The gateway address of the subnet. 46 | 47 | 48 | ## Attributes Reference 49 | 50 | In addition to the arguments listed above, the following computed attributes are exported: 51 | 52 | * `href` - The cloudscale.ch API URL of the current subnet. 53 | * `dns_servers` - A list of DNS resolver IP addresses, that act as DNS servers. 54 | * `network_href` - The cloudscale.ch API URL of the subnet's network. 55 | -------------------------------------------------------------------------------- /docs/data-sources/volume.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_volume" 3 | --- 4 | 5 | # cloudscale\_volume 6 | 7 | Provides access to cloudscale.ch volumes (block storage) that are not managed by terraform. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | data "cloudscale_volume" "web-worker01-volume" { 13 | name = "web-worker-data" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | The following arguments can be used to look up a volume: 20 | 21 | * `id` - (Optional) The UUID of a volume. 22 | * `name` - (Optional) The Name of the volume. 23 | * `zone_slug` - (Optional) The slug of the zone in which the new volume will be created. Options include `lpg1` and `rma1`. 24 | * `type` - (Optional) For SSD/NVMe volumes "ssd" (default); or "bulk" for our HDD cluster with NVMe caching. 25 | 26 | ## Attributes Reference 27 | 28 | In addition to the arguments listed above, the following computed attributes are exported: 29 | 30 | * `href` - The cloudscale.ch API URL of the current resource. 31 | * `size_gb` - The volume size in GB. Valid values are multiples of 1 for type "ssd" and multiples of 100 for type "bulk". 32 | * `server_uuids` - (Optional) A list of server UUIDs. 33 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "Provider: cloudscale.ch" 3 | --- 4 | 5 | # cloudscale.ch Provider 6 | 7 | The cloudscale.ch provider is used to interact with the resources supported by cloudscale.ch. The provider needs to be configured with proper credentials before it can be used. 8 | 9 | Use the navigation to the left to read about the available resources. 10 | 11 | ## Example Usage 12 | 13 | Terraform 0.13 and later: 14 | ```hcl 15 | terraform { 16 | required_providers { 17 | cloudscale = { 18 | source = "cloudscale-ch/cloudscale" 19 | // The version attribute can be used to pin to a specific version 20 | //version = "~> 3.0.0" 21 | } 22 | } 23 | } 24 | 25 | # Create a resource 26 | resource "cloudscale_server" "web-worker01" { 27 | # ... 28 | } 29 | ``` 30 | 31 | Terraform 0.12 and earlier: 32 | ```hcl 33 | provider "cloudscale" { 34 | version = "~> 2.3.0" 35 | } 36 | 37 | # Create a resource 38 | resource "cloudscale_server" "web-worker01" { 39 | # ... 40 | } 41 | ``` 42 | 43 | ## Authentication 44 | 45 | Please create a cloudscale.ch API token with read/write access in 46 | our [Cloud Control Panel](https://control.cloudscale.ch/). You can then 47 | pass the token to the provider using one of the following methods: 48 | 49 | ### Environment variable (recommended) 50 | 51 | Set a shell environment variable called `CLOUDSCALE_API_TOKEN`. 52 | 53 | ### Static credentials 54 | 55 | Add the following configuration: 56 | 57 | ```hcl 58 | # Set the variable value in a *.tfvars file or use 59 | # the -var="cloudscale_api_token=..." CLI option. 60 | variable "cloudscale_api_token" {} 61 | 62 | provider "cloudscale" { 63 | token = var.cloudscale_api_token 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/resources/custom_image.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_custom_image" 3 | --- 4 | 5 | # cloudscale\_custom\_image 6 | 7 | Provides a cloudscale.ch custom image resource. This can be used to create, modify, import, and delete custom images. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a custom image 13 | resource "cloudscale_custom_image" "your_image" { 14 | import_url = "https://mirror.example.com/your-distro-12.12-openstack-amd64.raw" 15 | name = "Your Distro 12.12" 16 | slug = "your-distro-12.12" 17 | user_data_handling = "extend-cloud-config" 18 | firmware_type = "bios" 19 | zone_slugs = ["rma1"] 20 | 21 | timeouts { 22 | create = "10m" 23 | } 24 | } 25 | 26 | # Create a Server using the custom image 27 | resource "cloudscale_server" "your_server" { 28 | name = "your-server" 29 | flavor_slug = "flex-8-4" 30 | image_uuid = "${cloudscale_custom_image.your_image.id}" 31 | volume_size_gb = 16 32 | zone_slug = "rma1" 33 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 34 | 35 | // If your image does not print complete SSH host keys to console during initial boot in the following format 36 | // enable the option below. 37 | // 38 | // -----BEGIN SSH HOST KEY KEYS----- 39 | // ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIdoMOxHQZwxnthOnUpd0Wl7TPRsJdj5KvW9YdE3Pbk 40 | // [... more keys ...] 41 | // -----END SSH HOST KEY KEYS----- 42 | // 43 | //skip_waiting_for_ssh_host_keys = true 44 | } 45 | ``` 46 | 47 | ## Argument Reference 48 | 49 | The following arguments are supported when creating/changing custom images: 50 | 51 | * `import_url` - (Required) The URL used to download the image. 52 | * `import_source_format` - (Optional) The file format of the image referenced in the `import_url`. Options include `raw`, `qcow2`. 53 | * `name` - (Required) The human-readable name of the custom image. 54 | * `slug` - (Optional) A string identifying the custom image for use within the API. 55 | * `user_data_handling` - (Required) How user_data will be handled when creating a server. Options include `pass-through` and `extend-cloud-config`. 56 | * `firmware_type` - (Optional) The firmware type that will be used for servers created with the custom image. Options include `bios` and `uefi`. 57 | * `zone_slugs` - (Required) Specify the zones in which the custom image will be available. Options include `lpg1` and `rma1`. 58 | * `timeouts` - (Optional) Specify how long certain operations are allowed to take before being considered to have failed. Currently, only the `create` timeout can be specified. Takes a string representation of a duration, such as `20m` for 20 minutes (default), `10s` for ten seconds, or `2h` for two hours. 59 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 60 | ```hcl 61 | tags = { 62 | foo = "bar" 63 | } 64 | ``` 65 | Tags are always strings (both keys and values). 66 | 67 | ## Attributes Reference 68 | 69 | In addition to the arguments listed above, the following computed attributes are exported: 70 | 71 | * `href` - The cloudscale.ch API URL of the current resource. 72 | * `size_gb` - The size in GB of the custom image. 73 | * `checksums` - The checksums of the custom image as map. 74 | * `import_href` - The cloudscale.ch API URL of the custom image import. 75 | * `import_uuid` - The UUID of the custom image import. 76 | * `import_status` - The status of the custom image import. Options include `started`, `in_progress`, `failed`, `success`. 77 | 78 | 79 | ## Import 80 | 81 | Custom images can currently not be imported, please use a data source. 82 | -------------------------------------------------------------------------------- /docs/resources/floating_ip.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_floating_ip" 3 | --- 4 | 5 | # cloudscale\_floating\_ip 6 | 7 | Provides a cloudscale.ch Floating IP to represent a publicly-accessible static IP address or IP network that can be assigned to one of your cloudscale.ch servers. Floating IPs can be moved between servers. Possible use cases include: High-availability, non-disruptive maintenance, multiple IPs per server, or re-using the same IP after replacing a server. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new Server 13 | resource "cloudscale_server" "web-worker01" { 14 | name = "web-worker01" 15 | flavor_slug = "flex-8-4" 16 | image_slug = "debian-11" 17 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 18 | } 19 | 20 | # Add a Floating IPv4 address to web-worker01 21 | resource "cloudscale_floating_ip" "web-worker01-vip" { 22 | server = cloudscale_server.web-worker01.id 23 | ip_version = 4 24 | reverse_ptr = "vip.web-worker01.example.com" 25 | } 26 | 27 | # Add a Floating IPv6 network to web-worker01 28 | resource "cloudscale_floating_ip" "web-worker01-net" { 29 | server = cloudscale_server.web-worker01.id 30 | ip_version = 6 31 | prefix_length = 56 32 | } 33 | ``` 34 | 35 | ## Argument Reference 36 | 37 | The following arguments are supported when adding Floating IPs: 38 | 39 | * `server` - (Optional) Assign the Floating IP to this server (UUID). 40 | * `load_balancer` - (Optional) Assign the Floating IP to this load balancer (UUID). 41 | * `ip_version` - (Required) `4` or `6`, for an IPv4 or IPv6 address or network respectively. 42 | * `prefix_length` - (Optional) If you want to assign an entire network instead of a single IP address to your server, you must specify the prefix length. Currently, there is only support for `ip_version=6` and `prefix_length=56`. 43 | * `type` - (Optional) You can specify the type. Options include `regional` (default) and `global`. 44 | * `region_slug` - (Optional) The slug of the region in which the new Regional Floating IP will be created. Options include `lpg` and `rma`. 45 | * `reverse_ptr` - (Optional) You can specify the PTR record (reverse DNS pointer) in case of a single Floating IP address. 46 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 47 | ```hcl 48 | tags = { 49 | foo = "bar" 50 | } 51 | ``` 52 | Tags are always strings (both keys and values). 53 | 54 | The following arguments are supported when updating Floating IPs: 55 | 56 | * `server` - (Optional) (Re-)Assign the Floating IP to this server (UUID). 57 | * `load_balancer` - (Optional) (Re-)Assign the Floating IP to this load balancer (UUID). 58 | * `reverse_ptr` - (Optional) You can specify the new PTR record (reverse DNS pointer) in case of a single Floating IP address. 59 | * `tags` - (Optional) Change tags (see documentation above) 60 | 61 | ## Attributes Reference 62 | 63 | In addition to the arguments listed above, the following computed attributes are exported: 64 | 65 | * `href` - The cloudscale.ch API URL of the current resource. 66 | * `network` - The CIDR notation of the Floating IP address or network, e.g. `192.0.2.123/32`. 67 | * `next_hop` - The IP address of the server or load balancer that your Floating IP is currently assigned to. 68 | 69 | 70 | ## Import 71 | 72 | Floating IPs can be imported using the Floating IP's network IP: 73 | 74 | ``` 75 | terraform import cloudscale_floating_ip.floating_ip 192.0.2.24 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/resources/load_balancer.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer" 3 | --- 4 | 5 | # cloudscale\_load_balancer 6 | 7 | Provides a cloudscale.ch load balancer resource. This can be used to create, modify, import, and delete load balancers. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new load balancer 13 | resource "cloudscale_load_balancer" "lb1" { 14 | name = "web-lb1" 15 | flavor_slug = "lb-standard" 16 | zone_slug = "lpg1" 17 | } 18 | ``` 19 | 20 | ## Argument Reference 21 | 22 | The following arguments are supported when creating new load balancer: 23 | 24 | * `name` - (Required) Name of the new load balancer. 25 | * `flavor_slug` - (Required) The slug (name) of the flavor to use for the new load balancer. Possible values can be found in our [API documentation](https://www.cloudscale.ch/en/api/v1#load-balancer-flavors). 26 | **Note:** It's currently not possible to update the flavor after the load balancer has been created. It is therfore recommended to use load balancer in conjunction with a Floating IP. 27 | * `zone_slug` - (Required) The slug of the zone in which the new load balancer will be created. Options include `lpg1` and `rma1`. 28 | * `vip_addresses` - (Optional) A list of VIP address objects. This attributes needs to be specified if the load balancer should be assigned a VIP address in a subnet on a private network. If the VIP address should be created on the public network, this attribute should be omitted. Each VIP address object has the following attributes: 29 | * `subnet_uuid` - (Optional) The UUID of the subnet this VIP address should be part of. 30 | * `address` - (Optional) An VIP address that has been assigned to this load balancer. 31 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 32 | ```hcl 33 | tags = { 34 | foo = "bar" 35 | } 36 | ``` 37 | Tags are always strings (both keys and values). 38 | 39 | The following arguments are supported when updating load balancers: 40 | 41 | * `name` - New name of the load balancer. 42 | * `tags` - Change tags (see documentation above) 43 | 44 | **Note on `vip_addresses`: It might be necessary to manually `terrafrom destroy` a load balancer in order 45 | for Terraform to detect the change correctly. A replacement of the load balancer is required in all cases.** 46 | 47 | 48 | ## Attributes Reference 49 | 50 | In addition to the arguments listed above, the following computed attributes are exported: 51 | 52 | * `id` - The UUID of this load balancer. 53 | * `href` - The cloudscale.ch API URL of the current resource. 54 | * `status` - The current status of the load balancer. 55 | * `vip_addresses` - A list of VIP address objects. Each VIP address object has the following attributes: 56 | * `version` - The IP version, either `4` or `6`. 57 | * `subnet_cidr` - The cidr of the subnet the VIP address is part of. 58 | * `subnet_href` - The cloudscale.ch API URL of the subnet the VIP address is part of. 59 | 60 | ## Import 61 | 62 | Load balancer can be imported using the load balancer's UUID: 63 | 64 | ``` 65 | terraform import cloudscale_load_balancer.lb 48151623-42aa-aaaa-bbbb-caffeeeeeeee 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/resources/load_balancer_health_monitor.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_health_monitor" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_health_monitor 6 | 7 | Provides a cloudscale.ch load balancer health monitor resource. This can be used to create, modify, import, and delete load balancer health monitors. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # all resources from the cloudscale_load_balancer_pool_member example.. 13 | # ..followed by: 14 | 15 | # Create a new load balancer health monitor 16 | resource "cloudscale_load_balancer_health_monitor" "lb1-health-monitor" { 17 | pool_uuid = cloudscale_load_balancer_pool.lb1-pool.id 18 | type = "http" 19 | http_url_path = "/" 20 | http_version = "1.1" 21 | http_host = "www.cloudscale.ch" 22 | } 23 | ``` 24 | 25 | ## Argument Reference 26 | 27 | The following arguments are supported when creating new load balancer health monitor: 28 | 29 | * `name` - (Required) Name of the new load balancer health monitor. 30 | * `pool_uuid` - (Required) The pool of the health monitor. 31 | 32 | * `type` - (Required) The type of the health monitor. Options include: `"ping"`, `"tcp"`, `"http"`, `"https"` and `"tls-hello"`. 33 | * `delay_s` - (Optional) The delay between two successive checks in seconds. Default is `2`. 34 | * `timeout_s` - (Optional) The maximum time allowed for an individual check in seconds. Default is `1`. 35 | * `up_threshold` - (Optional) The number of checks that need to be successful before the `monitor_status` of a pool member changes to `"up"`. Default is `2`. 36 | * `down_threshold` - (Optional) The number of checks that need to fail before the `monitor_status` of a pool member changes to `"down"`. Default is `3`. 37 | * `http_expected_codes` - (Optional) The HTTP status codes allowed for a check to be considered successful. Can either be a list of status codes, for example `["200", "202"]`, or a list containing a single range, for example `["200-204"]`. Default is `["200"]`. 38 | * `http_method` - (Optional) The HTTP method used for the check. Options include `"CONNECT"`, `"DELETE"`, `"GET"`, `"HEAD"`, `"OPTIONS"`, `"PATCH"`, `"POST"`, `"PUT"` and `"TRACE"`. Default is `"GET"`. 39 | * `http_url_path` - (Optional) The URL used for the check. Default is `"/"`. 40 | * `http_version` - (Optional) The HTTP version used for the check. Options include `"1.0"` and `"1.1"`. Default is `"1.1"`. 41 | * `http_host` - (Optional) The server name in the HTTP Host: header used for the check. Requires version to be set to `"1.1"`. 42 | 43 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 44 | ```hcl 45 | tags = { 46 | foo = "bar" 47 | } 48 | ``` 49 | Tags are always strings (both keys and values). 50 | 51 | The following arguments are supported when updating load balancer health monitor: 52 | 53 | * `delay_s` - The delay between two successive checks in seconds. Default is `2`. 54 | * `timeout_s` - The maximum time allowed for an individual check in seconds. Default is `1`. 55 | * `up_threshold` - The number of checks that need to be successful before the `monitor_status` of a pool member changes to `"up"`. Default is `2`. 56 | * `down_threshold` - The number of checks that need to fail before the `monitor_status` of a pool member changes to `"down"`. Default is `3`. 57 | * `http_expected_codes` - The HTTP status codes allowed for a check to be considered successful. Can either be a list of status codes, for example `["200", "202"]`, or a list containing a single range, for example `["200-204"]`. Default is `["200"]`. 58 | * `http_method` - The HTTP method used for the check. Options include `"CONNECT"`, `"DELETE"`, `"GET"`, `"HEAD"`, `"OPTIONS"`, `"PATCH"`, `"POST"`, `"PUT"` and `"TRACE"`. Default is `"GET"`. 59 | * `http_url_path` - The URL used for the check. Default is `"/"`. 60 | * `http_host` - The server name in the HTTP Host: header used for the check. Requires version to be set to `"1.1"`. 61 | * `tags` - Change tags (see documentation above) 62 | 63 | ## Attributes Reference 64 | 65 | In addition to the arguments listed above, the following computed attributes are exported: 66 | 67 | * `id` - The UUID of this load balancer health monitor. 68 | * `href` - The cloudscale.ch API URL of the current resource. 69 | * `pool_name` - The load balancer pool name of the health monitor. 70 | * `pool_href` - The cloudscale.ch API URL of the health monitor's load balancer pool. 71 | 72 | 73 | ## Import 74 | 75 | Load balancer health monitor can be imported using the load balancer health monitor's UUID: 76 | 77 | ``` 78 | terraform import cloudscale_load_balancer_health_monitor.lb1-health-monitor 48151623-42aa-aaaa-bbbb-caffeeeeeeee 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/resources/load_balancer_listener.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_listener" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_listener 6 | 7 | Provides a cloudscale.ch load balancer listener resource. This can be used to create, modify, import, and delete load balancer listeners. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "cloudscale_load_balancer" "lb1" { 13 | name = "web-lb1" 14 | flavor_slug = "lb-standard" 15 | zone_slug = "lpg1" 16 | } 17 | 18 | # Create a new load balancer pool 19 | resource "cloudscale_load_balancer_pool" "lb1-pool" { 20 | name = "web-lb1-pool" 21 | algorithm = "round_robin" 22 | protocol = "tcp" 23 | load_balancer_uuid = cloudscale_load_balancer.lb1.id 24 | } 25 | 26 | # Create a new load balancer listener 27 | resource "cloudscale_load_balancer_listener" "lb1-listener" { 28 | name = "web-lb1-listener" 29 | pool_uuid = cloudscale_load_balancer_pool.lb1-pool.id 30 | protocol = "tcp" 31 | protocol_port = 80 32 | } 33 | ``` 34 | 35 | ## Argument Reference 36 | 37 | The following arguments are supported when creating new load balancer listener: 38 | 39 | * `name` - (Required) Name of the new load balancer listener. 40 | * `pool_uuid` - (Optional/Required depending on protocol) The pool of the listener. 41 | * `protocol` - (Required) The protocol used for receiving traffic. Options include `"tcp"`. 42 | * `protocol_port` - (Required) The port on which traffic is received. 43 | * `timeout_client_data_ms` - (Optional) Client inactivity timeout in milliseconds. 44 | * `timeout_member_connect_ms` - (Optional) Pool member connection timeout in milliseconds. 45 | * `timeout_member_data_ms` - (Optional) Pool member inactivity timeout in milliseconds. 46 | * `allowed_cidrs` - (Optional) Restrict the allowed source IPs for this listener. `[]` means that any source IP is allowed. If the list is non-empty, traffic from source IPs not included is denied. 47 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 48 | ```hcl 49 | tags = { 50 | foo = "bar" 51 | } 52 | ``` 53 | Tags are always strings (both keys and values). 54 | 55 | The following arguments are supported when updating load balancer listener: 56 | 57 | * `name` - New name of the load balancer listener. 58 | * `protocol` - The protocol used for receiving traffic. Options include `"tcp"`. 59 | * `protocol_port` - The port on which traffic is received. 60 | * `timeout_client_data_ms` - Client inactivity timeout in milliseconds. 61 | * `timeout_member_connect_ms` - Pool member connection timeout in milliseconds. 62 | * `timeout_member_data_ms` - Pool member inactivity timeout in milliseconds. 63 | * `allowed_cidrs` - Restrict the allowed source IPs for this listener. `[]` means that any source IP is allowed. If the list is non-empty, traffic from source IPs not included is denied. 64 | * `tags` - Change tags (see documentation above) 65 | 66 | ## Attributes Reference 67 | 68 | In addition to the arguments listed above, the following computed attributes are exported: 69 | 70 | * `id` - The UUID of this load balancer listner. 71 | * `href` - The cloudscale.ch API URL of the current resource. 72 | * `pool_name` - The load balancer pool name of the listener. 73 | * `pool_href` - The cloudscale.ch API URL of the listener's load balancer pool. 74 | 75 | 76 | ## Import 77 | 78 | Load balancer listener can be imported using the load balancer listener's UUID: 79 | 80 | ``` 81 | terraform import cloudscale_load_balancer_listener.lb1-listener 48151623-42aa-aaaa-bbbb-caffeeeeeeee 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/resources/load_balancer_pool.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_pool" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_pool 6 | 7 | Provides a cloudscale.ch load balancer pool resource. This can be used to create, modify, import, and delete load balancer pools. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new load balancer 13 | resource "cloudscale_load_balancer" "lb1" { 14 | name = "web-lb1" 15 | flavor_slug = "lb-standard" 16 | zone_slug = "lpg1" 17 | } 18 | 19 | # Create a new load balancer pool 20 | resource "cloudscale_load_balancer_pool" "lb1-pool" { 21 | name = "web-lb1-pool" 22 | algorithm = "round_robin" 23 | protocol = "tcp" 24 | load_balancer_uuid = cloudscale_load_balancer.lb1.id 25 | } 26 | ``` 27 | 28 | ## Argument Reference 29 | 30 | The following arguments are supported when creating new load balancer pool: 31 | 32 | * `name` - (Required) Name of the new load balancer pool. 33 | * `algorithm` - (Required) The algorithm according to which the incoming traffic is distributed between the pool members. Options include `"round_robin"`, `"least_connections"` and `"source_ip"`. 34 | * `protocol` - (Required) The protocol used for traffic between the load balancer and the pool members. Options include: `"tcp"`, `"proxy"` and `"proxyv2"`. 35 | * `load_balancer_uuid` - (Required) The load balancer of the pool. 36 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 37 | ```hcl 38 | tags = { 39 | foo = "bar" 40 | } 41 | ``` 42 | Tags are always strings (both keys and values). 43 | 44 | The following arguments are supported when updating load balancer pools: 45 | 46 | * `name` - New name of the load balancer pool. 47 | * `tags` - Change tags (see documentation above) 48 | 49 | ## Attributes Reference 50 | 51 | In addition to the arguments listed above, the following computed attributes are exported: 52 | 53 | * `id` - The UUID of this load balancer pool. 54 | * `href` - The cloudscale.ch API URL of the current resource. 55 | * `load_balancer_name` - The load balancer name of the pool. 56 | * `load_balancer_href` - The cloudscale.ch API URL of the pool's load balancer. 57 | 58 | 59 | ## Import 60 | 61 | Load balancer pools can be imported using the load balancer pool's UUID: 62 | 63 | ``` 64 | terraform import cloudscale_load_balancer_pool.lb1-pool 48151623-42aa-aaaa-bbbb-caffeeeeeeee 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/resources/load_balancer_pool_member.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_load_balancer_pool_member" 3 | --- 4 | 5 | # cloudscale\_load\_balancer\_member 6 | 7 | Provides a cloudscale.ch load balancer pool member resource. This can be used to create, modify, import, and delete load balancer pool members. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new network 13 | resource "cloudscale_network" "backend" { 14 | name = "backend" 15 | zone_slug = "lpg1" 16 | auto_create_ipv4_subnet = "false" 17 | } 18 | 19 | # Create a new subnet 20 | resource "cloudscale_subnet" "backend-subnet" { 21 | cidr = "10.11.12.0/24" 22 | network_uuid = cloudscale_network.backend.id 23 | } 24 | 25 | # Create new workers 26 | resource "cloudscale_server" "web-worker" { 27 | count = 2 28 | name = "web-worker${count.index}" 29 | flavor_slug = "flex-4-2" 30 | image_slug = "ubuntu-22.04" 31 | zone_slug = "lpg1" 32 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 33 | 34 | 35 | interfaces { 36 | type = "public" 37 | } 38 | 39 | interfaces { 40 | type = "private" 41 | addresses { 42 | subnet_uuid = cloudscale_subnet.backend-subnet.id 43 | } 44 | } 45 | } 46 | 47 | # Create a new load balancer 48 | resource "cloudscale_load_balancer" "lb1" { 49 | name = "web-lb1" 50 | flavor_slug = "lb-standard" 51 | zone_slug = "lpg1" 52 | } 53 | 54 | # Create a new load balancer pool 55 | resource "cloudscale_load_balancer_pool" "lb1-pool" { 56 | name = "web-lb1-pool" 57 | algorithm = "round_robin" 58 | protocol = "tcp" 59 | load_balancer_uuid = cloudscale_load_balancer.lb1.id 60 | } 61 | 62 | # Create a new load balancer pool member 63 | resource "cloudscale_load_balancer_pool_member" "lb1-pool-member" { 64 | count = 2 65 | name = "web-lb1-pool-member-${count.index}" 66 | pool_uuid = cloudscale_load_balancer_pool.lb1-pool.id 67 | protocol_port = 80 68 | address = cloudscale_server.web-worker[count.index].interfaces[1].addresses[0].address 69 | subnet_uuid = cloudscale_subnet.backend-subnet.id 70 | } 71 | ``` 72 | 73 | ## Argument Reference 74 | 75 | The following arguments are supported when creating new load balancer pool: 76 | 77 | * `name` - (Required) Name of the new load balancer pool member. 78 | * `enabled` - (Optional) Pool member will not receive traffic if `false`. Default is `true`. 79 | * `pool_uuid` - (Required) The load balancer pool of the member. 80 | * `protocol_port` - (Required) The port to which actual traffic is sent. 81 | * `monitor_port` - (Optional) The port to which health monitor checks are sent. If not specified, `protocol_port` will be used. 82 | * `address` - (Required) The IP address to which traffic is sent. 83 | * `subnet_uuid` - (Required) The subnet UUID of the address must be specified here. 84 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 85 | ```hcl 86 | tags = { 87 | foo = "bar" 88 | } 89 | ``` 90 | Tags are always strings (both keys and values). 91 | 92 | The following arguments are supported when updating load balancer pool members: 93 | 94 | * `name` - New name of the load balancer pool. 95 | * `enabled` - Pool member will not receive traffic if `false`. 96 | * `tags` - Change tags (see documentation above) 97 | 98 | ## Attributes Reference 99 | 100 | In addition to the arguments listed above, the following computed attributes are exported: 101 | 102 | * `id` - The UUID of this load balancer pool member. 103 | * `href` - The cloudscale.ch API URL of the current resource. 104 | * `monitor_status` - The status of the pool's health monitor check for this member. Can be `"up"`, `"down"`, `"changing"`, `"no_monitor"` and `"unknown"`. 105 | * `pool_name` - The load balancer pool name of the member. 106 | * `pool_href` - The cloudscale.ch API URL of the member's load balancer pool. 107 | * `subnet_cidr` - The CIDR of the member's address subnet. 108 | * `subnet_href` - The cloudscale.ch API URL of the member's address subnet. 109 | 110 | 111 | ## Import 112 | 113 | Load balancer pool members can be imported using the load balancer pool member's UUID and the pool UUID 114 | using this schema `{pool_uuid}.{member_uuid}`: 115 | 116 | ``` 117 | terraform import cloudscale_load_balancer_pool_member.lb1-pool-member 48151623-42aa-aaaa-bbbb-caffeeeeeeee.6a18a377-9977-4cd0-b1fa-70908356efaa 118 | ``` 119 | -------------------------------------------------------------------------------- /docs/resources/network.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_network" 3 | --- 4 | 5 | # cloudscale\_network 6 | 7 | Provides a cloudscale.ch private network resource. This can be used to create, modify, import, and delete networks. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new private network 13 | resource "cloudscale_network" "privnet" { 14 | name = "privnet" 15 | zone_slug = "lpg1" 16 | mtu = "9000" 17 | } 18 | 19 | # Add a server with two interfaces: 20 | # - one attached to the public network 21 | # - one attached to the private network "privnet" 22 | resource "cloudscale_server" "gw" { 23 | name = "gateway" 24 | zone_slug = "lpg1" 25 | flavor_slug = "flex-8-4" 26 | image_slug = "debian-11" 27 | volume_size_gb = 20 28 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 29 | interfaces { 30 | type = "public" 31 | } 32 | interfaces { 33 | type = "private" 34 | network_uuid = cloudscale_network.privnet.id 35 | } 36 | } 37 | ``` 38 | 39 | ## Argument Reference 40 | 41 | The following arguments are supported when creating/changing networks: 42 | 43 | * `name` - (Required) Name of the network. 44 | * `zone_slug` - (Optional) The slug of the zone in which the new network will be created. Options include `lpg1` and `rma1`. 45 | * `mtu` - (Optional) You can specify the MTU size for the network, defaults to 9000. 46 | * `auto_create_ipv4_subnet` - (Optional) Automatically create an IPv4 Subnet on the network. Can be `true` (default) or `false`. 47 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 48 | ```hcl 49 | tags = { 50 | foo = "bar" 51 | } 52 | ``` 53 | Tags are always strings (both keys and values). 54 | 55 | 56 | ## Attributes Reference 57 | 58 | In addition to the arguments listed above, the following computed attributes are exported: 59 | 60 | * `href` - The cloudscale.ch API URL of the current network. 61 | * `subnets` - A list of subnet objects that are used in this network. Each subnet object has the following attributes: 62 | * `cidr` - The CIDR notation of the subnet. 63 | * `href` - The cloudscale.ch API URL of this subnet. 64 | * `uuid` - The UUID of this subnet. 65 | 66 | 67 | ## Import 68 | 69 | Networks can be imported using the network's UUID: 70 | 71 | ``` 72 | terraform import cloudscale_network.network 48151623-42aa-aaaa-bbbb-caffeeeeeeee 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/resources/objects_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_objects_user" 3 | --- 4 | 5 | # cloudscale\_objects\_user 6 | 7 | Provides a cloudscale.ch Objects User for the S3-compatible object storage. 8 | 9 | **Hint**: When using this resource, your Terraform state will contain sensitive data, namely the Objects User secret 10 | key. Hence you should treat the Terraform state the same way as you treat the secret key itself. For more 11 | information, see here. 12 | 13 | ## Example Usage 14 | 15 | ```hcl 16 | # Create an Objects User 17 | resource "cloudscale_objects_user" "basic" { 18 | display_name = "donald_knuth" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments are supported when adding Objects Users: 25 | 26 | * `display_name` - (Required) The display name of the Objects User. 27 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 28 | ```hcl 29 | tags = { 30 | foo = "bar" 31 | } 32 | ``` 33 | Tags are always strings (both keys and values). 34 | 35 | The following arguments are supported when updating Objects Users: 36 | 37 | * `display_name` - (Required) The new display name of the Objects User. 38 | * `tags` - (Optional) Change tags (see documentation above) 39 | 40 | ## Attributes Reference 41 | 42 | In addition to the arguments listed above, the following computed attributes are exported: 43 | 44 | * `href` - The cloudscale.ch API URL of the current resource. 45 | * `user_id` - The unique identifier of the Objects User. 46 | * `keys` - A list of key objects containing the access and secret key associated with the Objects User. Currently, only one key object is returned. Each key object has the following attributes: 47 | * `access_key` - The S3 access key of the Objects User. 48 | * `secret_key` - The S3 secret key of the Objects User. 49 | 50 | 51 | ## Import 52 | 53 | Objects Users can be imported using the Objects User's ID: 54 | 55 | ``` 56 | terraform import cloudscale_objects_user.objects_user 192f95401a23ef307d42e4ba0fdc475e9630db45132a5b499d1dd2425c28a0ca 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/resources/server_group.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_server_group" 3 | --- 4 | 5 | # cloudscale\_server\_group 6 | 7 | Provides a cloudscale.ch server group resource. This can be used to create, modify, import, and delete server groups. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Add a server group with anti affinity 13 | resource "cloudscale_server_group" "web-worker-group" { 14 | name = "web-worker-group" 15 | type = "anti-affinity" 16 | } 17 | 18 | # Create three new servers in that group 19 | resource "cloudscale_server" "web-worker01" { 20 | count = 3 21 | name = "web-worker${count.index}" 22 | flavor_slug = "flex-8-4" 23 | image_slug = "debian-11" 24 | server_group_ids = [cloudscale_server_group.web-worker-group.id] 25 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 26 | } 27 | ``` 28 | 29 | ## Argument Reference 30 | 31 | The following arguments are supported when creating server groups: 32 | 33 | * `name` - (Required) Name of the new server group. 34 | * `type` - (Required) The type of the server group can currently only be `"anti-affinity"`. 35 | * `zone_slug` - (Optional) The slug of the zone in which the new server group will be created. Options include `lpg1` and `rma1`. 36 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 37 | ```hcl 38 | tags = { 39 | foo = "bar" 40 | } 41 | ``` 42 | Tags are always strings (both keys and values). 43 | 44 | The following arguments are supported when updating server groups: 45 | 46 | * `name` - The new name of the server group. 47 | * `tags` - (Optional) Change tags (see documentation above) 48 | 49 | ## Attributes Reference 50 | 51 | In addition to the arguments listed above, the following computed attributes are exported: 52 | 53 | * `href` - The cloudscale.ch API URL of the current resource. 54 | 55 | 56 | ## Import 57 | 58 | Server groups can be imported using the server group's UUID: 59 | 60 | ``` 61 | terraform import cloudscale_server_group.server_group 48151623-42aa-aaaa-bbbb-caffeeeeeeee 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/resources/subnet.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_subnet" 3 | --- 4 | 5 | # cloudscale\_subnet 6 | 7 | Provides a cloudscale.ch subnet resource. This can be used to create, modify, import, and delete subnets. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new private network 13 | resource "cloudscale_network" "privnet" { 14 | name = "privnet" 15 | zone_slug = "lpg1" 16 | mtu = "9000" 17 | auto_create_ipv4_subnet = "false" 18 | } 19 | 20 | # Create a new subnet 21 | resource "cloudscale_subnet" "privnet-subnet" { 22 | cidr = "10.11.12.0/24" 23 | network_uuid = cloudscale_network.privnet.id 24 | gateway_address = "10.11.12.10" 25 | dns_servers = ["1.2.3.4", "5.6.7.8", "9.10.11.12"] 26 | } 27 | 28 | # Create a server with fixed IP address 29 | resource "cloudscale_server" "fixed" { 30 | name = "fix" 31 | zone_slug = "lpg1" 32 | flavor_slug = "flex-4-1" 33 | image_slug = "debian-11" 34 | interfaces { 35 | type = "public" 36 | } 37 | interfaces { 38 | type = "private" 39 | addresses { 40 | subnet_uuid = "cloudscale_subnet.privnet-subnet.id" 41 | address = "10.11.12.13" 42 | } 43 | } 44 | volume_size_gb = 10 45 | ssh_keys = ["ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY=", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFEepRNW5hDct4AdJ8oYsb4lNP5E9XY5fnz3ZvgNCEv7m48+bhUjJXUPuamWix3zigp2lgJHC6SChI/okJ41GUY="] 46 | } 47 | ``` 48 | 49 | ## Argument Reference 50 | 51 | The following arguments are supported when creating/changing subnets: 52 | 53 | * `cidr` - (Required) The address range in CIDR notation. Must be at least /24. 54 | * `network_uuid` - (Required) The network of the subnet. 55 | * `gateway_address` - (Optional) The gateway address of the subnet. 56 | * `dns_servers` - (Optional) A list of DNS resolver IP addresses, that act as DNS servers. If not defined, default DNS servers are used. Do not explicitly set to an empty list (`dns_servers = []`), use `disable_dns_servers` instead. 57 | * `disable_dns_servers` - (Optional) If set to true, no DNS servers are set. Can not be used together with `dns_servers`. 58 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 59 | ```hcl 60 | tags = { 61 | foo = "bar" 62 | } 63 | ``` 64 | Tags are always strings (both keys and values). 65 | 66 | 67 | ## Attributes Reference 68 | 69 | In addition to the arguments listed above, the following computed attributes are exported: 70 | 71 | * `href` - The cloudscale.ch API URL of the current subnet. 72 | * `network_name` - The network name of the subnet. 73 | * `network_href` - The cloudscale.ch API URL of the subnet's network. 74 | 75 | 76 | ## Import 77 | 78 | Subnets can be imported using the subnet's UUID: 79 | 80 | ``` 81 | terraform import cloudscale_subnet.subnet 48151623-42aa-aaaa-bbbb-caffeeeeeeee 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/resources/volume.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "cloudscale.ch: cloudscale_volume" 3 | --- 4 | 5 | # cloudscale\_volume 6 | 7 | Provides a cloudscale.ch volume (block storage) resource. This can be used to create, modify, import, and delete volumes. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | # Create a new Server 13 | resource "cloudscale_server" "web-worker01" { 14 | name = "web-worker01" 15 | flavor_slug = "flex-8-4" 16 | image_slug = "debian-11" 17 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 18 | } 19 | 20 | # Add a volume to web-worker01 21 | resource "cloudscale_volume" "web-worker01-volume" { 22 | name = "web-worker-data" 23 | size_gb = 100 24 | type = "ssd" 25 | server_uuids = [cloudscale_server.web-worker01.id] 26 | } 27 | ``` 28 | 29 | ## Argument Reference 30 | 31 | The following arguments are supported when creating/changing volumes: 32 | 33 | * `name` - (Required) Name of the new volume. 34 | * `size_gb` - (Required) The volume size in GB. Valid values are multiples of 1 for type "ssd" and multiples of 100 for type "bulk". 35 | * `zone_slug` - (Optional) The slug of the zone in which the new volume will be created. Options include `lpg1` and `rma1`. 36 | * `type` - (Optional) For SSD/NVMe volumes specify "ssd" (default) or use "bulk" for our HDD cluster with NVMe caching. This is the only attribute that cannot be altered. 37 | * `server_uuids` - (Optional) A list of server UUIDs. Default to an empty list. Currently a volume can only be attached to one server UUID. 38 | * `tags` - (Optional) Tags allow you to assign custom metadata to resources: 39 | ```hcl 40 | tags = { 41 | foo = "bar" 42 | } 43 | ``` 44 | Tags are always strings (both keys and values). 45 | 46 | ## Attributes Reference 47 | 48 | In addition to the arguments listed above, the following computed attributes are exported: 49 | 50 | * `href` - The cloudscale.ch API URL of the current resource. 51 | 52 | 53 | ## Import 54 | 55 | Volumes can be imported using the volume's UUID: 56 | 57 | ``` 58 | terraform import cloudscale_volume.volume 48151623-42aa-aaaa-bbbb-caffeeeeeeee 59 | ``` 60 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform* 2 | terraform.tfstate 3 | terraform.tfstate.backup 4 | -------------------------------------------------------------------------------- /examples/main.tf: -------------------------------------------------------------------------------- 1 | # Set the variable value in a *.tfvars file or use 2 | # the -var="cloudscale_api_token=..." CLI option. 3 | # 4 | # You can omit both the variable and provider if you 5 | # choose to set a shell environment variable called 6 | # `CLOUDSCALE_API_TOKEN` instead. 7 | 8 | variable "cloudscale_api_token" {} 9 | 10 | terraform { 11 | required_providers { 12 | cloudscale = { 13 | source = "cloudscale-ch/cloudscale" 14 | // version = "~> x.y.z" 15 | } 16 | } 17 | } 18 | 19 | provider "cloudscale" { 20 | token = "${var.cloudscale_api_token}" 21 | } 22 | 23 | # Create a new Server 24 | resource "cloudscale_server" "web-worker01" { 25 | name = "web-worker01" 26 | flavor_slug = "flex-8-4" 27 | image_slug = "debian-11" 28 | volume_size_gb = 50 29 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL2jzgla23DfRVLQr3KT20QQYovqCCN3clHrjm2ZuQFW user@example.com"] 30 | } 31 | 32 | # Add a Floating IPv4 address to web-worker01 33 | resource "cloudscale_floating_ip" "web-worker01-vip" { 34 | server = "${cloudscale_server.web-worker01.id}" 35 | ip_version = 4 36 | reverse_ptr = "vip.web-worker01.example.com" 37 | } 38 | 39 | # Add a Floating IPv6 network to web-worker01 40 | resource "cloudscale_floating_ip" "web-worker01-net" { 41 | server = "${cloudscale_server.web-worker01.id}" 42 | ip_version = 6 43 | prefix_length = 56 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/terraform-providers/terraform-provider-cloudscale 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/cloudscale-ch/cloudscale-go-sdk/v5 v5.1.0 9 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 10 | golang.org/x/oauth2 v0.28.0 11 | ) 12 | 13 | require ( 14 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 15 | github.com/agext/levenshtein v1.2.2 // indirect 16 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 17 | github.com/cenkalti/backoff/v5 v5.0.2 // indirect 18 | github.com/cloudflare/circl v1.3.7 // indirect 19 | github.com/fatih/color v1.16.0 // indirect 20 | github.com/golang/protobuf v1.5.4 // indirect 21 | github.com/google/go-cmp v0.6.0 // indirect 22 | github.com/hashicorp/errwrap v1.0.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-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect 26 | github.com/hashicorp/go-hclog v1.6.3 // indirect 27 | github.com/hashicorp/go-multierror v1.1.1 // indirect 28 | github.com/hashicorp/go-plugin v1.6.2 // indirect 29 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 30 | github.com/hashicorp/go-uuid v1.0.3 // indirect 31 | github.com/hashicorp/go-version v1.7.0 // indirect 32 | github.com/hashicorp/hc-install v0.9.1 // indirect 33 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 34 | github.com/hashicorp/logutils v1.0.0 // indirect 35 | github.com/hashicorp/terraform-exec v0.22.0 // indirect 36 | github.com/hashicorp/terraform-json v0.24.0 // indirect 37 | github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect 38 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 39 | github.com/hashicorp/terraform-registry-address v0.2.4 // indirect 40 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 41 | github.com/hashicorp/yamux v0.1.1 // indirect 42 | github.com/mattn/go-colorable v0.1.13 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/mitchellh/copystructure v1.2.0 // indirect 45 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 46 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 47 | github.com/mitchellh/mapstructure v1.5.0 // indirect 48 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 49 | github.com/oklog/run v1.0.0 // indirect 50 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 51 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 52 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 53 | github.com/zclconf/go-cty v1.16.2 // indirect 54 | golang.org/x/crypto v0.36.0 // indirect 55 | golang.org/x/mod v0.22.0 // indirect 56 | golang.org/x/net v0.37.0 // indirect 57 | golang.org/x/sync v0.12.0 // indirect 58 | golang.org/x/sys v0.31.0 // indirect 59 | golang.org/x/text v0.23.0 // indirect 60 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 61 | google.golang.org/appengine v1.6.8 // indirect 62 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 63 | google.golang.org/grpc v1.69.4 // indirect 64 | google.golang.org/protobuf v1.36.3 // indirect 65 | ) 66 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 5 | "github.com/terraform-providers/terraform-provider-cloudscale/cloudscale" 6 | ) 7 | 8 | func main() { 9 | plugin.Serve(&plugin.ServeOpts{ 10 | ProviderFunc: cloudscale.Provider, 11 | }) 12 | } 13 | --------------------------------------------------------------------------------