feat: add model serving resource
* add model serving * add right provider config * rename model_serving to modelserving * add model serving custom endpoint everywhere * rename file * add default region, docs for model serving * add right order of wait handler * rotate after to token * fixes * add initial doc files * address code comments * refactor region description * remove warning for not found resources * add service enablement * address code comments * address code comments * fix datasource * fix acc test * review changes * review changes * review changes * review changes * review changes * review changes * review changes * review changes * review changes * embed markdown description * go tidy --------- Co-authored-by: Mauritz Uphoff <mauritz.uphoff@me.com> Co-authored-by: Mauritz Uphoff <39736813+h3adex@users.noreply.github.com>
This commit is contained in:
parent
68859a3fad
commit
435de4c9eb
15 changed files with 1436 additions and 45 deletions
|
|
@ -162,6 +162,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
|
||||||
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service
|
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service
|
||||||
- `logme_custom_endpoint` (String) Custom endpoint for the LogMe service
|
- `logme_custom_endpoint` (String) Custom endpoint for the LogMe service
|
||||||
- `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service
|
- `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service
|
||||||
|
- `modelserving_custom_endpoint` (String) Custom endpoint for the Model Serving service
|
||||||
- `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service
|
- `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service
|
||||||
- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service
|
- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service
|
||||||
- `observability_custom_endpoint` (String) Custom endpoint for the Observability service
|
- `observability_custom_endpoint` (String) Custom endpoint for the Observability service
|
||||||
|
|
|
||||||
71
docs/resources/modelserving_token.md
Normal file
71
docs/resources/modelserving_token.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||||
|
page_title: "stackit_modelserving_token Resource - stackit"
|
||||||
|
subcategory: ""
|
||||||
|
description: |-
|
||||||
|
Model Serving Auth Token Resource schema.
|
||||||
|
Example Usage
|
||||||
|
Automatically rotate model serving token
|
||||||
|
|
||||||
|
resource "time_rotating" "rotate" {
|
||||||
|
rotation_days = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "stackit_modelserving_token" "example" {
|
||||||
|
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
name = "Example token"
|
||||||
|
|
||||||
|
rotate_when_changed = {
|
||||||
|
rotation = time_rotating.rotate.id
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
# stackit_modelserving_token (Resource)
|
||||||
|
|
||||||
|
Model Serving Auth Token Resource schema.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
### Automatically rotate model serving token
|
||||||
|
```terraform
|
||||||
|
resource "time_rotating" "rotate" {
|
||||||
|
rotation_days = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "stackit_modelserving_token" "example" {
|
||||||
|
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
name = "Example token"
|
||||||
|
|
||||||
|
rotate_when_changed = {
|
||||||
|
rotation = time_rotating.rotate.id
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) Name of the model serving auth token.
|
||||||
|
- `project_id` (String) STACKIT project ID to which the model serving auth token is associated.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `description` (String) The description of the model serving auth token.
|
||||||
|
- `region` (String) Region to which the model serving auth token is associated. If not defined, the provider region is used
|
||||||
|
- `rotate_when_changed` (Map of String) A map of arbitrary key/value pairs that will force recreation of the token when they change, enabling token rotation based on external conditions such as a rotating timestamp. Changing this forces a new resource to be created.
|
||||||
|
- `ttl_duration` (String) The TTL duration of the model serving auth token. E.g. 5h30m40s,5h,5h30m,30m,30s
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`token_id`".
|
||||||
|
- `state` (String) State of the model serving auth token.
|
||||||
|
- `token` (String, Sensitive) Content of the model serving auth token.
|
||||||
|
- `token_id` (String) The model serving auth token ID.
|
||||||
|
- `valid_until` (String) The time until the model serving auth token is valid.
|
||||||
21
go.mod
21
go.mod
|
|
@ -18,6 +18,7 @@ require (
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.0.0
|
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.0.0
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/logme v0.21.0
|
github.com/stackitcloud/stackit-sdk-go/services/logme v0.21.0
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0
|
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0
|
||||||
|
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.2.1
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0
|
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.1.0
|
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.1.0
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/observability v0.3.0
|
github.com/stackitcloud/stackit-sdk-go/services/observability v0.3.0
|
||||||
|
|
@ -40,7 +41,7 @@ require (
|
||||||
require github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
require github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
|
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||||
github.com/agext/levenshtein v1.2.2 // indirect
|
github.com/agext/levenshtein v1.2.2 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
|
|
@ -56,17 +57,17 @@ require (
|
||||||
github.com/hashicorp/go-plugin v1.6.2 // indirect
|
github.com/hashicorp/go-plugin v1.6.2 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
github.com/hashicorp/hc-install v0.9.0 // indirect
|
github.com/hashicorp/hc-install v0.9.1 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
||||||
github.com/hashicorp/logutils v1.0.0 // indirect
|
github.com/hashicorp/logutils v1.0.0 // indirect
|
||||||
github.com/hashicorp/terraform-exec v0.21.0 // indirect
|
github.com/hashicorp/terraform-exec v0.22.0 // indirect
|
||||||
github.com/hashicorp/terraform-json v0.23.0 // indirect
|
github.com/hashicorp/terraform-json v0.24.0 // indirect
|
||||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect
|
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect
|
||||||
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
|
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
|
||||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||||
|
|
@ -74,19 +75,19 @@ require (
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
github.com/oklog/run v1.1.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.6.0
|
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.6.0
|
||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.15.0 // indirect
|
github.com/zclconf/go-cty v1.16.2 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
google.golang.org/grpc v1.69.4 // indirect
|
google.golang.org/grpc v1.69.4 // indirect
|
||||||
|
|
|
||||||
60
go.sum
60
go.sum
|
|
@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
|
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
|
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
|
||||||
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||||
|
|
@ -14,8 +14,8 @@ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -26,10 +26,10 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
|
||||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
|
@ -76,16 +76,16 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE=
|
github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ=
|
||||||
github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg=
|
github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
|
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
|
github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64=
|
||||||
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
|
github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ=
|
||||||
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
|
github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q=
|
||||||
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
|
github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
|
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
|
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
|
||||||
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
|
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
|
||||||
|
|
@ -120,11 +120,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
|
@ -145,12 +144,12 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||||
github.com/stackitcloud/stackit-sdk-go/core v0.16.2 h1:F8A4P/LLlQSbz0S0+G3m8rb3BUOK6EcR/CKx5UQY5jQ=
|
github.com/stackitcloud/stackit-sdk-go/core v0.16.2 h1:F8A4P/LLlQSbz0S0+G3m8rb3BUOK6EcR/CKx5UQY5jQ=
|
||||||
github.com/stackitcloud/stackit-sdk-go/core v0.16.2/go.mod h1:8KIw3czdNJ9sdil9QQimxjR6vHjeINFrRv0iZ67wfn0=
|
github.com/stackitcloud/stackit-sdk-go/core v0.16.2/go.mod h1:8KIw3czdNJ9sdil9QQimxjR6vHjeINFrRv0iZ67wfn0=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 h1:JVEx/ouHB6PlwGzQa3ywyDym1HTWo3WgrxAyXprCnuM=
|
github.com/stackitcloud/stackit-sdk-go/services/argus v0.11.0 h1:JVEx/ouHB6PlwGzQa3ywyDym1HTWo3WgrxAyXprCnuM=
|
||||||
|
|
@ -167,6 +166,8 @@ github.com/stackitcloud/stackit-sdk-go/services/logme v0.21.0 h1:P7bxaVzkZPGMWIt
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/logme v0.21.0/go.mod h1:os4Kp2+jkMUJ2dZtgU9A91N3EJSw3MMh2slxgK1609g=
|
github.com/stackitcloud/stackit-sdk-go/services/logme v0.21.0/go.mod h1:os4Kp2+jkMUJ2dZtgU9A91N3EJSw3MMh2slxgK1609g=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0 h1:ks1i+cfD/YPRss//4aq6uvxbLvUwb5QvcUrOPeboLFY=
|
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0 h1:ks1i+cfD/YPRss//4aq6uvxbLvUwb5QvcUrOPeboLFY=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0/go.mod h1:kGAT87SO5Wkv/CSZevMZcPml3V38G6tnT1Wvdkdmkv4=
|
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.21.0/go.mod h1:kGAT87SO5Wkv/CSZevMZcPml3V38G6tnT1Wvdkdmkv4=
|
||||||
|
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.2.1 h1:6G6AZWZMWYRw4l+Gvg0KG3VcYnl/phOA8bpJ+2Yz/Rw=
|
||||||
|
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.2.1/go.mod h1:i3wU8mtK1sdu8AaVDFc39nfKyjuRAd/0oCnuGl1wMfs=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0 h1:mXVFa5/5uvOibPAUU0HTM7uf7H95IbnYnIzNvR5gB00=
|
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0 h1:mXVFa5/5uvOibPAUU0HTM7uf7H95IbnYnIzNvR5gB00=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0/go.mod h1:uuTdgDo4Ju2W0eMfHc3a5n9SXNKJPdxuq15e3AFss6Q=
|
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.18.0/go.mod h1:uuTdgDo4Ju2W0eMfHc3a5n9SXNKJPdxuq15e3AFss6Q=
|
||||||
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.1.0 h1:kWfmDQeTMijx0ySPiPfL4EU1TL6lcpkRVrzXenSaX6w=
|
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.1.0 h1:kWfmDQeTMijx0ySPiPfL4EU1TL6lcpkRVrzXenSaX6w=
|
||||||
|
|
@ -213,8 +214,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
|
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
|
||||||
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||||
|
|
@ -243,8 +244,8 @@ golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -255,7 +256,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|
@ -268,13 +268,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ type ProviderData struct {
|
||||||
LogMeCustomEndpoint string
|
LogMeCustomEndpoint string
|
||||||
MariaDBCustomEndpoint string
|
MariaDBCustomEndpoint string
|
||||||
MongoDBFlexCustomEndpoint string
|
MongoDBFlexCustomEndpoint string
|
||||||
|
ModelServingCustomEndpoint string
|
||||||
ObjectStorageCustomEndpoint string
|
ObjectStorageCustomEndpoint string
|
||||||
ObservabilityCustomEndpoint string
|
ObservabilityCustomEndpoint string
|
||||||
OpenSearchCustomEndpoint string
|
OpenSearchCustomEndpoint string
|
||||||
|
|
|
||||||
158
stackit/internal/services/modelserving/modelserving_acc_test.go
Normal file
158
stackit/internal/services/modelserving/modelserving_acc_test.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
package modelserving_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/modelserving"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/modelserving/wait"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token resource data
|
||||||
|
var tokenResource = map[string]string{
|
||||||
|
"project_id": testutil.ProjectId,
|
||||||
|
"name": "token01",
|
||||||
|
"description": "my description",
|
||||||
|
"description_updated": "my description updated",
|
||||||
|
"region": testutil.Region,
|
||||||
|
"ttl_duration": "1h",
|
||||||
|
}
|
||||||
|
|
||||||
|
func inputTokenConfig(name, description string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
%s
|
||||||
|
|
||||||
|
resource "stackit_modelserving_token" "token" {
|
||||||
|
project_id = "%s"
|
||||||
|
region = "%s"
|
||||||
|
name = "%s"
|
||||||
|
description = "%s"
|
||||||
|
ttl_duration = "%s"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
testutil.ModelServingProviderConfig(),
|
||||||
|
tokenResource["project_id"],
|
||||||
|
tokenResource["region"],
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
tokenResource["ttl_duration"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccModelServingTokenResource(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||||
|
CheckDestroy: testAccCheckModelServingTokenDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// Creation
|
||||||
|
{
|
||||||
|
Config: inputTokenConfig(
|
||||||
|
tokenResource["name"],
|
||||||
|
tokenResource["description"],
|
||||||
|
),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "project_id", tokenResource["project_id"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "region", tokenResource["region"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "name", tokenResource["name"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "description", tokenResource["description"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "ttl_duration", tokenResource["ttl_duration"]),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "token_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "state"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "valid_until"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "token"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Update
|
||||||
|
{
|
||||||
|
Config: inputTokenConfig(
|
||||||
|
tokenResource["name"],
|
||||||
|
tokenResource["description_updated"],
|
||||||
|
),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "project_id", tokenResource["project_id"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "region", tokenResource["region"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "name", tokenResource["name"]),
|
||||||
|
resource.TestCheckResourceAttr("stackit_modelserving_token.token", "description", tokenResource["description_updated"]),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "token_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "state"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_modelserving_token.token", "valid_until"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Deletion is done by the framework implicitly
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckModelServingTokenDestroy(s *terraform.State) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
var client *modelserving.APIClient
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if testutil.ModelServingCustomEndpoint == "" {
|
||||||
|
client, err = modelserving.NewAPIClient()
|
||||||
|
} else {
|
||||||
|
client, err = modelserving.NewAPIClient(
|
||||||
|
config.WithEndpoint(testutil.ModelServingCustomEndpoint),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokensToDestroy := []string{}
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "stackit_modelserving_token" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token terraform ID: "[project_id],[region],[token_id]"
|
||||||
|
idParts := strings.Split(rs.Primary.ID, core.Separator)
|
||||||
|
if len(idParts) != 3 {
|
||||||
|
return fmt.Errorf("invalid ID: %s", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
if idParts[2] != "" {
|
||||||
|
tokensToDestroy = append(tokensToDestroy, idParts[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tokensToDestroy) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tokensResp, err := client.ListTokens(ctx, testutil.Region, testutil.ProjectId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting tokensResp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokensResp.Tokens == nil || (tokensResp.Tokens != nil && len(*tokensResp.Tokens) == 0) {
|
||||||
|
fmt.Print("No tokens found for project \n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
items := *tokensResp.Tokens
|
||||||
|
for i := range items {
|
||||||
|
if items[i].Name == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if utils.Contains(tokensToDestroy, *items[i].Name) {
|
||||||
|
_, err := client.DeleteToken(ctx, testutil.Region, testutil.ProjectId, *items[i].Id).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("destroying token %s during CheckDestroy: %w", *items[i].Name, err)
|
||||||
|
}
|
||||||
|
_, err = wait.DeleteModelServingWaitHandler(ctx, client, testutil.Region, testutil.ProjectId, *items[i].Id).WaitWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("destroying token %s during CheckDestroy: waiting for deletion %w", *items[i].Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
20
stackit/internal/services/modelserving/token/description.md
Normal file
20
stackit/internal/services/modelserving/token/description.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
Model Serving Auth Token Resource schema.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
### Automatically rotate model serving token
|
||||||
|
```terraform
|
||||||
|
resource "time_rotating" "rotate" {
|
||||||
|
rotation_days = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "stackit_modelserving_token" "example" {
|
||||||
|
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
name = "Example token"
|
||||||
|
|
||||||
|
rotate_when_changed = {
|
||||||
|
rotation = time_rotating.rotate.id
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
676
stackit/internal/services/modelserving/token/resource.go
Normal file
676
stackit/internal/services/modelserving/token/resource.go
Normal file
|
|
@ -0,0 +1,676 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/modelserving"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/modelserving/wait"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/serviceenablement"
|
||||||
|
serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/wait"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the implementation satisfies the expected interfaces.
|
||||||
|
var (
|
||||||
|
_ resource.Resource = &tokenResource{}
|
||||||
|
_ resource.ResourceWithConfigure = &tokenResource{}
|
||||||
|
_ resource.ResourceWithModifyPlan = &tokenResource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
inactiveState = "inactive"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed description.md
|
||||||
|
var markdownDescription string
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Id types.String `tfsdk:"id"` // needed by TF
|
||||||
|
ProjectId types.String `tfsdk:"project_id"`
|
||||||
|
Region types.String `tfsdk:"region"`
|
||||||
|
TokenId types.String `tfsdk:"token_id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
Description types.String `tfsdk:"description"`
|
||||||
|
State types.String `tfsdk:"state"`
|
||||||
|
ValidUntil types.String `tfsdk:"valid_until"`
|
||||||
|
TTLDuration types.String `tfsdk:"ttl_duration"`
|
||||||
|
Token types.String `tfsdk:"token"`
|
||||||
|
// RotateWhenChanged is a map of arbitrary key/value pairs that will force
|
||||||
|
// recreation of the token when they change, enabling token rotation based on
|
||||||
|
// external conditions such as a rotating timestamp. Changing this forces a new
|
||||||
|
// resource to be created.
|
||||||
|
RotateWhenChanged types.Map `tfsdk:"rotate_when_changed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenResource is a helper function to simplify the provider implementation.
|
||||||
|
func NewTokenResource() resource.Resource {
|
||||||
|
return &tokenResource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenResource is the resource implementation.
|
||||||
|
type tokenResource struct {
|
||||||
|
client *modelserving.APIClient
|
||||||
|
providerData core.ProviderData
|
||||||
|
serviceEnablementClient *serviceenablement.APIClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns the resource type name.
|
||||||
|
func (r *tokenResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_modelserving_token"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure adds the provider configured client to the resource.
|
||||||
|
func (r *tokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
// Prevent panic if the provider has not been configured.
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
providerData, ok := req.ProviderData.(core.ProviderData)
|
||||||
|
if !ok {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiClient *modelserving.APIClient
|
||||||
|
var err error
|
||||||
|
if providerData.ModelServingCustomEndpoint != "" {
|
||||||
|
ctx = tflog.SetField(
|
||||||
|
ctx,
|
||||||
|
"modelserving_custom_endpoint",
|
||||||
|
providerData.ModelServingCustomEndpoint,
|
||||||
|
)
|
||||||
|
apiClient, err = modelserving.NewAPIClient(
|
||||||
|
config.WithCustomAuth(providerData.RoundTripper),
|
||||||
|
config.WithEndpoint(providerData.ModelServingCustomEndpoint),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiClient, err = modelserving.NewAPIClient(
|
||||||
|
config.WithCustomAuth(providerData.RoundTripper),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error configuring API client",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration",
|
||||||
|
err,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceEnablementClient *serviceenablement.APIClient
|
||||||
|
if providerData.ServiceEnablementCustomEndpoint != "" {
|
||||||
|
serviceEnablementClient, err = serviceenablement.NewAPIClient(
|
||||||
|
config.WithCustomAuth(providerData.RoundTripper),
|
||||||
|
config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
serviceEnablementClient, err = serviceenablement.NewAPIClient(
|
||||||
|
config.WithCustomAuth(providerData.RoundTripper),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error configuring service enablement client",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration",
|
||||||
|
err,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = apiClient
|
||||||
|
r.providerData = providerData
|
||||||
|
r.serviceEnablementClient = serviceEnablementClient
|
||||||
|
tflog.Info(ctx, "Model-Serving auth token client configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||||
|
// Use the modifier to set the effective region in the current plan.
|
||||||
|
func (r *tokenResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
var configModel Model
|
||||||
|
|
||||||
|
// skip initial empty configuration to avoid follow-up errors
|
||||||
|
if req.Config.Raw.IsNull() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var planModel Model
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.AdaptRegion(
|
||||||
|
ctx,
|
||||||
|
configModel.Region,
|
||||||
|
&planModel.Region,
|
||||||
|
r.providerData.GetRegion(),
|
||||||
|
resp,
|
||||||
|
)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema defines the schema for the resource.
|
||||||
|
func (r *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: markdownDescription,
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`token_id`\".",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"project_id": schema.StringAttribute{
|
||||||
|
Description: "STACKIT project ID to which the model serving auth token is associated.",
|
||||||
|
Required: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.UUID(),
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"region": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
// must be computed to allow for storing the override value from the provider
|
||||||
|
Computed: true,
|
||||||
|
Description: "Region to which the model serving auth token is associated. If not defined, the provider region is used",
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"token_id": schema.StringAttribute{
|
||||||
|
Description: "The model serving auth token ID.",
|
||||||
|
Computed: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.UUID(),
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ttl_duration": schema.StringAttribute{
|
||||||
|
Description: "The TTL duration of the model serving auth token. E.g. 5h30m40s,5h,5h30m,30m,30s",
|
||||||
|
Required: false,
|
||||||
|
Optional: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.ValidDurationString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"rotate_when_changed": schema.MapAttribute{
|
||||||
|
Description: "A map of arbitrary key/value pairs that will force " +
|
||||||
|
"recreation of the token when they change, enabling token rotation " +
|
||||||
|
"based on external conditions such as a rotating timestamp. Changing " +
|
||||||
|
"this forces a new resource to be created.",
|
||||||
|
Optional: true,
|
||||||
|
Required: false,
|
||||||
|
ElementType: types.StringType,
|
||||||
|
PlanModifiers: []planmodifier.Map{
|
||||||
|
mapplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": schema.StringAttribute{
|
||||||
|
Description: "The description of the model serving auth token.",
|
||||||
|
Required: false,
|
||||||
|
Optional: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthBetween(1, 2000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: "Name of the model serving auth token.",
|
||||||
|
Required: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthBetween(1, 200),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"state": schema.StringAttribute{
|
||||||
|
Description: "State of the model serving auth token.",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"token": schema.StringAttribute{
|
||||||
|
Description: "Content of the model serving auth token.",
|
||||||
|
Computed: true,
|
||||||
|
Sensitive: true,
|
||||||
|
},
|
||||||
|
"valid_until": schema.StringAttribute{
|
||||||
|
Description: "The time until the model serving auth token is valid.",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates the resource and sets the initial Terraform state.
|
||||||
|
func (r *tokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
// Retrieve values from plan
|
||||||
|
var model Model
|
||||||
|
diags := req.Plan.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId := model.ProjectId.ValueString()
|
||||||
|
|
||||||
|
var region string
|
||||||
|
if utils.IsUndefined(model.Region) {
|
||||||
|
region = r.providerData.GetRegion()
|
||||||
|
} else {
|
||||||
|
region = model.Region.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||||
|
ctx = tflog.SetField(ctx, "region", region)
|
||||||
|
|
||||||
|
// If model serving is not enabled, enable it
|
||||||
|
err := r.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelServingServiceId).
|
||||||
|
Execute()
|
||||||
|
if err != nil {
|
||||||
|
var oapiErr *oapierror.GenericOpenAPIError
|
||||||
|
if errors.As(err, &oapiErr) {
|
||||||
|
if oapiErr.StatusCode == http.StatusNotFound {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling model serving",
|
||||||
|
fmt.Sprintf("Service not available in region %s \n%v", region, err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error enabling model serving",
|
||||||
|
fmt.Sprintf("Error enabling model serving: %v", err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, r.serviceEnablementClient, region, projectId, utils.ModelServingServiceId).
|
||||||
|
WaitWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error enabling model serving",
|
||||||
|
fmt.Sprintf("Error enabling model serving: %v", err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate API request body from model
|
||||||
|
payload, err := toCreatePayload(&model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating model serving auth token", fmt.Sprintf("Creating API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new model serving auth token
|
||||||
|
createTokenResp, err := r.client.CreateToken(ctx, region, projectId).
|
||||||
|
CreateTokenPayload(*payload).
|
||||||
|
Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error creating model serving auth token",
|
||||||
|
fmt.Sprintf("Calling API: %v", err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
waitResp, err := wait.CreateModelServingWaitHandler(ctx, r.client, region, projectId, *createTokenResp.Token.Id).WaitWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating model serving auth token", fmt.Sprintf("Waiting for token to be active: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map response body to schema
|
||||||
|
err = mapCreateResponse(createTokenResp, waitResp, &model, region)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating model serving auth token", fmt.Sprintf("Processing API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set state to fully populated data
|
||||||
|
diags = resp.State.Set(ctx, model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Model-Serving auth token created")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read refreshes the Terraform state with the latest data.
|
||||||
|
func (r *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
var model Model
|
||||||
|
diags := req.State.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId := model.ProjectId.ValueString()
|
||||||
|
tokenId := model.TokenId.ValueString()
|
||||||
|
|
||||||
|
var region string
|
||||||
|
if utils.IsUndefined(model.Region) {
|
||||||
|
region = r.providerData.GetRegion()
|
||||||
|
} else {
|
||||||
|
region = model.Region.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||||
|
ctx = tflog.SetField(ctx, "token_id", tokenId)
|
||||||
|
ctx = tflog.SetField(ctx, "region", region)
|
||||||
|
|
||||||
|
getTokenResp, err := r.client.GetToken(ctx, region, projectId, tokenId).
|
||||||
|
Execute()
|
||||||
|
if err != nil {
|
||||||
|
var oapiErr *oapierror.GenericOpenAPIError
|
||||||
|
if errors.As(err, &oapiErr) {
|
||||||
|
if oapiErr.StatusCode == http.StatusNotFound {
|
||||||
|
// Remove the resource from the state so Terraform will recreate it
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading model serving auth token", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if getTokenResp != nil && getTokenResp.Token.State != nil &&
|
||||||
|
*getTokenResp.Token.State == inactiveState {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
core.LogAndAddWarning(ctx, &resp.Diagnostics, "Error reading model serving auth token", "Model serving auth token has expired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map response body to schema
|
||||||
|
err = mapGetResponse(getTokenResp, &model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading model serving auth token", fmt.Sprintf("Processing API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set refreshed state
|
||||||
|
diags = resp.State.Set(ctx, model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Model-Serving auth token read")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the resource and sets the updated Terraform state on success.
|
||||||
|
func (r *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
// Retrieve values from plan
|
||||||
|
var model Model
|
||||||
|
diags := req.Plan.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
var state Model
|
||||||
|
diags = req.State.Get(ctx, &state)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId := state.ProjectId.ValueString()
|
||||||
|
tokenId := state.TokenId.ValueString()
|
||||||
|
|
||||||
|
var region string
|
||||||
|
if utils.IsUndefined(model.Region) {
|
||||||
|
region = r.providerData.GetRegion()
|
||||||
|
} else {
|
||||||
|
region = model.Region.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||||
|
ctx = tflog.SetField(ctx, "token_id", tokenId)
|
||||||
|
ctx = tflog.SetField(ctx, "region", region)
|
||||||
|
|
||||||
|
// Generate API request body from model
|
||||||
|
payload, err := toUpdatePayload(&model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating model serving auth token", fmt.Sprintf("Creating API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update model serving auth token
|
||||||
|
updateTokenResp, err := r.client.PartialUpdateToken(ctx, region, projectId, tokenId).PartialUpdateTokenPayload(*payload).Execute()
|
||||||
|
if err != nil {
|
||||||
|
var oapiErr *oapierror.GenericOpenAPIError
|
||||||
|
if errors.As(err, &oapiErr) {
|
||||||
|
if oapiErr.StatusCode == http.StatusNotFound {
|
||||||
|
// Remove the resource from the state so Terraform will recreate it
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error updating model serving auth token",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Calling API: %v, tokenId: %s, region: %s, projectId: %s",
|
||||||
|
err,
|
||||||
|
tokenId,
|
||||||
|
region,
|
||||||
|
projectId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateTokenResp != nil && updateTokenResp.Token.State != nil &&
|
||||||
|
*updateTokenResp.Token.State == inactiveState {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
core.LogAndAddWarning(ctx, &resp.Diagnostics, "Error updating model serving auth token", "Model serving auth token has expired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
waitResp, err := wait.UpdateModelServingWaitHandler(ctx, r.client, region, projectId, tokenId).WaitWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating model serving auth token", fmt.Sprintf("Waiting for token to be updated: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since STACKIT is not saving the content of the token. We have to use it from the state.
|
||||||
|
model.Token = state.Token
|
||||||
|
err = mapGetResponse(waitResp, &model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating model serving auth token", fmt.Sprintf("Processing API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = resp.State.Set(ctx, model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Model-Serving auth token updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the resource and removes the Terraform state on success.
|
||||||
|
func (r *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
// Retrieve values from plan
|
||||||
|
var model Model
|
||||||
|
diags := req.State.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId := model.ProjectId.ValueString()
|
||||||
|
tokenId := model.TokenId.ValueString()
|
||||||
|
|
||||||
|
var region string
|
||||||
|
if utils.IsUndefined(model.Region) {
|
||||||
|
region = r.providerData.GetRegion()
|
||||||
|
} else {
|
||||||
|
region = model.Region.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||||
|
ctx = tflog.SetField(ctx, "token_id", tokenId)
|
||||||
|
ctx = tflog.SetField(ctx, "region", region)
|
||||||
|
|
||||||
|
// Delete existing model serving auth token. We will ignore the state 'deleting' for now.
|
||||||
|
_, err := r.client.DeleteToken(ctx, region, projectId, tokenId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
var oapiErr *oapierror.GenericOpenAPIError
|
||||||
|
if errors.As(err, &oapiErr) {
|
||||||
|
if oapiErr.StatusCode == http.StatusNotFound {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting model serving auth token", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = wait.DeleteModelServingWaitHandler(ctx, r.client, region, projectId, tokenId).
|
||||||
|
WaitWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting model serving auth token", fmt.Sprintf("Waiting for token to be deleted: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Model-Serving auth token deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapCreateResponse(tokenCreateResp *modelserving.CreateTokenResponse, waitResp *modelserving.GetTokenResponse, model *Model, region string) error {
|
||||||
|
if tokenCreateResp == nil || tokenCreateResp.Token == nil {
|
||||||
|
return fmt.Errorf("response input is nil")
|
||||||
|
}
|
||||||
|
if model == nil {
|
||||||
|
return fmt.Errorf("model input is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := tokenCreateResp.Token
|
||||||
|
|
||||||
|
if token.Id == nil {
|
||||||
|
return fmt.Errorf("token id not present")
|
||||||
|
}
|
||||||
|
|
||||||
|
validUntil := types.StringNull()
|
||||||
|
if token.ValidUntil != nil {
|
||||||
|
validUntil = types.StringValue(token.ValidUntil.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
if waitResp == nil || waitResp.Token == nil || waitResp.Token.State == nil {
|
||||||
|
return fmt.Errorf("response input is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
idParts := []string{model.ProjectId.ValueString(), region, *tokenCreateResp.Token.Id}
|
||||||
|
model.Id = types.StringValue(
|
||||||
|
strings.Join(idParts, core.Separator),
|
||||||
|
)
|
||||||
|
model.TokenId = types.StringPointerValue(token.Id)
|
||||||
|
model.Name = types.StringPointerValue(token.Name)
|
||||||
|
model.State = types.StringPointerValue(waitResp.Token.State)
|
||||||
|
model.ValidUntil = validUntil
|
||||||
|
model.Token = types.StringPointerValue(token.Content)
|
||||||
|
model.Description = types.StringPointerValue(token.Description)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapGetResponse(tokenGetResp *modelserving.GetTokenResponse, model *Model) error {
|
||||||
|
if tokenGetResp == nil {
|
||||||
|
return fmt.Errorf("response input is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenGetResp.Token == nil {
|
||||||
|
return fmt.Errorf("response input is nil")
|
||||||
|
}
|
||||||
|
if model == nil {
|
||||||
|
return fmt.Errorf("model input is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// theoretically, should never happen, but still catch null pointers
|
||||||
|
validUntil := types.StringNull()
|
||||||
|
if tokenGetResp.Token.ValidUntil != nil {
|
||||||
|
validUntil = types.StringValue(tokenGetResp.Token.ValidUntil.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
idParts := []string{model.ProjectId.ValueString(), model.Region.ValueString(), model.TokenId.ValueString()}
|
||||||
|
model.Id = types.StringValue(strings.Join(idParts, core.Separator))
|
||||||
|
model.TokenId = types.StringPointerValue(tokenGetResp.Token.Id)
|
||||||
|
model.Name = types.StringPointerValue(tokenGetResp.Token.Name)
|
||||||
|
model.State = types.StringPointerValue(tokenGetResp.Token.State)
|
||||||
|
model.ValidUntil = validUntil
|
||||||
|
model.Description = types.StringPointerValue(tokenGetResp.Token.Description)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCreatePayload(model *Model) (*modelserving.CreateTokenPayload, error) {
|
||||||
|
if model == nil {
|
||||||
|
return nil, fmt.Errorf("nil model")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &modelserving.CreateTokenPayload{
|
||||||
|
Name: conversion.StringValueToPointer(model.Name),
|
||||||
|
Description: conversion.StringValueToPointer(model.Description),
|
||||||
|
TtlDuration: conversion.StringValueToPointer(model.TTLDuration),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUpdatePayload(model *Model) (*modelserving.PartialUpdateTokenPayload, error) {
|
||||||
|
if model == nil {
|
||||||
|
return nil, fmt.Errorf("nil model")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &modelserving.PartialUpdateTokenPayload{
|
||||||
|
Name: conversion.StringValueToPointer(model.Name),
|
||||||
|
Description: conversion.StringValueToPointer(model.Description),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
341
stackit/internal/services/modelserving/token/resource_test.go
Normal file
341
stackit/internal/services/modelserving/token/resource_test.go
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/modelserving"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapGetTokenFields(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
state *Model
|
||||||
|
input *modelserving.GetTokenResponse
|
||||||
|
expected Model
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "should error when response is nil",
|
||||||
|
state: &Model{},
|
||||||
|
input: nil,
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should error when token is nil in response",
|
||||||
|
state: &Model{},
|
||||||
|
input: &modelserving.GetTokenResponse{Token: nil},
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should error when state is nil in response",
|
||||||
|
state: nil,
|
||||||
|
input: &modelserving.GetTokenResponse{
|
||||||
|
Token: &modelserving.Token{},
|
||||||
|
},
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should map fields correctly",
|
||||||
|
state: &Model{
|
||||||
|
Id: types.StringValue("pid,eu01,tid"),
|
||||||
|
ProjectId: types.StringValue("pid"),
|
||||||
|
TokenId: types.StringValue("tid"),
|
||||||
|
Region: types.StringValue("eu01"),
|
||||||
|
RotateWhenChanged: types.MapNull(types.StringType),
|
||||||
|
},
|
||||||
|
input: &modelserving.GetTokenResponse{
|
||||||
|
Token: &modelserving.Token{
|
||||||
|
Id: utils.Ptr("tid"),
|
||||||
|
ValidUntil: utils.Ptr(
|
||||||
|
time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
State: utils.Ptr("active"),
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
Description: utils.Ptr("desc"),
|
||||||
|
Region: utils.Ptr("eu01"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: Model{
|
||||||
|
Id: types.StringValue("pid,eu01,tid"),
|
||||||
|
ProjectId: types.StringValue("pid"),
|
||||||
|
Region: types.StringValue("eu01"),
|
||||||
|
TokenId: types.StringValue("tid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
Description: types.StringValue("desc"),
|
||||||
|
State: types.StringValue("active"),
|
||||||
|
ValidUntil: types.StringValue("2099-01-01T00:00:00Z"),
|
||||||
|
RotateWhenChanged: types.MapNull(types.StringType),
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := mapGetResponse(tt.input, tt.state)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(tt.state, &tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapCreateTokenFields(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
state *Model
|
||||||
|
inputCreateTokenResponse *modelserving.CreateTokenResponse
|
||||||
|
inputGetTokenResponse *modelserving.GetTokenResponse
|
||||||
|
expected Model
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "should error when create token response is nil",
|
||||||
|
state: &Model{},
|
||||||
|
inputCreateTokenResponse: nil,
|
||||||
|
inputGetTokenResponse: nil,
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should error when token is nil in create token response",
|
||||||
|
state: &Model{},
|
||||||
|
inputCreateTokenResponse: &modelserving.CreateTokenResponse{
|
||||||
|
Token: nil,
|
||||||
|
},
|
||||||
|
inputGetTokenResponse: nil,
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should error when get token response is nil",
|
||||||
|
state: &Model{},
|
||||||
|
inputCreateTokenResponse: &modelserving.CreateTokenResponse{
|
||||||
|
Token: &modelserving.TokenCreated{},
|
||||||
|
},
|
||||||
|
inputGetTokenResponse: nil,
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should error when get token response is nil",
|
||||||
|
state: &Model{
|
||||||
|
Id: types.StringValue("pid,eu01,tid"),
|
||||||
|
ProjectId: types.StringValue("pid"),
|
||||||
|
},
|
||||||
|
inputCreateTokenResponse: &modelserving.CreateTokenResponse{
|
||||||
|
Token: &modelserving.TokenCreated{
|
||||||
|
Id: utils.Ptr("tid"),
|
||||||
|
ValidUntil: utils.Ptr(
|
||||||
|
time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
State: utils.Ptr("active"),
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
Description: utils.Ptr("desc"),
|
||||||
|
Region: utils.Ptr("eu01"),
|
||||||
|
Content: utils.Ptr("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputGetTokenResponse: nil,
|
||||||
|
expected: Model{},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should map fields correctly",
|
||||||
|
state: &Model{
|
||||||
|
Id: types.StringValue("pid,eu01,tid"),
|
||||||
|
ProjectId: types.StringValue("pid"),
|
||||||
|
Region: types.StringValue("eu01"),
|
||||||
|
RotateWhenChanged: types.MapNull(types.StringType),
|
||||||
|
},
|
||||||
|
inputCreateTokenResponse: &modelserving.CreateTokenResponse{
|
||||||
|
Token: &modelserving.TokenCreated{
|
||||||
|
Id: utils.Ptr("tid"),
|
||||||
|
ValidUntil: utils.Ptr(
|
||||||
|
time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
),
|
||||||
|
State: utils.Ptr("active"),
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
Description: utils.Ptr("desc"),
|
||||||
|
Region: utils.Ptr("eu01"),
|
||||||
|
Content: utils.Ptr("content"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputGetTokenResponse: &modelserving.GetTokenResponse{
|
||||||
|
Token: &modelserving.Token{
|
||||||
|
State: utils.Ptr("active"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: Model{
|
||||||
|
Id: types.StringValue("pid,eu01,tid"),
|
||||||
|
ProjectId: types.StringValue("pid"),
|
||||||
|
Region: types.StringValue("eu01"),
|
||||||
|
TokenId: types.StringValue("tid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
Description: types.StringValue("desc"),
|
||||||
|
State: types.StringValue("active"),
|
||||||
|
ValidUntil: types.StringValue("2099-01-01T00:00:00Z"),
|
||||||
|
Token: types.StringValue("content"),
|
||||||
|
RotateWhenChanged: types.MapNull(types.StringType),
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := mapCreateResponse(
|
||||||
|
tt.inputCreateTokenResponse,
|
||||||
|
tt.inputGetTokenResponse,
|
||||||
|
tt.state,
|
||||||
|
"eu01",
|
||||||
|
)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(tt.state, &tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToCreatePayload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
input *Model
|
||||||
|
expected *modelserving.CreateTokenPayload
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "should error on nil input",
|
||||||
|
input: nil,
|
||||||
|
expected: nil,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should convert correctly",
|
||||||
|
input: &Model{
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
Description: types.StringValue("desc"),
|
||||||
|
TTLDuration: types.StringValue("1h"),
|
||||||
|
},
|
||||||
|
expected: &modelserving.CreateTokenPayload{
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
Description: utils.Ptr("desc"),
|
||||||
|
TtlDuration: utils.Ptr("1h"),
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
output, err := toCreatePayload(tt.input)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(output, tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToUpdatePayload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
input *Model
|
||||||
|
expected *modelserving.PartialUpdateTokenPayload
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "should error on nil input",
|
||||||
|
input: nil,
|
||||||
|
expected: nil,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should convert correctly",
|
||||||
|
input: &Model{
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
Description: types.StringValue("desc"),
|
||||||
|
},
|
||||||
|
expected: &modelserving.PartialUpdateTokenPayload{
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
Description: utils.Ptr("desc"),
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
output, err := toUpdatePayload(tt.input)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(output, tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,6 +56,7 @@ var (
|
||||||
LoadBalancerCustomEndpoint = os.Getenv("TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT")
|
LoadBalancerCustomEndpoint = os.Getenv("TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT")
|
||||||
LogMeCustomEndpoint = os.Getenv("TF_ACC_LOGME_CUSTOM_ENDPOINT")
|
LogMeCustomEndpoint = os.Getenv("TF_ACC_LOGME_CUSTOM_ENDPOINT")
|
||||||
MariaDBCustomEndpoint = os.Getenv("TF_ACC_MARIADB_CUSTOM_ENDPOINT")
|
MariaDBCustomEndpoint = os.Getenv("TF_ACC_MARIADB_CUSTOM_ENDPOINT")
|
||||||
|
ModelServingCustomEndpoint = os.Getenv("TF_ACC_MODELSERVING_CUSTOM_ENDPOINT")
|
||||||
AuthorizationCustomEndpoint = os.Getenv("TF_ACC_authorization_custom_endpoint")
|
AuthorizationCustomEndpoint = os.Getenv("TF_ACC_authorization_custom_endpoint")
|
||||||
MongoDBFlexCustomEndpoint = os.Getenv("TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT")
|
MongoDBFlexCustomEndpoint = os.Getenv("TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT")
|
||||||
OpenSearchCustomEndpoint = os.Getenv("TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT")
|
OpenSearchCustomEndpoint = os.Getenv("TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT")
|
||||||
|
|
@ -178,6 +179,22 @@ func MariaDBProviderConfig() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ModelServingProviderConfig() string {
|
||||||
|
if ModelServingCustomEndpoint == "" {
|
||||||
|
return `
|
||||||
|
provider "stackit" {
|
||||||
|
region = "eu01"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "stackit" {
|
||||||
|
modelserving_custom_endpoint = "%s"
|
||||||
|
}`,
|
||||||
|
ModelServingCustomEndpoint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func MongoDBFlexProviderConfig() string {
|
func MongoDBFlexProviderConfig() string {
|
||||||
if MongoDBFlexCustomEndpoint == "" {
|
if MongoDBFlexCustomEndpoint == "" {
|
||||||
return `
|
return `
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func AdaptRegion(ctx context.Context, configRegion types.String, planRegion *typ
|
||||||
|
|
||||||
// check if the currently configured region corresponds to the planned region
|
// check if the currently configured region corresponds to the planned region
|
||||||
// on mismatch override the planned region with the intended region
|
// on mismatch override the planned region with the intended region
|
||||||
// and force a replace of the resource
|
// and force a replacement of the resource
|
||||||
p := path.Root("region")
|
p := path.Root("region")
|
||||||
if !intendedRegion.Equal(*planRegion) {
|
if !intendedRegion.Equal(*planRegion) {
|
||||||
resp.RequiresReplace.Append(p)
|
resp.RequiresReplace.Append(p)
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SKEServiceId = "cloud.stackit.ske"
|
SKEServiceId = "cloud.stackit.ske"
|
||||||
|
ModelServingServiceId = "cloud.stackit.model-serving"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -72,7 +72,7 @@ func ListValuetoStringSlice(list basetypes.ListValue) ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove leading 0s from backup schedule numbers (e.g. "00 00 * * *" becomes "0 0 * * *")
|
// SimplifyBackupSchedule removes leading 0s from backup schedule numbers (e.g. "00 00 * * *" becomes "0 0 * * *")
|
||||||
// Needed as the API does it internally and would otherwise cause inconsistent result in Terraform
|
// Needed as the API does it internally and would otherwise cause inconsistent result in Terraform
|
||||||
func SimplifyBackupSchedule(schedule string) string {
|
func SimplifyBackupSchedule(schedule string) string {
|
||||||
regex := regexp.MustCompile(`0+\d+`) // Matches series of one or more zeros followed by a series of one or more digits
|
regex := regexp.MustCompile(`0+\d+`) // Matches series of one or more zeros followed by a series of one or more digits
|
||||||
|
|
|
||||||
|
|
@ -295,3 +295,21 @@ func FileExists() *Validator {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidDurationString() *Validator {
|
||||||
|
description := "value must be in a valid duration string. Such as \"300ms\", \"-1.5h\" or \"2h45m\".\nValid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"."
|
||||||
|
|
||||||
|
return &Validator{
|
||||||
|
description: description,
|
||||||
|
validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||||
|
_, err := time.ParseDuration(req.ConfigValue.ValueString())
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||||
|
req.Path,
|
||||||
|
description,
|
||||||
|
req.ConfigValue.ValueString(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -769,3 +769,79 @@ func TestFileExists(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidTtlDuration(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
input string
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid duration with hours, minutes, and seconds",
|
||||||
|
"5h30m40s",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid duration with hours only",
|
||||||
|
"5h",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid duration with hours and minutes",
|
||||||
|
"5h30m",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid duration with minutes only",
|
||||||
|
"30m",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid duration with seconds only",
|
||||||
|
"30s",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid duration with incorrect unit",
|
||||||
|
"30o",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid duration without unit",
|
||||||
|
"30",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid duration with invalid letters",
|
||||||
|
"30e",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid duration with letters in middle",
|
||||||
|
"1h30x",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty string",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
r := validator.StringResponse{}
|
||||||
|
va := ValidDurationString()
|
||||||
|
va.ValidateString(context.Background(), validator.StringRequest{
|
||||||
|
ConfigValue: types.StringValue(tt.input),
|
||||||
|
}, &r)
|
||||||
|
|
||||||
|
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||||
|
t.Fatalf("Expected validation to fail for input: %v", tt.input)
|
||||||
|
}
|
||||||
|
if tt.isValid && r.Diagnostics.HasError() {
|
||||||
|
t.Fatalf("Expected validation to succeed for input: %v, but got errors: %v", tt.input, r.Diagnostics.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import (
|
||||||
logMeInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/instance"
|
logMeInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/instance"
|
||||||
mariaDBCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/credential"
|
mariaDBCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/credential"
|
||||||
mariaDBInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/instance"
|
mariaDBInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/instance"
|
||||||
|
modelServingToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelserving/token"
|
||||||
mongoDBFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/instance"
|
mongoDBFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/instance"
|
||||||
mongoDBFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/user"
|
mongoDBFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/user"
|
||||||
objectStorageBucket "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/bucket"
|
objectStorageBucket "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/bucket"
|
||||||
|
|
@ -118,6 +119,7 @@ type providerModel struct {
|
||||||
IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"`
|
IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"`
|
||||||
PostgresFlexCustomEndpoint types.String `tfsdk:"postgresflex_custom_endpoint"`
|
PostgresFlexCustomEndpoint types.String `tfsdk:"postgresflex_custom_endpoint"`
|
||||||
MongoDBFlexCustomEndpoint types.String `tfsdk:"mongodbflex_custom_endpoint"`
|
MongoDBFlexCustomEndpoint types.String `tfsdk:"mongodbflex_custom_endpoint"`
|
||||||
|
ModelServingCustomEndpoint types.String `tfsdk:"modelserving_custom_endpoint"`
|
||||||
LoadBalancerCustomEndpoint types.String `tfsdk:"loadbalancer_custom_endpoint"`
|
LoadBalancerCustomEndpoint types.String `tfsdk:"loadbalancer_custom_endpoint"`
|
||||||
LogMeCustomEndpoint types.String `tfsdk:"logme_custom_endpoint"`
|
LogMeCustomEndpoint types.String `tfsdk:"logme_custom_endpoint"`
|
||||||
RabbitMQCustomEndpoint types.String `tfsdk:"rabbitmq_custom_endpoint"`
|
RabbitMQCustomEndpoint types.String `tfsdk:"rabbitmq_custom_endpoint"`
|
||||||
|
|
@ -156,6 +158,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
|
||||||
"dns_custom_endpoint": "Custom endpoint for the DNS service",
|
"dns_custom_endpoint": "Custom endpoint for the DNS service",
|
||||||
"iaas_custom_endpoint": "Custom endpoint for the IaaS service",
|
"iaas_custom_endpoint": "Custom endpoint for the IaaS service",
|
||||||
"mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service",
|
"mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service",
|
||||||
|
"modelserving_custom_endpoint": "Custom endpoint for the Model Serving service",
|
||||||
"loadbalancer_custom_endpoint": "Custom endpoint for the Load Balancer service",
|
"loadbalancer_custom_endpoint": "Custom endpoint for the Load Balancer service",
|
||||||
"logme_custom_endpoint": "Custom endpoint for the LogMe service",
|
"logme_custom_endpoint": "Custom endpoint for the LogMe service",
|
||||||
"rabbitmq_custom_endpoint": "Custom endpoint for the RabbitMQ service",
|
"rabbitmq_custom_endpoint": "Custom endpoint for the RabbitMQ service",
|
||||||
|
|
@ -246,6 +249,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: descriptions["mariadb_custom_endpoint"],
|
Description: descriptions["mariadb_custom_endpoint"],
|
||||||
},
|
},
|
||||||
|
"modelserving_custom_endpoint": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: descriptions["modelserving_custom_endpoint"],
|
||||||
|
},
|
||||||
"authorization_custom_endpoint": schema.StringAttribute{
|
"authorization_custom_endpoint": schema.StringAttribute{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: descriptions["authorization_custom_endpoint"],
|
Description: descriptions["authorization_custom_endpoint"],
|
||||||
|
|
@ -376,6 +383,9 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
|
||||||
if !(providerConfig.PostgresFlexCustomEndpoint.IsUnknown() || providerConfig.PostgresFlexCustomEndpoint.IsNull()) {
|
if !(providerConfig.PostgresFlexCustomEndpoint.IsUnknown() || providerConfig.PostgresFlexCustomEndpoint.IsNull()) {
|
||||||
providerData.PostgresFlexCustomEndpoint = providerConfig.PostgresFlexCustomEndpoint.ValueString()
|
providerData.PostgresFlexCustomEndpoint = providerConfig.PostgresFlexCustomEndpoint.ValueString()
|
||||||
}
|
}
|
||||||
|
if !(providerConfig.ModelServingCustomEndpoint.IsUnknown() || providerConfig.ModelServingCustomEndpoint.IsNull()) {
|
||||||
|
providerData.ModelServingCustomEndpoint = providerConfig.ModelServingCustomEndpoint.ValueString()
|
||||||
|
}
|
||||||
if !(providerConfig.MongoDBFlexCustomEndpoint.IsUnknown() || providerConfig.MongoDBFlexCustomEndpoint.IsNull()) {
|
if !(providerConfig.MongoDBFlexCustomEndpoint.IsUnknown() || providerConfig.MongoDBFlexCustomEndpoint.IsNull()) {
|
||||||
providerData.MongoDBFlexCustomEndpoint = providerConfig.MongoDBFlexCustomEndpoint.ValueString()
|
providerData.MongoDBFlexCustomEndpoint = providerConfig.MongoDBFlexCustomEndpoint.ValueString()
|
||||||
}
|
}
|
||||||
|
|
@ -537,6 +547,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
|
||||||
logMeCredential.NewCredentialResource,
|
logMeCredential.NewCredentialResource,
|
||||||
mariaDBInstance.NewInstanceResource,
|
mariaDBInstance.NewInstanceResource,
|
||||||
mariaDBCredential.NewCredentialResource,
|
mariaDBCredential.NewCredentialResource,
|
||||||
|
modelServingToken.NewTokenResource,
|
||||||
mongoDBFlexInstance.NewInstanceResource,
|
mongoDBFlexInstance.NewInstanceResource,
|
||||||
mongoDBFlexUser.NewUserResource,
|
mongoDBFlexUser.NewUserResource,
|
||||||
objectStorageBucket.NewBucketResource,
|
objectStorageBucket.NewBucketResource,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue