Compare commits

...

13 commits

Author SHA1 Message Date
635a9abf20
fix: disable shell color in runnerstats (#80)
Signed-off-by: marcel.henselin <marcel.henselin@stackit.cloud>

## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #80
Co-authored-by: marcel.henselin <marcel.henselin@stackit.cloud>
Co-committed-by: marcel.henselin <marcel.henselin@stackit.cloud>
2026-02-27 10:25:10 +00:00
07458c5677
feat: add runner stats (#79)
Signed-off-by: marcel.henselin <marcel.henselin@stackit.cloud>

## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #79
Co-authored-by: marcel.henselin <marcel.henselin@stackit.cloud>
Co-committed-by: marcel.henselin <marcel.henselin@stackit.cloud>
2026-02-27 10:20:02 +00:00
eb13630d2f
feat: test STACKIT runner (#78)
Signed-off-by: marcel.henselin <marcel.henselin@stackit.cloud>

## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #78
Co-authored-by: marcel.henselin <marcel.henselin@stackit.cloud>
Co-committed-by: marcel.henselin <marcel.henselin@stackit.cloud>
2026-02-27 10:08:09 +00:00
4a2819787d
fix: linting (#77)
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #77
Co-authored-by: Andre Harms <andre.harms@stackit.cloud>
Co-committed-by: Andre Harms <andre.harms@stackit.cloud>
2026-02-19 08:54:34 +00:00
36eccc52c3
fix: null_ident (#76)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 9s
Publish / Publish provider (push) Successful in 34m58s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #76
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-17 17:18:40 +00:00
841e702b95
fix: encryption_fix (#75)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 12s
Publish / Publish provider (push) Successful in 33m28s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #75
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-17 09:42:46 +00:00
aba831cbdd
fix: some_fixes (#74)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 8s
Publish / Publish provider (push) Successful in 14m6s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #74
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 19:49:02 +00:00
89a24ce780
fix: try fix errors (#73)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 9s
Publish / Publish provider (push) Successful in 43m1s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #73
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 19:09:42 +00:00
f05e90c35a
fix: some more fix tests (#72)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 5s
Publish / Publish provider (push) Successful in 12m42s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #72
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 16:12:53 +00:00
7ee82366d7
fix: try fix errors (#71)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 11s
Publish / Publish provider (push) Successful in 35m24s
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #71
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 13:40:05 +00:00
d5644ec27f
chore: #64 add system hardening with retry logic for client (#68)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 5s
Publish / Publish provider (push) Successful in 12m49s
- implement RetryRoundTripper

Refs: #64

Reviewed-on: #68
Reviewed-by: Marcel_Henselin <marcel.henselin@stackit.cloud>
Co-authored-by: Andre Harms <andre.harms@stackit.cloud>
Co-committed-by: Andre Harms <andre.harms@stackit.cloud>
2026-02-16 09:35:21 +00:00
20e9b3ca4c
fix: #66 non generic api error handling (#67)
## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #67
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 09:20:36 +00:00
43223f5d1f
fix: #63 sort user roles to prevent state change (#65)
fix: include recent api changes
Reviewed-on: #65
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Co-committed-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
2026-02-16 09:04:16 +00:00
97 changed files with 5237 additions and 2660 deletions

View file

@ -22,6 +22,39 @@ env:
CODE_COVERAGE_ARTIFACT_NAME: "code-coverage"
jobs:
runner_test:
name: "Test STACKIT runner"
runs-on: stackit-docker
steps:
- name: Install needed tools
run: |
apt-get -y -qq update
apt-get -y -qq install jq python3 python3-pip python-is-python3 s3cmd git make wget
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- name: Install go tools
run: |
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework@latest
go install github.com/hashicorp/terraform-plugin-codegen-openapi/cmd/tfplugingen-openapi@latest
- name: Setup JAVA
uses: actions/setup-java@v5
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Checkout
uses: actions/checkout@v6
- name: Run build pkg directory
run: |
go run cmd/main.go build
publish_test:
name: "Test readiness for publishing provider"
needs: config

29
.github/workflows/runnerstats.yaml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Runner stats
on:
workflow_dispatch:
jobs:
stats-own:
name: "Get own runner stats"
runs-on: ubuntu-latest
steps:
- name: Install needed tools
run: |
apt-get -y -qq update
apt-get -y -qq install inxi
- name: Show stats
run: inxi -c 0
stats-stackit:
name: "Get STACKIT runner stats"
runs-on: stackit-docker
steps:
- name: Install needed tools
run: |
apt-get -y -qq update
apt-get -y -qq install inxi
- name: Show stats
run: inxi -c 0

2
.gitignore vendored
View file

@ -46,3 +46,5 @@ dist
pkg_gen
/release/
.env
**/.env

View file

@ -12,9 +12,10 @@ project-tools:
# LINT
lint-golangci-lint:
@echo "Linting with golangci-lint"
@$(SCRIPTS_BASE)/lint-golangci-lint.sh
@go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint run --fix --config golang-ci.yaml
lint-tf:
lint-tf:
@echo "Linting terraform files"
@terraform fmt -check -diff -recursive
@ -23,6 +24,7 @@ lint: lint-golangci-lint lint-tf
# DOCUMENTATION GENERATION
generate-docs:
@echo "Generating documentation with tfplugindocs"
@$(SCRIPTS_BASE)/tfplugindocs.sh
build:

View file

@ -60,7 +60,7 @@ func (b *Builder) Build() error {
if !b.PackagesOnly {
slog.Info(" ... Checking needed commands available")
err := checkCommands([]string{"tfplugingen-framework", "tfplugingen-openapi"})
err := checkCommands([]string{})
if err != nil {
return err
}
@ -111,7 +111,7 @@ func (b *Builder) Build() error {
}
slog.Info("Creating OAS dir")
err = os.MkdirAll(path.Join(genDir, "oas"), 0755)
err = os.MkdirAll(path.Join(genDir, "oas"), 0o755) //nolint:gosec // this dir is not sensitive, so we can use 0755
if err != nil {
return err
}
@ -158,7 +158,17 @@ func (b *Builder) Build() error {
if err = cmd.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
slog.Error("cmd.Wait", "code", exitErr.ExitCode(), "error", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"cmd.Wait",
"code",
exitErr.ExitCode(),
"error",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
@ -192,7 +202,11 @@ func (b *Builder) Build() error {
}
slog.Info("Rearranging package directories")
err = os.MkdirAll(path.Join(*root, "pkg_gen"), 0755) // noqa:gosec
//nolint:gosec // this dir is not sensitive, so we can use 0755
err = os.MkdirAll(
path.Join(*root, "pkg_gen"),
0o755,
)
if err != nil {
return err
}
@ -202,20 +216,21 @@ func (b *Builder) Build() error {
return err
}
for _, item := range items {
if item.IsDir() {
slog.Info(" -> package", "name", item.Name())
tgtDir := path.Join(*root, "pkg_gen", item.Name())
if fileExists(tgtDir) {
delErr := os.RemoveAll(tgtDir)
if delErr != nil {
return delErr
}
}
err = os.Rename(path.Join(srcDir, item.Name()), tgtDir)
if err != nil {
return err
if !item.IsDir() {
continue
}
slog.Info(" -> package", "name", item.Name())
tgtDir := path.Join(*root, "pkg_gen", item.Name())
if fileExists(tgtDir) {
delErr := os.RemoveAll(tgtDir)
if delErr != nil {
return delErr
}
}
err = os.Rename(path.Join(srcDir, item.Name()), tgtDir)
if err != nil {
return err
}
}
if !b.PackagesOnly {
@ -275,8 +290,8 @@ type templateData struct {
Fields []string
}
func fileExists(path string) bool {
_, err := os.Stat(path)
func fileExists(pathValue string) bool {
_, err := os.Stat(pathValue)
if os.IsNotExist(err) {
return false
}
@ -312,10 +327,22 @@ func createBoilerplate(rootFolder, folder string) error {
resourceName := res.Name()
dsFile := path.Join(folder, svc.Name(), res.Name(), "datasources_gen", fmt.Sprintf("%s_data_source_gen.go", res.Name()))
dsFile := path.Join(
folder,
svc.Name(),
res.Name(),
"datasources_gen",
fmt.Sprintf("%s_data_source_gen.go", res.Name()),
)
handleDS = fileExists(dsFile)
resFile := path.Join(folder, svc.Name(), res.Name(), "resources_gen", fmt.Sprintf("%s_resource_gen.go", res.Name()))
resFile := path.Join(
folder,
svc.Name(),
res.Name(),
"resources_gen",
fmt.Sprintf("%s_resource_gen.go", res.Name()),
)
handleRes = fileExists(resFile)
dsGoFile := path.Join(folder, svc.Name(), res.Name(), "datasource.go")
@ -407,7 +434,6 @@ func createBoilerplate(rootFolder, folder string) error {
if err != nil {
return err
}
}
}
}
@ -416,7 +442,7 @@ func createBoilerplate(rootFolder, folder string) error {
}
func ucfirst(s string) string {
if len(s) == 0 {
if s == "" {
return ""
}
return strings.ToUpper(s[:1]) + s[1:]
@ -451,8 +477,8 @@ func writeTemplateToFile(tplName, tplFile, outFile string, data *templateData) e
}
func generateServiceFiles(rootDir, generatorDir string) error {
// slog.Info("Generating specs folder")
err := os.MkdirAll(path.Join(rootDir, "generated", "specs"), 0755)
//nolint:gosec // this file is not sensitive, so we can use 0755
err := os.MkdirAll(path.Join(rootDir, "generated", "specs"), 0o755)
if err != nil {
return err
}
@ -490,7 +516,6 @@ func generateServiceFiles(rootDir, generatorDir string) error {
continue
}
// slog.Info("Checking spec", "name", spec.Name())
r := regexp.MustCompile(`^(.*)_config.yml$`)
matches := r.FindAllStringSubmatch(specFile.Name(), -1)
if matches != nil {
@ -506,27 +531,44 @@ func generateServiceFiles(rootDir, generatorDir string) error {
resource,
)
oasFile := path.Join(generatorDir, "oas", fmt.Sprintf("%s%s.json", service.Name(), svcVersion.Name()))
oasFile := path.Join(
generatorDir,
"oas",
fmt.Sprintf("%s%s.json", service.Name(), svcVersion.Name()),
)
if _, oasErr := os.Stat(oasFile); os.IsNotExist(oasErr) {
slog.Warn(" could not find matching oas", "svc", service.Name(), "version", svcVersion.Name())
slog.Warn(
" could not find matching oas",
"svc",
service.Name(),
"version",
svcVersion.Name(),
)
continue
}
scName := fmt.Sprintf("%s%s", service.Name(), svcVersion.Name())
scName = strings.ReplaceAll(scName, "-", "")
err = os.MkdirAll(path.Join(rootDir, "generated", "internal", "services", scName, resource), 0755)
//nolint:gosec // this file is not sensitive, so we can use 0755
err = os.MkdirAll(path.Join(rootDir, "generated", "internal", "services", scName, resource), 0o755)
if err != nil {
return err
}
// slog.Info("Generating openapi spec json")
specJsonFile := path.Join(rootDir, "generated", "specs", fmt.Sprintf("%s_%s_spec.json", scName, resource))
specJsonFile := path.Join(
rootDir,
"generated",
"specs",
fmt.Sprintf("%s_%s_spec.json", scName, resource),
)
var stdOut, stdErr bytes.Buffer
// noqa:gosec
// nolint:gosec // #nosec this command is not using any untrusted input, so we can ignore gosec warning
cmd := exec.Command(
"tfplugingen-openapi",
"go",
"run",
"github.com/hashicorp/terraform-plugin-codegen-openapi/cmd/tfplugingen-openapi",
"generate",
"--config",
path.Join(rootDir, "service_specs", service.Name(), svcVersion.Name(), fileName),
@ -553,11 +595,29 @@ func generateServiceFiles(rootDir, generatorDir string) error {
if err = cmd.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
slog.Error("tfplugingen-openapi generate", "code", exitErr.ExitCode(), "error", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-openapi generate",
"code",
exitErr.ExitCode(),
"error",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-openapi generate", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-openapi generate",
"err",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return err
}
}
@ -565,18 +625,26 @@ func generateServiceFiles(rootDir, generatorDir string) error {
slog.Warn(" command output", "stdout", stdOut.String(), "stderr", stdErr.String())
}
// slog.Info("Creating terraform svc resource files folder")
tgtFolder := path.Join(rootDir, "generated", "internal", "services", scName, resource, "resources_gen")
err = os.MkdirAll(tgtFolder, 0755)
tgtFolder := path.Join(
rootDir,
"generated",
"internal",
"services",
scName,
resource,
"resources_gen",
)
//nolint:gosec // this file is not sensitive, so we can use 0755
err = os.MkdirAll(tgtFolder, 0o755)
if err != nil {
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
// nolint:gosec // #nosec this command is not using any untrusted input, so we can ignore gosec warning
cmd2 := exec.Command(
"tfplugingen-framework",
"go",
"run",
"github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework",
"generate",
"resources",
"--input",
@ -597,27 +665,53 @@ func generateServiceFiles(rootDir, generatorDir string) error {
if err = cmd2.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
slog.Error("tfplugingen-framework generate resources", "code", exitErr.ExitCode(), "error", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-framework generate resources",
"code",
exitErr.ExitCode(),
"error",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-framework generate resources", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-framework generate resources",
"err",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return err
}
}
// slog.Info("Creating terraform svc datasource files folder")
tgtFolder = path.Join(rootDir, "generated", "internal", "services", scName, resource, "datasources_gen")
err = os.MkdirAll(tgtFolder, 0755)
tgtFolder = path.Join(
rootDir,
"generated",
"internal",
"services",
scName,
resource,
"datasources_gen",
)
//nolint:gosec // this directory is not sensitive, so we can use 0755
err = os.MkdirAll(tgtFolder, 0o755)
if err != nil {
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
// nolint:gosec // #nosec this command is not using any untrusted input, so we can ignore gosec warning
cmd3 := exec.Command(
"tfplugingen-framework",
"go",
"run",
"github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework",
"generate",
"data-sources",
"--input",
@ -639,11 +733,29 @@ func generateServiceFiles(rootDir, generatorDir string) error {
if err = cmd3.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
slog.Error("tfplugingen-framework generate data-sources", "code", exitErr.ExitCode(), "error", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-framework generate data-sources",
"code",
exitErr.ExitCode(),
"error",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-framework generate data-sources", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
slog.Error(
"tfplugingen-framework generate data-sources",
"err",
err,
"stdout",
stdOut.String(),
"stderr",
stdErr.String(),
)
return err
}
}
@ -674,10 +786,10 @@ func handleTfTagForDatasourceFile(filePath, service, resource string) error {
if err != nil {
return err
}
defer f.Close()
root, err := getRoot()
if err != nil {
//nolint:gocritic // in this case, we want to log the error and exit, as we cannot proceed without the root directory
log.Fatal(err)
}
@ -685,7 +797,6 @@ func handleTfTagForDatasourceFile(filePath, service, resource string) error {
if err != nil {
return err
}
defer tmp.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
@ -709,6 +820,7 @@ func handleTfTagForDatasourceFile(filePath, service, resource string) error {
return err
}
//nolint:gosec // path traversal is not a concern here
if err := os.Rename(tmp.Name(), filePath); err != nil {
log.Fatal(err)
}
@ -773,13 +885,23 @@ func copyFile(src, dst string) (int64, error) {
if err != nil {
return 0, err
}
defer source.Close()
defer func(source *os.File) {
err := source.Close()
if err != nil {
slog.Error("copyFile", "err", err)
}
}(source)
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
defer func(destination *os.File) {
err := destination.Close()
if err != nil {
slog.Error("copyFile", "err", err)
}
}(destination)
nBytes, err := io.Copy(destination, source)
return nBytes, err
}
@ -790,10 +912,8 @@ func getOnlyLatest(m map[string]version) (map[string]version, error) {
item, ok := tmpMap[k]
if !ok {
tmpMap[k] = v
} else {
if item.major == v.major && item.minor < v.minor {
tmpMap[k] = v
}
} else if item.major == v.major && item.minor < v.minor {
tmpMap[k] = v
}
}
return tmpMap, nil
@ -807,18 +927,19 @@ func getVersions(dir string) (map[string]version, error) {
}
for _, entry := range children {
if entry.IsDir() {
versions, err := os.ReadDir(path.Join(dir, "services", entry.Name()))
if err != nil {
return nil, err
}
m, err2 := extractVersions(entry.Name(), versions)
if err2 != nil {
return m, err2
}
for k, v := range m {
res[k] = v
}
if !entry.IsDir() {
continue
}
versions, err := os.ReadDir(path.Join(dir, "services", entry.Name()))
if err != nil {
return nil, err
}
m, err2 := extractVersions(entry.Name(), versions)
if err2 != nil {
return m, err2
}
for k, v := range m {
res[k] = v
}
}
return res, nil
@ -827,20 +948,21 @@ func getVersions(dir string) (map[string]version, error) {
func extractVersions(service string, versionDirs []os.DirEntry) (map[string]version, error) {
res := make(map[string]version)
for _, vDir := range versionDirs {
if vDir.IsDir() {
r := regexp.MustCompile(`v([0-9]+)([a-z]+)([0-9]*)`)
matches := r.FindAllStringSubmatch(vDir.Name(), -1)
if matches == nil {
continue
}
svc, ver, err := handleVersion(service, matches[0])
if err != nil {
return nil, err
}
if !vDir.IsDir() {
continue
}
r := regexp.MustCompile(`v(\d+)([a-z]+)(\d*)`)
matches := r.FindAllStringSubmatch(vDir.Name(), -1)
if matches == nil {
continue
}
svc, ver, err := handleVersion(service, matches[0])
if err != nil {
return nil, err
}
if svc != nil && ver != nil {
res[*svc] = *ver
}
if svc != nil && ver != nil {
res[*svc] = *ver
}
}
return res, nil
@ -927,30 +1049,25 @@ func getTokens(fileName string) ([]string, error) {
return nil, err
}
ast.Inspect(node, func(n ast.Node) bool {
// Suche nach Typ-Deklarationen (structs)
ts, ok := n.(*ast.TypeSpec)
if ok {
if strings.Contains(ts.Name.Name, "Model") {
// fmt.Printf("found model: %s\n", ts.Name.Name)
ast.Inspect(ts, func(sn ast.Node) bool {
tts, tok := sn.(*ast.Field)
if tok {
// fmt.Printf(" found: %+v\n", tts.Names[0])
// spew.Dump(tts.Type)
result = append(result, tts.Names[0].String())
// fld, fldOk := tts.Type.(*ast.Ident)
//if fldOk {
// fmt.Printf("type: %+v\n", fld)
//}
}
return true
})
ast.Inspect(
node, func(n ast.Node) bool {
// Suche nach Typ-Deklarationen (structs)
ts, ok := n.(*ast.TypeSpec)
if ok {
if strings.Contains(ts.Name.Name, "Model") {
ast.Inspect(
ts, func(sn ast.Node) bool {
tts, tok := sn.(*ast.Field)
if tok {
result = append(result, tts.Names[0].String())
}
return true
},
)
}
}
}
return true
})
return true
},
)
return result, nil
}

View file

@ -3,6 +3,7 @@ package build
import (
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"syscall"
@ -74,14 +75,24 @@ func Copy(srcFile, dstFile string) error {
return err
}
defer out.Close()
defer func(out *os.File) {
err := out.Close()
if err != nil {
slog.Error("failed to close file", slog.Any("err", err))
}
}(out)
in, err := os.Open(srcFile)
if err != nil {
return err
}
defer in.Close()
defer func(in *os.File) {
err := in.Close()
if err != nil {
slog.Error("error closing destination file", slog.Any("err", err))
}
}(in)
_, err = io.Copy(out, in)
if err != nil {

View file

@ -16,7 +16,7 @@ var buildCmd = &cobra.Command{
Use: "build",
Short: "Build the necessary boilerplate",
Long: `...`,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
b := build.Builder{
SkipClone: skipClone,
SkipCleanup: skipCleanup,
@ -30,7 +30,7 @@ func NewBuildCmd() *cobra.Command {
return buildCmd
}
func init() { // nolint: gochecknoinits
func init() { //nolint:gochecknoinits // This is the standard way to set up Cobra commands
buildCmd.Flags().BoolVarP(&skipCleanup, "skip-clean", "c", false, "Skip cleanup steps")
buildCmd.Flags().BoolVarP(&skipClone, "skip-clone", "g", false, "Skip cloning from git")
buildCmd.Flags().BoolVarP(&packagesOnly, "packages-only", "p", false, "Only generate packages")

114
cmd/cmd/examplesCmd.go Normal file
View file

@ -0,0 +1,114 @@
package cmd
import (
"fmt"
"os"
"path"
"github.com/spf13/cobra"
)
var examplesCmd = &cobra.Command{
Use: "examples",
Short: "create examples",
Long: `...`,
RunE: func(_ *cobra.Command, _ []string) error {
// filePathStr := "stackit/internal/services/postgresflexalpha/database/datasources_gen/database_data_source_gen.go"
//
// src, err := os.ReadFile(filePathStr)
// if err != nil {
// return err
//}
//
// i := interp.New(
// interp.Options{
// GoPath: "/home/henselinm/.asdf/installs/golang/1.25.6/packages",
// BuildTags: nil,
// Stdin: nil,
// Stdout: nil,
// Stderr: nil,
// Args: nil,
// Env: nil,
// SourcecodeFilesystem: nil,
// Unrestricted: false,
// },
//)
// err = i.Use(i.Symbols("github.com/hashicorp/terraform-plugin-framework-validators"))
// if err != nil {
// return err
//}
// err = i.Use(stdlib.Symbols)
// if err != nil {
// return err
//}
// _, err = i.Eval(string(src))
// if err != nil {
// return err
//}
//
// v, err := i.Eval("DatabaseDataSourceSchema")
// if err != nil {
// return err
//}
//
// bar := v.Interface().(func(string) string)
//
// r := bar("Kung")
// println(r)
//
// evalPath, err := i.EvalPath(filePathStr)
// if err != nil {
// return err
//}
//
// fmt.Printf("%+v\n", evalPath)
// _, err = i.Eval(`import "fmt"`)
// if err != nil {
// return err
//}
// _, err = i.Eval(`func Hallo() { fmt.Println("Hi!") }`)
// if err != nil {
// return err
//}
// v = i.Symbols("Hallo")
// fmt.Println(v)
return workServices()
},
}
func workServices() error {
startPath := path.Join("stackit", "internal", "services")
services, err := os.ReadDir(startPath)
if err != nil {
return err
}
for _, entry := range services {
if !entry.IsDir() {
continue
}
resources, err := os.ReadDir(path.Join(startPath, entry.Name()))
if err != nil {
return err
}
for _, res := range resources {
if !res.IsDir() {
continue
}
fmt.Println("Gefunden:", startPath, "subdir", entry.Name(), "resource", res.Name())
}
}
return nil
}
func NewExamplesCmd() *cobra.Command {
return examplesCmd
}
// func init() { // nolint: gochecknoinits
// examplesCmd.Flags().BoolVarP(&example, "example", "e", false, "example")
//}

View file

@ -24,7 +24,7 @@ var getFieldsCmd = &cobra.Command{
Use: "get-fields",
Short: "get fields from file",
Long: `...`,
PreRunE: func(cmd *cobra.Command, args []string) error {
PreRunE: func(_ *cobra.Command, _ []string) error {
typeStr := "data_source"
if resType != "resource" && resType != "datasource" {
return fmt.Errorf("--type can only be resource or datasource")
@ -76,13 +76,13 @@ var getFieldsCmd = &cobra.Command{
//// Enum check
// switch format {
//case "json", "yaml":
// case "json", "yaml":
//default:
// return fmt.Errorf("invalid --format: %s (want json|yaml)", format)
//}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
return getFields(filePath)
},
}
@ -107,31 +107,26 @@ func getTokens(fileName string) ([]string, error) {
return nil, err
}
ast.Inspect(node, func(n ast.Node) bool {
// Suche nach Typ-Deklarationen (structs)
ts, ok := n.(*ast.TypeSpec)
if ok {
if strings.Contains(ts.Name.Name, "Model") {
// fmt.Printf("found model: %s\n", ts.Name.Name)
ast.Inspect(ts, func(sn ast.Node) bool {
tts, tok := sn.(*ast.Field)
if tok {
// fmt.Printf(" found: %+v\n", tts.Names[0])
// spew.Dump(tts.Type)
result = append(result, tts.Names[0].String())
// fld, fldOk := tts.Type.(*ast.Ident)
//if fldOk {
// fmt.Printf("type: %+v\n", fld)
//}
}
return true
})
ast.Inspect(
node, func(n ast.Node) bool {
// Suche nach Typ-Deklarationen (structs)
ts, ok := n.(*ast.TypeSpec)
if ok {
if strings.Contains(ts.Name.Name, "Model") {
ast.Inspect(
ts, func(sn ast.Node) bool {
tts, tok := sn.(*ast.Field)
if tok {
result = append(result, tts.Names[0].String())
}
return true
},
)
}
}
}
return true
})
return true
},
)
return result, nil
}
@ -139,9 +134,15 @@ func NewGetFieldsCmd() *cobra.Command {
return getFieldsCmd
}
func init() { // nolint: gochecknoinits
func init() { //nolint:gochecknoinits //this is the only way to add the command to the rootCmd
getFieldsCmd.Flags().StringVarP(&inFile, "infile", "i", "", "input filename incl path")
getFieldsCmd.Flags().StringVarP(&svcName, "service", "s", "", "service name")
getFieldsCmd.Flags().StringVarP(&resName, "resource", "r", "", "resource name")
getFieldsCmd.Flags().StringVarP(&resType, "type", "t", "resource", "resource type (data-source or resource [default])")
getFieldsCmd.Flags().StringVarP(
&resType,
"type",
"t",
"resource",
"resource type (data-source or resource [default])",
)
}

View file

@ -35,36 +35,27 @@ type GpgPublicKey struct {
}
func (p *Provider) CreateArchitectureFiles() error {
// var namespace, provider, distPath, repoName, version, gpgFingerprint, gpgPubKeyFile, domain string
log.Println("* Creating architecture files in target directories")
// filename = terraform-provider-[provider]_0.0.1_darwin_amd64.zip - provider_name + version + target + architecture + .zip
// prefix := fmt.Sprintf("v1/providers/%s/%s/%s/", namespace, provider, version)
prefix := path.Join("v1", "providers", p.Namespace, p.Provider, p.Version)
// pathPrefix := fmt.Sprintf("release/%s", prefix)
pathPrefix := path.Join("release", prefix)
// urlPrefix := fmt.Sprintf("https://%s/%s", domain, prefix)
urlPrefix, err := url.JoinPath("https://", p.Domain, prefix)
if err != nil {
return fmt.Errorf("error creating base url: %w", err)
}
// download url = https://example.com/v1/providers/namespace/provider/0.0.1/download/terraform-provider_0.0.1_darwin_amd64.zip
downloadUrlPrefix, err := url.JoinPath(urlPrefix, "download")
if err != nil {
return fmt.Errorf("error crearting download url: %w", err)
}
downloadPathPrefix := path.Join(pathPrefix, "download")
// shasums url = https://example.com/v1/providers/namespace/provider/0.0.1/terraform-provider_0.0.1_SHA256SUMS
shasumsUrl, err := url.JoinPath(urlPrefix, fmt.Sprintf("%s_%s_SHA256SUMS", p.RepoName, p.Version))
if err != nil {
return fmt.Errorf("error creating shasums url: %w", err)
}
// shasums_signature_url = https://example.com/v1/providers/namespace/provider/0.0.1/terraform-provider_0.0.1_SHA256SUMS.sig
shasumsSigUrl := shasumsUrl + ".sig"
gpgAsciiPub, err := p.ReadGpgFile()
@ -116,33 +107,6 @@ func (p *Provider) CreateArchitectureFiles() error {
},
},
}
// var architectureTemplate = []byte(fmt.Sprintf(`
//{
// "protocols": [
// "4.0",
// "5.1",
// "6.0"
// ],
// "os": "%s",
// "arch": "%s",
// "filename": "%s",
// "download_url": "%s",
// "shasums_url": "%s",
// "shasums_signature_url": "%s",
// "shasum": "%s",
// "signing_keys": {
// "gpg_public_keys": [
// {
// "key_id": "%s",
// "ascii_armor": "%s",
// "trust_signature": "",
// "source": "",
// "source_url": ""
// }
// ]
// }
//}
// `, target, arch, fileName, downloadUrl, shasumsUrl, shasumsSigUrl, shasum, gpgFingerprint, gpgAsciiPub))
log.Printf(" - Arch file: %s", archFileName)
@ -160,8 +124,12 @@ func WriteArchitectureFile(filePath string, arch Architecture) error {
if err != nil {
return fmt.Errorf("error encoding data: %w", err)
}
err = os.WriteFile(filePath, jsonString, os.ModePerm)
//nolint:gosec // this file is not sensitive, so we can use os.ModePerm
err = os.WriteFile(
filePath,
jsonString,
os.ModePerm,
)
if err != nil {
return fmt.Errorf("error writing data: %w", err)
}

View file

@ -161,10 +161,12 @@ func (p *Provider) createVersionsFile() error {
target := fileNameSplit[2]
arch := fileNameSplit[3]
version.Platforms = append(version.Platforms, Platform{
OS: target,
Arch: arch,
})
version.Platforms = append(
version.Platforms, Platform{
OS: target,
Arch: arch,
},
)
}
data := Data{}
@ -206,16 +208,19 @@ func (p *Provider) CreateWellKnown() error {
log.Println("* Creating .well-known directory")
pathString := path.Join(p.RootPath, "release", ".well-known")
//nolint:gosec // this file is not sensitive, so we can use ModePerm
err := os.MkdirAll(pathString, os.ModePerm)
if err != nil && !errors.Is(err, fs.ErrExist) {
return fmt.Errorf("error creating '%s' dir: %w", pathString, err)
}
log.Println(" - Writing to .well-known/terraform.json file")
//nolint:gosec // this file is not sensitive, so we can use 0644
err = os.WriteFile(
fmt.Sprintf("%s/terraform.json", pathString),
[]byte(`{"providers.v1": "/v1/providers/"}`),
0644,
0o644,
)
if err != nil {
return err
@ -224,9 +229,10 @@ func (p *Provider) CreateWellKnown() error {
return nil
}
func CreateDir(path string) error {
log.Printf("* Creating %s directory", path)
err := os.MkdirAll(path, os.ModePerm)
func CreateDir(pathValue string) error {
log.Printf("* Creating %s directory", pathValue)
//nolint:gosec // this file is not sensitive, so we can use ModePerm
err := os.MkdirAll(pathValue, os.ModePerm)
if errors.Is(err, fs.ErrExist) {
return nil
}
@ -269,13 +275,23 @@ func CopyFile(src, dst string) (int64, error) {
if err != nil {
return 0, err
}
defer source.Close()
defer func(source *os.File) {
err := source.Close()
if err != nil {
slog.Error("error closing source file", slog.Any("err", err))
}
}(source)
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
defer func(destination *os.File) {
err := destination.Close()
if err != nil {
slog.Error("error closing destination file", slog.Any("err", err))
}
}(destination)
nBytes, err := io.Copy(destination, source)
return nBytes, err
}

View file

@ -0,0 +1,38 @@
{
log {
level debug
}
filesystem tf s3 {
bucket "terraform-provider-privatepreview"
region eu01
endpoint https://object.storage.eu01.onstackit.cloud
use_path_style
}
}
tfregistry.sysops.stackit.rocks {
encode zstd gzip
handle_path /docs/* {
root /srv/www
templates
@md {
file {path}
path *.md
}
rewrite @md /markdown.html
file_server {
browse
}
}
file_server {
fs tf
browse
}
}

View file

@ -35,7 +35,12 @@ func (d *Data) WriteToFile(filePath string) error {
return fmt.Errorf("error encoding data: %w", err)
}
err = os.WriteFile(filePath, jsonString, os.ModePerm)
//nolint:gosec // this file is not sensitive, so we can use os.ModePerm
err = os.WriteFile(
filePath,
jsonString,
os.ModePerm,
)
if err != nil {
return fmt.Errorf("error writing data: %w", err)
}
@ -86,7 +91,13 @@ func (d *Data) LoadFromUrl(uri string) error {
if err != nil {
return err
}
defer os.Remove(file.Name()) // Clean up
defer func(name string) {
//nolint:gosec // The file path is generated by os.CreateTemp and is not user-controllable
err := os.Remove(name)
if err != nil {
slog.Error("failed to remove temporary file", slog.Any("err", err))
}
}(file.Name()) // Clean up
err = DownloadFile(
u.String(),
@ -123,20 +134,30 @@ func (v *Version) AddProtocol(p string) error {
// DownloadFile will download a url and store it in local filepath.
// It writes to the destination file as it downloads it, without
// loading the entire file into memory.
func DownloadFile(url string, filepath string) error {
func DownloadFile(urlValue, filepath string) error {
// Create the file
//nolint:gosec // path traversal is not a concern here, as the filepath is generated by us and not user input
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
defer func(out *os.File) {
err := out.Close()
if err != nil {
slog.Error("failed to close file", slog.Any("err", err))
}
}(out)
// Get the data
resp, err := http.Get(url)
//nolint:gosec,bodyclose // this is a controlled URL, not user input
resp, err := http.Get(urlValue)
if err != nil {
return err
}
defer resp.Body.Close()
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
// Write the body to file
_, err = io.Copy(out, resp.Body)

View file

@ -29,20 +29,32 @@ var publishCmd = &cobra.Command{
Use: "publish",
Short: "Publish terraform provider",
Long: `...`,
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
return publish()
},
}
func init() { // nolint: gochecknoinits
func init() { //nolint:gochecknoinits //this is the standard way to set up cobra commands
publishCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace for the Terraform registry.")
publishCmd.Flags().StringVarP(&domain, "domain", "d", "", "Domain for the Terraform registry.")
publishCmd.Flags().StringVarP(&providerName, "providerName", "p", "", "ProviderName for the Terraform registry.")
publishCmd.Flags().StringVarP(&distPath, "distPath", "x", "dist", "Dist Path for the Terraform registry.")
publishCmd.Flags().StringVarP(&repoName, "repoName", "r", "", "RepoName for the Terraform registry.")
publishCmd.Flags().StringVarP(&version, "version", "v", "", "Version for the Terraform registry.")
publishCmd.Flags().StringVarP(&gpgFingerprint, "gpgFingerprint", "f", "", "GPG Fingerprint for the Terraform registry.")
publishCmd.Flags().StringVarP(&gpgPubKeyFile, "gpgPubKeyFile", "k", "", "GPG PubKey file name for the Terraform registry.")
publishCmd.Flags().StringVarP(
&gpgFingerprint,
"gpgFingerprint",
"f",
"",
"GPG Fingerprint for the Terraform registry.",
)
publishCmd.Flags().StringVarP(
&gpgPubKeyFile,
"gpgPubKeyFile",
"k",
"",
"GPG PubKey file name for the Terraform registry.",
)
err := publishCmd.MarkFlagRequired("namespace")
if err != nil {
@ -105,6 +117,7 @@ func publish() error {
// Create release dir - only the contents of this need to be uploaded to S3
log.Printf("* Creating release directory")
//nolint:gosec // this directory is not sensitive, so we can use 0750
err = os.MkdirAll(path.Join(p.RootPath, "release"), os.ModePerm)
if err != nil && !errors.Is(err, fs.ErrExist) {
return fmt.Errorf("error creating '%s' dir: %w", path.Join(p.RootPath, "release"), err)

View file

@ -5,7 +5,7 @@ import (
"log/slog"
"os"
"github.com/MatusOllah/slogcolor"
"github.com/SladkyCitron/slogcolor"
cc "github.com/ivanpirog/coloredcobra"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd"
@ -30,6 +30,7 @@ func main() {
cmd.NewBuildCmd(),
cmd.NewPublishCmd(),
cmd.NewGetFieldsCmd(),
cmd.NewExamplesCmd(),
)
err := rootCmd.Execute()

View file

@ -1,38 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_database Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_database (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_postgresflexalpha_database" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `database_id` (Number) The ID of the database.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `id` (String) Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`,`database_id`\".",
- `name` (String) The name of the database.
- `owner` (String) The owner of the database.
- `tf_original_api_id` (Number) The id of the database.

View file

@ -1,54 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_flavor Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_flavor (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_postgresflexalpha_flavor" "flavor" {
project_id = var.project_id
region = var.region
cpu = 4
ram = 16
node_type = "Single"
storage_class = "premium-perf2-stackit"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `cpu` (Number) The cpu count of the instance.
- `node_type` (String) defines the nodeType it can be either single or replica
- `project_id` (String) The cpu count of the instance.
- `ram` (Number) The memory of the instance in Gibibyte.
- `region` (String) The flavor description.
- `storage_class` (String) The memory of the instance in Gibibyte.
### Read-Only
- `description` (String) The flavor description.
- `flavor_id` (String) The flavor id of the instance flavor.
- `id` (String) The terraform id of the instance flavor.
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
- `storage_classes` (Attributes List) (see [below for nested schema](#nestedatt--storage_classes))
<a id="nestedatt--storage_classes"></a>
### Nested Schema for `storage_classes`
Read-Only:
- `class` (String)
- `max_io_per_sec` (Number)
- `max_through_in_mb` (Number)

View file

@ -1,68 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_flavors Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_flavors (Data Source)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Optional
- `page` (Number) Number of the page of items list to be returned.
- `size` (Number) Number of items to be returned on each page.
- `sort` (String) Sorting of the flavors to be returned on each page.
### Read-Only
- `flavors` (Attributes List) List of flavors available for the project. (see [below for nested schema](#nestedatt--flavors))
- `pagination` (Attributes) (see [below for nested schema](#nestedatt--pagination))
<a id="nestedatt--flavors"></a>
### Nested Schema for `flavors`
Read-Only:
- `cpu` (Number) The cpu count of the instance.
- `description` (String) The flavor description.
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
- `memory` (Number) The memory of the instance in Gibibyte.
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
- `node_type` (String) defines the nodeType it can be either single or replica
- `storage_classes` (Attributes List) maximum storage which can be ordered for the flavor in Gigabyte. (see [below for nested schema](#nestedatt--flavors--storage_classes))
- `tf_original_api_id` (String) The id of the instance flavor.
<a id="nestedatt--flavors--storage_classes"></a>
### Nested Schema for `flavors.storage_classes`
Read-Only:
- `class` (String)
- `max_io_per_sec` (Number)
- `max_through_in_mb` (Number)
<a id="nestedatt--pagination"></a>
### Nested Schema for `pagination`
Read-Only:
- `page` (Number)
- `size` (Number)
- `sort` (String)
- `total_pages` (Number)
- `total_rows` (Number)

View file

@ -1,87 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_instance Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_instance (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_postgresflexalpha_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `acl` (List of String) List of IPV4 cidr.
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `connection_info` (Attributes) The DNS name and port in the instance overview (see [below for nested schema](#nestedatt--connection_info))
- `encryption` (Attributes) The configuration for instance's volume and backup storage encryption.
⚠︝ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days.
- `status` (String) The current status of the instance.
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters.
<a id="nestedatt--connection_info"></a>
### Nested Schema for `connection_info`
Read-Only:
- `host` (String) The host of the instance.
- `port` (Number) The port of the instance.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Read-Only:
- `kek_key_id` (String) The encryption-key key identifier
- `kek_key_ring_id` (String) The encryption-key keyring identifier
- `kek_key_version` (String) The encryption-key version
- `service_account` (String)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Read-Only:
- `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped.
- `acl` (List of String) List of IPV4 cidr.
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Read-Only:
- `performance_class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.

View file

@ -1,42 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_user Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_user (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_postgresflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
user_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
- `user_id` (Number) The ID of the user.
### Optional
- `id` (String) Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".",
### Read-Only
- `name` (String) The name of the user.
- `roles` (List of String) A list of user roles.
- `status` (String) The current status of the user.
- `tf_original_api_id` (Number) The ID of the user.

View file

@ -1,32 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_database Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_database (Data Source)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `database_name` (String) The name of the database.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility_level` (Number) CompatibilityLevel of the Database.
- `id` (String) Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`,`database_id`\".",
- `name` (String) The name of the database.
- `owner` (String) The owner of the database.
- `tf_original_api_id` (Number) The id of the database.

View file

@ -1,54 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_flavor Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_flavor (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexalpha_flavor" "flavor" {
project_id = var.project_id
region = var.region
cpu = 4
ram = 16
node_type = "Single"
storage_class = "premium-perf2-stackit"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `cpu` (Number) The cpu count of the instance.
- `node_type` (String) defines the nodeType it can be either single or replica
- `project_id` (String) The cpu count of the instance.
- `ram` (Number) The memory of the instance in Gibibyte.
- `region` (String) The flavor description.
- `storage_class` (String) The memory of the instance in Gibibyte.
### Read-Only
- `description` (String) The flavor description.
- `flavor_id` (String) The flavor id of the instance flavor.
- `id` (String) The terraform id of the instance flavor.
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
- `storage_classes` (Attributes List) (see [below for nested schema](#nestedatt--storage_classes))
<a id="nestedatt--storage_classes"></a>
### Nested Schema for `storage_classes`
Read-Only:
- `class` (String)
- `max_io_per_sec` (Number)
- `max_through_in_mb` (Number)

View file

@ -1,78 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_instance Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_instance (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `edition` (String) Edition of the MSSQL server instance
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor.
- `id` (String) Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `status` (String)
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The sqlserver version used for the instance.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Read-Only:
- `kek_key_id` (String) The key identifier
- `kek_key_ring_id` (String) The keyring identifier
- `kek_key_version` (String) The key version
- `service_account` (String)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Read-Only:
- `access_scope` (String) The network access scope of the instance
⚠️ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.
- `acl` (List of String) List of IPV4 cidr.
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Read-Only:
- `class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.

View file

@ -1,44 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_user Data Source - stackitprivatepreview"
subcategory: ""
description: |-
SQLServer Flex user data source schema. Must have a region specified in the provider configuration.
---
# stackitprivatepreview_sqlserverflexalpha_user (Data Source)
SQLServer Flex user data source schema. Must have a `region` specified in the provider configuration.
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
user_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `instance_id` (String) ID of the SQLServer Flex instance.
- `project_id` (String) STACKIT project ID to which the instance is associated.
- `user_id` (Number) User ID.
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only
- `default_database` (String)
- `host` (String)
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`,`user_id`".
- `port` (Number)
- `roles` (Set of String) Database access levels for the user.
- `status` (String)
- `username` (String) Username of the SQLServer Flex instance.

View file

@ -1,40 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexbeta_database Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexbeta_database (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexbeta_database" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
database_name = "dbname"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `database_name` (String) The name of the database.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility_level` (Number) CompatibilityLevel of the Database.
- `id` (String) The terraform internal identifier.
- `name` (String) The name of the database.
- `owner` (String) The owner of the database.
- `tf_original_api_id` (Number) The id of the database.

View file

@ -1,54 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexbeta_flavor Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexbeta_flavor (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexbeta_flavor" "flavor" {
project_id = var.project_id
region = var.region
cpu = 4
ram = 16
node_type = "Single"
storage_class = "premium-perf2-stackit"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `cpu` (Number) The cpu count of the instance.
- `node_type` (String) defines the nodeType it can be either single or HA
- `project_id` (String) The project ID of the flavor.
- `ram` (Number) The memory of the instance in Gibibyte.
- `region` (String) The region of the flavor.
- `storage_class` (String) The memory of the instance in Gibibyte.
### Read-Only
- `description` (String) The flavor description.
- `flavor_id` (String) The id of the instance flavor.
- `id` (String) The id of the instance flavor.
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
- `storage_classes` (Attributes List) maximum storage which can be ordered for the flavor in Gigabyte. (see [below for nested schema](#nestedatt--storage_classes))
<a id="nestedatt--storage_classes"></a>
### Nested Schema for `storage_classes`
Read-Only:
- `class` (String)
- `max_io_per_sec` (Number)
- `max_through_in_mb` (Number)

View file

@ -1,77 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexbeta_instance Data Source - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexbeta_instance (Data Source)
## Example Usage
```terraform
data "stackitprivatepreview_sqlserverflexbeta_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `edition` (String) Edition of the MSSQL server instance
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `status` (String)
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The sqlserver version used for the instance.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Read-Only:
- `kek_key_id` (String) The key identifier
- `kek_key_ring_id` (String) The keyring identifier
- `kek_key_version` (String) The key version
- `service_account` (String)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Read-Only:
- `access_scope` (String) The network access scope of the instance
⚠️ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.
- `acl` (List of String) List of IPV4 cidr.
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Read-Only:
- `class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.

View file

@ -1,83 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview Provider"
description: |-
---
# stackitprivatepreview Provider
## Example Usage
```terraform
provider "stackitprivatepreview" {
default_region = "eu01"
}
provider "stackitprivatepreview" {
default_region = "eu01"
service_account_key_path = "service_account.json"
}
# Authentication
# Key flow
provider "stackitprivatepreview" {
default_region = "eu01"
service_account_key = var.service_account_key
private_key = var.private_key
}
# Key flow (using path)
provider "stackitprivatepreview" {
default_region = "eu01"
service_account_key_path = var.service_account_key_path
private_key_path = var.private_key_path
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Optional
- `authorization_custom_endpoint` (String) Custom endpoint for the Membership service
- `cdn_custom_endpoint` (String) Custom endpoint for the CDN service
- `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`.
- `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global
- `dns_custom_endpoint` (String) Custom endpoint for the DNS service
- `enable_beta_resources` (Boolean) Enable beta resources. Default is false.
- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network
- `git_custom_endpoint` (String) Custom endpoint for the Git service
- `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service
- `kms_custom_endpoint` (String) Custom endpoint for the KMS service
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service
- `logme_custom_endpoint` (String) Custom endpoint for the LogMe service
- `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service
- `modelserving_custom_endpoint` (String) Custom endpoint for the AI Model Serving service
- `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service
- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service
- `observability_custom_endpoint` (String) Custom endpoint for the Observability service
- `opensearch_custom_endpoint` (String) Custom endpoint for the OpenSearch service
- `postgresflex_custom_endpoint` (String) Custom endpoint for the PostgresFlex service
- `private_key` (String) Private RSA key used for authentication, relevant for the key flow. It takes precedence over the private key that is included in the service account key.
- `private_key_path` (String) Path for the private RSA key used for authentication, relevant for the key flow. It takes precedence over the private key that is included in the service account key.
- `rabbitmq_custom_endpoint` (String) Custom endpoint for the RabbitMQ service
- `redis_custom_endpoint` (String) Custom endpoint for the Redis service
- `region` (String, Deprecated) Region will be used as the default location for regional services. Not all services require a region, some are global
- `resourcemanager_custom_endpoint` (String) Custom endpoint for the Resource Manager service
- `scf_custom_endpoint` (String) Custom endpoint for the Cloud Foundry (SCF) service
- `secretsmanager_custom_endpoint` (String) Custom endpoint for the Secrets Manager service
- `server_backup_custom_endpoint` (String) Custom endpoint for the Server Backup service
- `server_update_custom_endpoint` (String) Custom endpoint for the Server Update service
- `service_account_custom_endpoint` (String) Custom endpoint for the Service Account service
- `service_account_email` (String, Deprecated) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource.
- `service_account_key` (String) Service account key used for authentication. If set, the key flow will be used to authenticate all operations.
- `service_account_key_path` (String) Path for the service account key used for authentication. If set, the key flow will be used to authenticate all operations.
- `service_account_token` (String, Deprecated) Token used for authentication. If set, the token flow will be used to authenticate all operations.
- `service_enablement_custom_endpoint` (String) Custom endpoint for the Service Enablement API
- `ske_custom_endpoint` (String) Custom endpoint for the Kubernetes Engine (SKE) service
- `sqlserverflex_custom_endpoint` (String) Custom endpoint for the SQL Server Flex service
- `token_custom_endpoint` (String) Custom endpoint for the token API, which is used to request access tokens when using the key flow

View file

@ -1,47 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_database Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_database (Resource)
## Example Usage
```terraform
resource "stackitprivatepreview_postgresflexalpha_database" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "mydb"
owner = "myusername"
}
# Only use the import statement, if you want to import an existing postgresflex database
import {
to = stackitprivatepreview_postgresflexalpha_database.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.postgres_database_id}"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) The name of the database.
### Optional
- `database_id` (Number) The ID of the database.
- `instance_id` (String) The ID of the instance.
- `owner` (String) The owner of the database.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `id` (Number) The id of the database.

View file

@ -1,131 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_instance Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_instance (Resource)
## Example Usage
```terraform
resource "stackitprivatepreview_postgresflexalpha_instance" "msh-instance-only" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
backup_schedule = "0 0 * * *"
retention_days = 30
flavor_id = "flavor.id"
replicas = 1
storage = {
performance_class = "premium-perf2-stackit"
size = 10
}
encryption = {
kek_key_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
kek_key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
kek_key_version = 1
service_account = "service@account.email"
}
network = {
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
access_scope = "PUBLIC"
}
version = 17
}
# Only use the import statement, if you want to import an existing postgresflex instance
import {
to = stackitprivatepreview_postgresflexalpha_instance.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id}"
}
import {
to = stackitprivatepreview_postgresflexalpha_instance.import-example
identity = {
project_id = var.project_id
region = var.region
instance_id = var.postgres_instance_id
}
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `flavor_id` (String) The id of the instance flavor.
- `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days.
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters.
### Optional
- `encryption` (Attributes) The configuration for instance's volume and backup storage encryption.
⚠︝ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. (see [below for nested schema](#nestedatt--encryption))
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `acl` (List of String) List of IPV4 cidr.
- `connection_info` (Attributes) The DNS name and port in the instance overview (see [below for nested schema](#nestedatt--connection_info))
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `status` (String) The current status of the instance.
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Required:
- `acl` (List of String) List of IPV4 cidr.
Optional:
- `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped.
Read-Only:
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Required:
- `performance_class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Required:
- `kek_key_id` (String) The encryption-key key identifier
- `kek_key_ring_id` (String) The encryption-key keyring identifier
- `kek_key_version` (String) The encryption-key version
- `service_account` (String)
<a id="nestedatt--connection_info"></a>
### Nested Schema for `connection_info`
Read-Only:
- `host` (String) The host of the instance.
- `port` (Number) The port of the instance.

View file

@ -1,50 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_postgresflexalpha_user Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_postgresflexalpha_user (Resource)
## Example Usage
```terraform
resource "stackitprivatepreview_postgresflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "username"
roles = ["role"]
}
# Only use the import statement, if you want to import an existing postgresflex user
import {
to = stackitprivatepreview_postgresflexalpha_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id}"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) The name of the user.
### Optional
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
- `roles` (List of String) A list containing the user roles for the instance.
- `user_id` (Number) The ID of the user.
### Read-Only
- `connection_string` (String) The connection string for the user to the instance.
- `id` (Number) The ID of the user.
- `password` (String) The password for the user.
- `status` (String) The current status of the user.

View file

@ -1,36 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_database Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_database (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) The name of the database.
- `owner` (String) The owner of the database.
### Optional
- `collation` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility` (Number) CompatibilityLevel of the Database.
- `database_name` (String) The name of the database.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility_level` (Number) CompatibilityLevel of the Database.
- `id` (Number) The id of the database.

View file

@ -1,103 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_instance Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_instance (Resource)
## Example Usage
```terraform
resource "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
backup_schedule = "00 00 * * *"
flavor = {
cpu = 4
ram = 16
}
storage = {
class = "class"
size = 5
}
version = 2022
}
# Only use the import statement, if you want to import an existing sqlserverflex instance
import {
to = stackitprivatepreview_sqlserverflexalpha_instance.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id}"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `flavor_id` (String) The id of the instance flavor.
- `name` (String) The name of the instance.
- `network` (Attributes) the network configuration of the instance. (see [below for nested schema](#nestedatt--network))
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `version` (String) The sqlserver version used for the instance.
### Optional
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `edition` (String) Edition of the MSSQL server instance
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `replicas` (Number) How many replicas the instance should have.
- `status` (String)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Required:
- `acl` (List of String) List of IPV4 cidr.
Optional:
- `access_scope` (String) The network access scope of the instance
⚠️ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.
Read-Only:
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Required:
- `class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Required:
- `kek_key_id` (String) The key identifier
- `kek_key_ring_id` (String) The keyring identifier
- `kek_key_version` (String) The key version
- `service_account` (String)

View file

@ -1,53 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexalpha_user Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexalpha_user (Resource)
## Example Usage
```terraform
resource "stackitprivatepreview_sqlserverflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
username = "username"
roles = ["role"]
}
# Only use the import statement, if you want to import an existing sqlserverflex user
import {
to = stackitprivatepreview_sqlserverflexalpha_user.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id},${var.sql_user_id}"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `roles` (List of String) A list containing the user roles for the instance. A list with the valid user roles can be retrieved using the List Roles endpoint.
- `username` (String) The name of the user.
### Optional
- `default_database` (String) The default database for a user of the instance.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
- `user_id` (Number) The ID of the user.
### Read-Only
- `host` (String) The host of the instance in which the user belongs to.
- `id` (Number) The ID of the user.
- `password` (String) The password for the user.
- `port` (Number) The port of the instance in which the user belongs to.
- `status` (String) The current status of the user.
- `uri` (String) The connection string for the user to the instance.

View file

@ -1,36 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexbeta_database Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexbeta_database (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) The name of the database.
- `owner` (String) The owner of the database.
### Optional
- `collation` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility` (Number) CompatibilityLevel of the Database.
- `database_name` (String) The name of the database.
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility_level` (Number) CompatibilityLevel of the Database.
- `id` (Number) The id of the database.

View file

@ -1,158 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackitprivatepreview_sqlserverflexbeta_instance Resource - stackitprivatepreview"
subcategory: ""
description: |-
---
# stackitprivatepreview_sqlserverflexbeta_instance (Resource)
## Example Usage
```terraform
# without encryption and SNA
resource "stackitprivatepreview_sqlserverflexbeta_instance" "instance" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = "flavor_id"
storage = {
class = "premium-perf2-stackit"
size = 50
}
version = 2022
network = {
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
access_scope = "SNA"
}
}
# without encryption and PUBLIC
resource "stackitprivatepreview_sqlserverflexbeta_instance" "instance" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = "flavor_id"
storage = {
class = "premium-perf2-stackit"
size = 50
}
version = 2022
network = {
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
access_scope = "PUBLIC"
}
}
# with encryption and SNA
resource "stackitprivatepreview_sqlserverflexbeta_instance" "instance" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = "flavor_id"
storage = {
class = "premium-perf2-stackit"
size = 50
}
version = 2022
encryption = {
kek_key_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
kek_key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
kek_key_version = 1
service_account = "service_account@email"
}
network = {
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
access_scope = "SNA"
}
}
# Only use the import statement, if you want to import an existing sqlserverflex instance
import {
to = stackitprivatepreview_sqlserverflexalpha_instance.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id}"
}
# import with identity
import {
to = stackitprivatepreview_sqlserverflexalpha_instance.import-example
identity = {
project_id = var.project_id
region = var.region
instance_id = var.sql_instance_id
}
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `flavor_id` (String) The id of the instance flavor.
- `name` (String) The name of the instance.
- `network` (Attributes) the network configuration of the instance. (see [below for nested schema](#nestedatt--network))
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `version` (String) The sqlserver version used for the instance.
### Optional
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only
- `edition` (String) Edition of the MSSQL server instance
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `replicas` (Number) How many replicas the instance should have.
- `status` (String)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Required:
- `acl` (List of String) List of IPV4 cidr.
Optional:
- `access_scope` (String) The network access scope of the instance
⚠️ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.
Read-Only:
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a>
### Nested Schema for `storage`
Required:
- `class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Required:
- `kek_key_id` (String) The key identifier
- `kek_key_ring_id` (String) The keyring identifier
- `kek_key_version` (String) The key version
- `service_account` (String)

View file

@ -9,4 +9,14 @@ resource "stackitprivatepreview_postgresflexalpha_database" "example" {
import {
to = stackitprivatepreview_postgresflexalpha_database.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.postgres_database_id}"
}
}
import {
to = stackitprivatepreview_postgresflexalpha_database.import-example
identity = {
project_id = "project_id"
region = "region"
instance_id = "instance_id"
database_id = "database_id"
}
}

View file

@ -1,4 +1,4 @@
resource "stackitprivatepreview_postgresflexalpha_instance" "msh-instance-only" {
resource "stackitprivatepreview_postgresflexalpha_instance" "example-instance" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance"
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
@ -17,7 +17,7 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "msh-instance-only"
service_account = "service@account.email"
}
network = {
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
access_scope = "PUBLIC"
}
version = 17

View file

@ -1,7 +1,7 @@
resource "stackitprivatepreview_postgresflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "username"
name = "username"
roles = ["role"]
}
@ -9,4 +9,14 @@ resource "stackitprivatepreview_postgresflexalpha_user" "example" {
import {
to = stackitprivatepreview_postgresflexalpha_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id}"
}
}
import {
to = stackitprivatepreview_postgresflexalpha_user.import-example
identity = {
project_id = "project.id"
region = "region"
instance_id = "instance.id"
user_id = "user.id"
}
}

View file

@ -0,0 +1,24 @@
resource "stackitprivatepreview_sqlserverflexalpha_database" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
collation = ""
compatibility = "160"
name = ""
owner = ""
}
# Only use the import statement, if you want to import a existing sqlserverflex database
import {
to = stackitprivatepreview_sqlserverflexalpha_database.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id},${var.sql_user_id}"
}
import {
to = stackitprivatepreview_sqlserverflexalpha_database.import-example
identity = {
project_id = "project.id"
region = "region"
instance_id = "instance.id"
database_id = "database.id"
}
}

View file

@ -0,0 +1,12 @@
resource "stackitprivatepreview_sqlserverflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
username = "username"
roles = ["role"]
}
# Only use the import statement, if you want to import an existing sqlserverflex user
import {
to = stackitprivatepreview_sqlserverflexalpha_user.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id},${var.sql_user_id}"
}

View file

@ -0,0 +1,12 @@
resource "stackitprivatepreview_sqlserverflexalpha_user" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
username = "username"
roles = ["role"]
}
# Only use the import statement, if you want to import an existing sqlserverflex user
import {
to = stackitprivatepreview_sqlserverflexalpha_user.import-example
id = "${var.project_id},${var.region},${var.sql_instance_id},${var.sql_user_id}"
}

260
go.mod
View file

@ -2,10 +2,17 @@ module tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stac
go 1.25.6
require (
github.com/MatusOllah/slogcolor v1.7.0
github.com/SladkyCitron/slogcolor v1.8.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/golangci/golangci-lint/v2 v2.10.1
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0
github.com/hashicorp/terraform-plugin-docs v0.24.0
github.com/hashicorp/terraform-plugin-framework v1.17.0
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.29.0
@ -13,74 +20,283 @@ require (
github.com/hashicorp/terraform-plugin-testing v1.14.0
github.com/iancoleman/strcase v0.3.0
github.com/ivanpirog/coloredcobra v1.0.1
github.com/jarcoal/httpmock v1.4.1
github.com/joho/godotenv v1.5.1
github.com/ldez/go-git-cmd-wrapper/v2 v2.9.1
github.com/spf13/cobra v1.10.2
github.com/stackitcloud/stackit-sdk-go/core v0.21.0
github.com/stackitcloud/stackit-sdk-go/core v0.21.1
github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.23-alpha
github.com/teambition/rrule-go v1.8.2
golang.org/x/tools v0.42.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
golang.org/x/telemetry v0.0.0-20260116145544-c6413dc483f5 // indirect
)
require github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
require (
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
4d63.com/gochecknoglobals v0.2.2 // indirect
codeberg.org/chavacava/garif v0.2.0 // indirect
codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect
dario.cat/mergo v1.0.1 // indirect
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
dev.gaijin.team/go/golib v0.6.0 // indirect
github.com/4meepo/tagalign v1.4.3 // indirect
github.com/Abirdcfly/dupword v0.1.7 // indirect
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
github.com/Antonboom/errname v1.1.1 // indirect
github.com/Antonboom/nilnil v1.1.1 // indirect
github.com/Antonboom/testifylint v1.6.4 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Djarvur/go-err113 v0.1.1 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/MirrexOne/unqueryvet v1.5.3 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
github.com/alexkohler/prealloc v1.0.2 // indirect
github.com/alfatraining/structtag v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/alingse/nilnesserr v0.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/cloudflare/circl v1.6.2 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bkielbasa/cyclop v1.2.3 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
github.com/bombsimon/wsl/v5 v5.6.0 // indirect
github.com/breml/bidichk v0.3.3 // indirect
github.com/breml/errchkjson v0.4.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/butuzov/ireturn v0.4.0 // indirect
github.com/butuzov/mirror v1.3.0 // indirect
github.com/catenacyber/perfsprint v0.10.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.11 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/ckaznocha/intrange v0.3.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/daixiang0/gci v0.13.7 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.6 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.20 // indirect
github.com/go-critic/go-critic v0.14.3 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/godoc-lint/godoc-lint v0.11.2 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golangci/asciicheck v0.5.0 // indirect
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
github.com/golangci/go-printf-func-name v0.1.1 // indirect
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
github.com/golangci/golines v0.15.0 // indirect
github.com/golangci/misspell v0.8.0 // indirect
github.com/golangci/plugin-module-register v0.1.2 // indirect
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
github.com/gordonklaus/ineffassign v0.2.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.5.0 // indirect
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
github.com/hashicorp/cli v1.1.7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.7.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hc-install v0.9.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.24.0 // indirect
github.com/hashicorp/terraform-exec v0.25.0 // indirect
github.com/hashicorp/terraform-json v0.27.2 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.2 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.2.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/jgautheron/goconst v1.8.2 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jjti/go-spancheck v0.6.5 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
github.com/kulti/thelper v0.7.1 // indirect
github.com/kunwardeep/paralleltest v1.0.15 // indirect
github.com/lasiar/canonicalheader v1.1.2 // indirect
github.com/ldez/exptostd v0.4.5 // indirect
github.com/ldez/gomoddirectives v0.8.0 // indirect
github.com/ldez/grignotin v0.10.1 // indirect
github.com/ldez/structtags v0.6.1 // indirect
github.com/ldez/tagliatelle v0.7.2 // indirect
github.com/ldez/usetesting v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/macabu/inamedparam v0.2.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
github.com/manuelarte/funcorder v0.5.0 // indirect
github.com/maratori/testableexamples v1.0.1 // indirect
github.com/maratori/testpackage v1.1.2 // indirect
github.com/matoous/godox v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgechev/revive v1.14.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
github.com/oklog/run v1.2.0 // indirect
github.com/pb33f/libopenapi v0.15.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/ryancurrah/gomodguard v1.4.1 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
github.com/securego/gosec/v2 v2.23.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sonatard/noctx v0.4.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tetafro/godot v1.5.4 // indirect
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
github.com/timonwong/loggercheck v0.11.0 // indirect
github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/ultraware/funlen v0.2.0 // indirect
github.com/ultraware/whitespace v0.2.0 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/uudashr/iface v1.4.1 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xen0n/gosmopolitan v1.3.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
github.com/yuin/goldmark v1.7.7 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.14.0 // indirect
go-simpler.org/sloglint v0.11.1 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
go.augendre.info/arangolint v0.4.0 // indirect
go.augendre.info/fatcontext v0.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.7.0 // indirect
mvdan.cc/gofumpt v0.9.2 // indirect
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
)
tool golang.org/x/tools/cmd/goimports

1099
go.sum

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,13 @@
version: "2"
run:
concurrency: 4
output:
formats:
text:
print-linter-name: true
print-issued-lines: true
colors: true
path: stdout
linters:
enable:
- bodyclose
@ -68,6 +75,10 @@ linters:
- name: empty-lines
- name: early-return
exclusions:
paths:
- stackit-sdk-generator/
- generated/
- pkg_gen/
generated: lax
warn-unused: true
# Excluding configuration per-path, per-linter, per-text and per-source.
@ -76,14 +87,6 @@ linters:
- path: _test\.go
linters:
- gochecknoinits
paths:
- third_party/
- builtin/
- examples/
- tools/copy.go
- tools/main.go
- pkg_gen/
- cmd/
formatters:
enable:
- gofmt
@ -91,12 +94,4 @@ formatters:
settings:
goimports:
local-prefixes:
- tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview
exclusions:
generated: lax
paths:
- third_party/
- builtin/
- examples/
- pkg_gen/
- cmd/
- tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview

View file

@ -0,0 +1,39 @@
package testutils
import (
"fmt"
"net/http"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/jarcoal/httpmock"
)
func TestName() string {
pc, _, _, _ := runtime.Caller(1)
nameFull := runtime.FuncForPC(pc).Name()
nameEnd := filepath.Ext(nameFull)
name := strings.TrimPrefix(nameEnd, ".")
return name
}
func ActivateEnvironmentHttpMocks() {
httpmock.RegisterNoResponder(
func(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("no responder found for %s %s, please check your http mocks", req.Method, req.URL)
},
)
httpmock.RegisterRegexpResponder(
"GET",
regexp.MustCompile(`^https://api\.bap\.microsoft\.com/providers/Microsoft\.BusinessAppPlatform/locations/(europe|unitedstates)/environmentLanguages\?api-version=2023-06-01$`),
func(_ *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(
http.StatusOK,
httpmock.File("../../services/languages/tests/datasource/Validate_Read/get_languages.json").String(),
), nil
},
)
}

View file

@ -65,15 +65,15 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example2
resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbadminuser" {
project_id = var.project_id
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example.instance_id
username = var.db_admin_username
roles = ["createdb", "login"]
name = var.db_admin_username
roles = ["createdb", "login", "login"]
# roles = ["createdb", "login", "createrole"]
}
resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbadminuser2" {
project_id = var.project_id
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example2.instance_id
username = var.db_admin_username
name = var.db_admin_username
roles = ["createdb", "login"]
# roles = ["createdb", "login", "createrole"]
}
@ -81,7 +81,7 @@ resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbadminuser2" {
resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbuser" {
project_id = var.project_id
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example.instance_id
username = var.db_username
name = var.db_name
roles = ["login"]
# roles = ["createdb", "login", "createrole"]
}

View file

@ -1,5 +1,5 @@
data "stackitprivatepreview_sqlserverflexalpha_flavor" "sqlserver_flavor" {
data "stackitprivatepreview_sqlserverflexbeta_flavor" "sqlserver_flavor" {
project_id = var.project_id
region = "eu01"
cpu = 4
@ -9,5 +9,5 @@ data "stackitprivatepreview_sqlserverflexalpha_flavor" "sqlserver_flavor" {
}
output "sqlserver_flavor" {
value = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
value = data.stackitprivatepreview_sqlserverflexbeta_flavor.sqlserver_flavor.flavor_id
}

View file

@ -18,15 +18,15 @@
# value = stackit_kms_key.key.key_id
# }
resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-sna-001" {
resource "stackitprivatepreview_sqlserverflexbeta_instance" "msh-beta-sna-001" {
project_id = var.project_id
name = "msh-sna-001"
name = "msh-beta-sna-001"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
flavor_id = data.stackitprivatepreview_sqlserverflexbeta_flavor.sqlserver_flavor.flavor_id
storage = {
class = "premium-perf2-stackit"
size = 50
size = 10
}
version = 2022
encryption = {
@ -34,9 +34,11 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-sna-001" {
#keyring_id = stackit_kms_keyring.keyring.keyring_id
#key_version = 1
# key with scope public
kek_key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
# kek_key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
kek_key_id = "c6878f92-ce55-4b79-8236-ba9d001d7967" # msh-k-001
# key_id = var.key_id
kek_key_ring_id = var.keyring_id
# kek_key_ring_id = var.keyring_id
kek_key_ring_id = "0dea3f5f-9947-4dda-a9d3-18418832cefe" # msh-kr-sna01
kek_key_version = var.key_version
service_account = var.sa_email
}
@ -46,83 +48,16 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-sna-001" {
}
}
resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-sna-101" {
project_id = var.project_id
name = "msh-sna-101"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
storage = {
class = "premium-perf2-stackit"
size = 50
}
version = 2022
encryption = {
#key_id = stackit_kms_key.key.key_id
#keyring_id = stackit_kms_keyring.keyring.keyring_id
#key_version = 1
# key with scope public
kek_key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
# key_id = var.key_id
kek_key_ring_id = var.keyring_id
kek_key_version = var.key_version
service_account = var.sa_email
}
network = {
acl = ["0.0.0.0/0", "193.148.160.0/19"]
access_scope = "SNA"
}
resource "stackitprivatepreview_sqlserverflexbeta_user" "betauser" {
project_id = var.project_id
instance_id = stackitprivatepreview_sqlserverflexbeta_instance.msh-beta-sna-001.instance_id
username = "betauser"
roles = ["##STACKIT_DatabaseManager##", "##STACKIT_LoginManager##"]
}
resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-nosna-001" {
project_id = var.project_id
name = "msh-nosna-001"
backup_schedule = "0 3 * * *"
retention_days = 31
flavor_id = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
storage = {
class = "premium-perf2-stackit"
size = 50
}
version = 2022
# encryption = {
# #key_id = stackit_kms_key.key.key_id
# #keyring_id = stackit_kms_keyring.keyring.keyring_id
# #key_version = 1
# #key_id = var.key_id
# # key with scope public
# key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
# keyring_id = var.keyring_id
# key_version = var.key_version
# service_account = var.sa_email
# }
network = {
acl = ["0.0.0.0/0", "193.148.160.0/19"]
access_scope = "PUBLIC"
}
resource "stackitprivatepreview_sqlserverflexbeta_database" "betadb" {
project_id = var.project_id
instance_id = stackitprivatepreview_sqlserverflexbeta_instance.msh-beta-sna-001.instance_id
name = "mshtest002"
owner = stackitprivatepreview_sqlserverflexbeta_user.betauser.username
}
# data "stackitprivatepreview_sqlserverflexalpha_instance" "test" {
# project_id = var.project_id
# instance_id = var.instance_id
# region = "eu01"
# }
# output "test" {
# value = data.stackitprivatepreview_sqlserverflexalpha_instance.test
# }
# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbadminuser" {
# project_id = var.project_id
# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id
# username = var.db_admin_username
# roles = ["##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"]
# }
# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" {
# project_id = var.project_id
# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id
# username = var.db_username
# roles = ["##STACKIT_LoginManager##"]
# }

View file

@ -1,19 +0,0 @@
#!/usr/bin/env bash
# This script lints the SDK modules and the internal examples
# Pre-requisites: golangci-lint
set -eo pipefail
ROOT_DIR=$(git rev-parse --show-toplevel)
GOLANG_CI_YAML_PATH="${ROOT_DIR}/golang-ci.yaml"
GOLANG_CI_ARGS="--allow-parallel-runners --timeout=5m --config=${GOLANG_CI_YAML_PATH}"
if type -p golangci-lint >/dev/null; then
:
else
echo "golangci-lint not installed, unable to proceed."
exit 1
fi
cd ${ROOT_DIR}
golangci-lint run ${GOLANG_CI_ARGS}

View file

@ -17,11 +17,7 @@ elif [ "$action" = "tools" ]; then
go mod download
# go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.2
# go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@v0.21.0
go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@v0.24.0
go install golang.org/x/tools/cmd/goimports@v0.42.0
else
echo "Invalid action: '$action', please use $0 help for help"
fi

View file

@ -14,5 +14,5 @@ fi
mkdir -p ${ROOT_DIR}/docs
echo ">> Generating documentation"
tfplugindocs generate \
go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate \
--provider-name "stackitprivatepreview"

View file

@ -32,7 +32,7 @@ const (
type EphemeralProviderData struct {
ProviderData
PrivateKey string
PrivateKey string //nolint:gosec //this is a placeholder and not used in this code
PrivateKeyPath string
ServiceAccountKey string
ServiceAccountKeyPath string
@ -105,11 +105,13 @@ func DiagsToError(diags diag.Diagnostics) error {
diagsError := diags.Errors()
diagsStrings := make([]string, 0)
for _, diagnostic := range diagsError {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"(%s) %s",
diagnostic.Summary(),
diagnostic.Detail(),
))
diagsStrings = append(
diagsStrings, fmt.Sprintf(
"(%s) %s",
diagnostic.Summary(),
diagnostic.Detail(),
),
)
}
return fmt.Errorf("%s", strings.Join(diagsStrings, ";"))
}
@ -136,14 +138,22 @@ func LogAndAddWarning(ctx context.Context, diags *diag.Diagnostics, summary, det
func LogAndAddWarningBeta(ctx context.Context, diags *diag.Diagnostics, name string, resourceType ResourceType) {
warnTitle := fmt.Sprintf("The %s %q is in beta", resourceType, name)
warnContent := fmt.Sprintf("The %s %q is in beta and may be subject to breaking changes in the future. Use with caution.", resourceType, name)
warnContent := fmt.Sprintf(
"The %s %q is in beta and may be subject to breaking changes in the future. Use with caution.",
resourceType,
name,
)
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
diags.AddWarning(warnTitle, warnContent)
}
func LogAndAddErrorBeta(ctx context.Context, diags *diag.Diagnostics, name string, resourceType ResourceType) {
errTitle := fmt.Sprintf("The %s %q is in beta and beta is not enabled", resourceType, name)
errContent := fmt.Sprintf(`The %s %q is in beta and the beta functionality is currently not enabled. To enable it, set the environment variable STACKIT_TF_ENABLE_BETA_RESOURCES to "true" or set the "enable_beta_resources" provider field to true.`, resourceType, name)
errContent := fmt.Sprintf(
`The %s %q is in beta and the beta functionality is currently not enabled. To enable it, set the environment variable STACKIT_TF_ENABLE_BETA_RESOURCES to "true" or set the "enable_beta_resources" provider field to true.`,
resourceType,
name,
)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diags.AddError(errTitle, errContent)
}
@ -161,8 +171,10 @@ func LogResponse(ctx context.Context) context.Context {
traceId := runtime.GetTraceId(ctx)
ctx = tflog.SetField(ctx, "x-trace-id", traceId)
tflog.Info(ctx, "response data", map[string]interface{}{
"x-trace-id": traceId,
})
tflog.Info(
ctx, "response data", map[string]interface{}{
"x-trace-id": traceId,
},
)
return ctx
}

View file

@ -0,0 +1,237 @@
package core
import (
"context"
"crypto/rand"
"errors"
"fmt"
"math/big"
"net/http"
"time"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
const (
// backoffMultiplier is the factor by which the delay is multiplied for exponential backoff.
backoffMultiplier = 2
// jitterFactor is the divisor used to calculate jitter (e.g., half of the base delay).
jitterFactor = 2
)
var (
// ErrRequestFailedAfterRetries is returned when a request fails after all retry attempts.
ErrRequestFailedAfterRetries = errors.New("request failed after all retry attempts")
)
// RetryRoundTripper implements an http.RoundTripper that adds automatic retry logic for failed requests.
type RetryRoundTripper struct {
next http.RoundTripper
maxRetries int
initialDelay time.Duration
maxDelay time.Duration
perTryTimeout time.Duration
}
// NewRetryRoundTripper creates a new instance of the RetryRoundTripper with the specified configuration.
func NewRetryRoundTripper(
next http.RoundTripper,
maxRetries int,
initialDelay, maxDelay, perTryTimeout time.Duration,
) *RetryRoundTripper {
return &RetryRoundTripper{
next: next,
maxRetries: maxRetries,
initialDelay: initialDelay,
maxDelay: maxDelay,
perTryTimeout: perTryTimeout,
}
}
// RoundTrip executes the request and retries on failure.
func (rrt *RetryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := rrt.executeRequest(req)
if !rrt.shouldRetry(resp, err) {
if err != nil {
return resp, fmt.Errorf("initial request failed, not retrying: %w", err)
}
return resp, nil
}
return rrt.retryLoop(req, resp, err)
}
// executeRequest performs a single HTTP request with a per-try timeout.
func (rrt *RetryRoundTripper) executeRequest(req *http.Request) (*http.Response, error) {
ctx, cancel := context.WithTimeout(req.Context(), rrt.perTryTimeout)
defer cancel()
resp, err := rrt.next.RoundTrip(req.WithContext(ctx))
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return resp, fmt.Errorf("per-try timeout of %v exceeded: %w", rrt.perTryTimeout, err)
}
return resp, fmt.Errorf("http roundtrip failed: %w", err)
}
return resp, nil
}
// retryLoop handles the retry logic for a failed request.
func (rrt *RetryRoundTripper) retryLoop(
req *http.Request,
initialResp *http.Response,
initialErr error,
) (*http.Response, error) {
var (
lastErr = initialErr
resp = initialResp
currentDelay = rrt.initialDelay
)
ctx := req.Context()
for attempt := 1; attempt <= rrt.maxRetries; attempt++ {
rrt.logRetryAttempt(ctx, attempt, currentDelay, lastErr)
waitDuration := rrt.calculateWaitDurationWithJitter(ctx, currentDelay)
if err := rrt.waitForDelay(ctx, waitDuration); err != nil {
return nil, err // Context was canceled during wait.
}
// Exponential backoff for the next potential retry.
currentDelay = rrt.updateCurrentDelay(currentDelay)
// Retry attempt.
resp, lastErr = rrt.executeRequest(req)
if !rrt.shouldRetry(resp, lastErr) {
if lastErr != nil {
return resp, fmt.Errorf("request failed on retry attempt %d: %w", attempt, lastErr)
}
return resp, nil
}
}
return nil, rrt.handleFinalError(ctx, resp, lastErr)
}
// logRetryAttempt logs the details of a retry attempt.
func (rrt *RetryRoundTripper) logRetryAttempt(
ctx context.Context,
attempt int,
delay time.Duration,
err error,
) {
tflog.Info(
ctx, "Request failed, retrying...", map[string]interface{}{
"attempt": attempt,
"max_attempts": rrt.maxRetries,
"delay": delay,
"error": err,
},
)
}
// updateCurrentDelay calculates the next delay for exponential backoff.
func (rrt *RetryRoundTripper) updateCurrentDelay(currentDelay time.Duration) time.Duration {
currentDelay *= backoffMultiplier
if currentDelay > rrt.maxDelay {
return rrt.maxDelay
}
return currentDelay
}
// handleFinalError constructs and returns the final error after all retries have been exhausted.
func (rrt *RetryRoundTripper) handleFinalError(
ctx context.Context,
resp *http.Response,
lastErr error,
) error {
if resp != nil {
if err := resp.Body.Close(); err != nil {
tflog.Warn(
ctx, "Failed to close response body", map[string]interface{}{
"error": err.Error(),
},
)
}
}
if lastErr != nil {
return fmt.Errorf("%w: %w", ErrRequestFailedAfterRetries, lastErr)
}
// This case occurs if shouldRetry was true due to a retryable status code,
// but all retries failed with similar status codes.
if resp != nil {
return fmt.Errorf(
"%w: last retry attempt failed with status code %d",
ErrRequestFailedAfterRetries,
resp.StatusCode,
)
}
return fmt.Errorf("%w: no response received", ErrRequestFailedAfterRetries)
}
// shouldRetry determines if a request should be retried based on the response or an error.
func (rrt *RetryRoundTripper) shouldRetry(resp *http.Response, err error) bool {
if err != nil {
return true
}
if resp != nil {
if resp.StatusCode == http.StatusBadGateway ||
resp.StatusCode == http.StatusServiceUnavailable ||
resp.StatusCode == http.StatusGatewayTimeout {
return true
}
}
return false
}
// calculateWaitDurationWithJitter calculates the backoff duration for the next retry,
// adding a random jitter to prevent thundering herd issues.
func (rrt *RetryRoundTripper) calculateWaitDurationWithJitter(
ctx context.Context,
baseDelay time.Duration,
) time.Duration {
if baseDelay <= 0 {
return 0
}
maxJitter := int64(baseDelay / jitterFactor)
if maxJitter <= 0 {
return baseDelay
}
random, err := rand.Int(rand.Reader, big.NewInt(maxJitter))
if err != nil {
tflog.Warn(
ctx, "Failed to generate random jitter, proceeding without it.", map[string]interface{}{
"error": err.Error(),
},
)
return baseDelay
}
jitter := time.Duration(random.Int64())
return baseDelay + jitter
}
// waitForDelay pauses execution for a given duration or until the context is canceled.
func (rrt *RetryRoundTripper) waitForDelay(ctx context.Context, delay time.Duration) error {
select {
case <-ctx.Done():
return fmt.Errorf("context canceled during backoff wait: %w", ctx.Err())
case <-time.After(delay):
return nil
}
}

View file

@ -0,0 +1,252 @@
package core
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
)
type mockRoundTripper struct {
roundTripFunc func(req *http.Request) (*http.Response, error)
callCount int32
}
func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
atomic.AddInt32(&m.callCount, 1)
return m.roundTripFunc(req)
}
func (m *mockRoundTripper) CallCount() int32 {
return atomic.LoadInt32(&m.callCount)
}
func TestRetryRoundTripper_RoundTrip(t *testing.T) {
t.Parallel()
testRetryConfig := func(next http.RoundTripper) *RetryRoundTripper {
return NewRetryRoundTripper(
next,
3,
1*time.Millisecond,
10*time.Millisecond,
50*time.Millisecond,
)
}
noRetryTests := []struct {
name string
mockStatusCode int
expectedStatusCode int
}{
{
name: "should succeed on the first try",
mockStatusCode: http.StatusOK,
expectedStatusCode: http.StatusOK,
},
{
name: "should not retry on a non-retryable status code like 400",
mockStatusCode: http.StatusBadRequest,
expectedStatusCode: http.StatusBadRequest,
},
}
for _, testCase := range noRetryTests {
t.Run(
testCase.name, func(t *testing.T) {
t.Parallel()
mock := &mockRoundTripper{
roundTripFunc: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: testCase.mockStatusCode,
Body: io.NopCloser(nil),
Request: req,
}, nil
},
}
tripper := testRetryConfig(mock)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
resp, err := tripper.RoundTrip(req)
if resp != nil {
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
t.Errorf("failed to close response body: %v", closeErr)
}
}()
}
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if resp.StatusCode != testCase.expectedStatusCode {
t.Fatalf("expected status code %d, got %d", testCase.expectedStatusCode, resp.StatusCode)
}
if mock.CallCount() != 1 {
t.Fatalf("expected 1 call, got %d", mock.CallCount())
}
},
)
}
t.Run(
"should retry on retryable status code (503) and eventually fail", func(t *testing.T) {
t.Parallel()
mock := &mockRoundTripper{
roundTripFunc: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusServiceUnavailable,
Body: io.NopCloser(nil),
Request: req,
}, nil
},
}
tripper := testRetryConfig(mock)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
resp, err := tripper.RoundTrip(req)
if resp != nil {
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
t.Errorf("failed to close response body: %v", closeErr)
}
}()
}
if err == nil {
t.Fatal("expected an error, but got nil")
}
expectedErrorMsg := "last retry attempt failed with status code 503"
if !strings.Contains(err.Error(), expectedErrorMsg) {
t.Fatalf("expected error to contain %q, got %q", expectedErrorMsg, err.Error())
}
if mock.CallCount() != 4 { // 1 initial + 3 retries
t.Fatalf("expected 4 calls, got %d", mock.CallCount())
}
},
)
t.Run(
"should succeed after one retry", func(t *testing.T) {
t.Parallel()
mock := &mockRoundTripper{}
mock.roundTripFunc = func(req *http.Request) (*http.Response, error) {
if mock.CallCount() < 2 {
return &http.Response{
StatusCode: http.StatusServiceUnavailable,
Body: io.NopCloser(nil),
Request: req,
}, nil
}
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(nil),
Request: req,
}, nil
}
tripper := testRetryConfig(mock)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
resp, err := tripper.RoundTrip(req)
if resp != nil {
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
t.Errorf("failed to close response body: %v", closeErr)
}
}()
}
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status code %d, got %d", http.StatusOK, resp.StatusCode)
}
if mock.CallCount() != 2 {
t.Fatalf("expected 2 calls, got %d", mock.CallCount())
}
},
)
t.Run(
"should retry on network error", func(t *testing.T) {
t.Parallel()
mockErr := errors.New("simulated network error")
mock := &mockRoundTripper{
roundTripFunc: func(_ *http.Request) (*http.Response, error) {
return nil, mockErr
},
}
tripper := testRetryConfig(mock)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
resp, err := tripper.RoundTrip(req)
if resp != nil {
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
t.Errorf("failed to close response body: %v", closeErr)
}
}()
}
if !errors.Is(err, mockErr) {
t.Fatalf("expected error to be %v, got %v", mockErr, err)
}
if mock.CallCount() != 4 { // 1 initial + 3 retries
t.Fatalf("expected 4 calls, got %d", mock.CallCount())
}
},
)
t.Run(
"should abort retries if the main context is canceled", func(t *testing.T) {
t.Parallel()
mock := &mockRoundTripper{
roundTripFunc: func(req *http.Request) (*http.Response, error) {
select {
case <-time.After(100 * time.Millisecond):
return nil, errors.New("this should not be returned")
case <-req.Context().Done():
return nil, req.Context().Err()
}
},
}
tripper := testRetryConfig(mock)
baseCtx := context.Background()
ctx, cancel := context.WithTimeout(baseCtx, 20*time.Millisecond)
defer cancel()
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody).WithContext(ctx)
resp, err := tripper.RoundTrip(req)
if resp != nil {
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
t.Errorf("failed to close response body: %v", closeErr)
}
}()
}
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("expected error to be context.DeadlineExceeded, got %v", err)
}
if mock.CallCount() != 1 {
t.Fatalf("expected 1 call, got %d", mock.CallCount())
}
},
)
}

View file

@ -281,24 +281,12 @@ func (r *databaseResource) Read(
return
}
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseId, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
region := model.Region.ValueString()
databaseId := model.DatabaseId.ValueInt64()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
@ -333,6 +321,7 @@ func (r *databaseResource) Read(
return
}
// TODO: use values from api to identify drift
// Save identity into Terraform state
identity := DatabaseResourceIdentityModel{
ProjectID: types.StringValue(projectId),
@ -367,24 +356,12 @@ func (r *databaseResource) Update(
return
}
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseId64, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
region := model.Region.ValueString()
databaseId64 := model.DatabaseId.ValueInt64()
if databaseId64 > math.MaxInt32 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")

View file

@ -33,15 +33,27 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema {
},
"connection_info": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"host": schema.StringAttribute{
"write": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"host": schema.StringAttribute{
Computed: true,
Description: "The host of the instance.",
MarkdownDescription: "The host of the instance.",
},
"port": schema.Int64Attribute{
Computed: true,
Description: "The port of the instance.",
MarkdownDescription: "The port of the instance.",
},
},
CustomType: WriteType{
ObjectType: types.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
},
Computed: true,
Description: "The host of the instance.",
MarkdownDescription: "The host of the instance.",
},
"port": schema.Int64Attribute{
Computed: true,
Description: "The port of the instance.",
MarkdownDescription: "The port of the instance.",
Description: "The DNS name and port in the instance overview",
MarkdownDescription: "The DNS name and port in the instance overview",
},
},
CustomType: ConnectionInfoType{
@ -50,8 +62,8 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema {
},
},
Computed: true,
Description: "The DNS name and port in the instance overview",
MarkdownDescription: "The DNS name and port in the instance overview",
Description: "The connection information of the instance",
MarkdownDescription: "The connection information of the instance",
},
"encryption": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
@ -243,40 +255,22 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob
attributes := in.Attributes()
hostAttribute, ok := attributes["host"]
writeAttribute, ok := attributes["write"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
`write is missing from object`)
return nil, diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
writeVal, ok := writeAttribute.(basetypes.ObjectValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return nil, diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute))
}
if diags.HasError() {
@ -284,8 +278,7 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob
}
return ConnectionInfoValue{
Host: hostVal,
Port: portVal,
Write: writeVal,
state: attr.ValueStateKnown,
}, diags
}
@ -353,40 +346,22 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[
return NewConnectionInfoValueUnknown(), diags
}
hostAttribute, ok := attributes["host"]
writeAttribute, ok := attributes["write"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
`write is missing from object`)
return NewConnectionInfoValueUnknown(), diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
writeVal, ok := writeAttribute.(basetypes.ObjectValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return NewConnectionInfoValueUnknown(), diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute))
}
if diags.HasError() {
@ -394,8 +369,7 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[
}
return ConnectionInfoValue{
Host: hostVal,
Port: portVal,
Write: writeVal,
state: attr.ValueStateKnown,
}, diags
}
@ -468,12 +442,401 @@ func (t ConnectionInfoType) ValueType(ctx context.Context) attr.Value {
var _ basetypes.ObjectValuable = ConnectionInfoValue{}
type ConnectionInfoValue struct {
Write basetypes.ObjectValue `tfsdk:"write"`
state attr.ValueState
}
func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 1)
var val tftypes.Value
var err error
attrTypes["write"] = basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 1)
val, err = v.Write.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["write"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v ConnectionInfoValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v ConnectionInfoValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v ConnectionInfoValue) String() string {
return "ConnectionInfoValue"
}
func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
var write basetypes.ObjectValue
if v.Write.IsNull() {
write = types.ObjectNull(
WriteValue{}.AttributeTypes(ctx),
)
}
if v.Write.IsUnknown() {
write = types.ObjectUnknown(
WriteValue{}.AttributeTypes(ctx),
)
}
if !v.Write.IsNull() && !v.Write.IsUnknown() {
write = types.ObjectValueMust(
WriteValue{}.AttributeTypes(ctx),
v.Write.Attributes(),
)
}
attributeTypes := map[string]attr.Type{
"write": basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"write": write,
})
return objVal, diags
}
func (v ConnectionInfoValue) Equal(o attr.Value) bool {
other, ok := o.(ConnectionInfoValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Write.Equal(other.Write) {
return false
}
return true
}
func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type {
return ConnectionInfoType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"write": basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
}
}
var _ basetypes.ObjectTypable = WriteType{}
type WriteType struct {
basetypes.ObjectType
}
func (t WriteType) Equal(o attr.Type) bool {
other, ok := o.(WriteType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t WriteType) String() string {
return "WriteType"
}
func (t WriteType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
hostAttribute, ok := attributes["host"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
return nil, diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return nil, diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
}
if diags.HasError() {
return nil, diags
}
return WriteValue{
Host: hostVal,
Port: portVal,
state: attr.ValueStateKnown,
}, diags
}
func NewWriteValueNull() WriteValue {
return WriteValue{
state: attr.ValueStateNull,
}
}
func NewWriteValueUnknown() WriteValue {
return WriteValue{
state: attr.ValueStateUnknown,
}
}
func NewWriteValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (WriteValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing WriteValue Attribute Value",
"While creating a WriteValue value, a missing attribute value was detected. "+
"A WriteValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid WriteValue Attribute Type",
"While creating a WriteValue value, an invalid attribute value was detected. "+
"A WriteValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("WriteValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra WriteValue Attribute Value",
"While creating a WriteValue value, an extra attribute value was detected. "+
"A WriteValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra WriteValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewWriteValueUnknown(), diags
}
hostAttribute, ok := attributes["host"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
return NewWriteValueUnknown(), diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return NewWriteValueUnknown(), diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
}
if diags.HasError() {
return NewWriteValueUnknown(), diags
}
return WriteValue{
Host: hostVal,
Port: portVal,
state: attr.ValueStateKnown,
}, diags
}
func NewWriteValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) WriteValue {
object, diags := NewWriteValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewWriteValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t WriteType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewWriteValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewWriteValueUnknown(), nil
}
if in.IsNull() {
return NewWriteValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewWriteValueMust(WriteValue{}.AttributeTypes(ctx), attributes), nil
}
func (t WriteType) ValueType(ctx context.Context) attr.Value {
return WriteValue{}
}
var _ basetypes.ObjectValuable = WriteValue{}
type WriteValue struct {
Host basetypes.StringValue `tfsdk:"host"`
Port basetypes.Int64Value `tfsdk:"port"`
state attr.ValueState
}
func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
func (v WriteValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
var val tftypes.Value
@ -518,19 +881,19 @@ func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Valu
}
}
func (v ConnectionInfoValue) IsNull() bool {
func (v WriteValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v ConnectionInfoValue) IsUnknown() bool {
func (v WriteValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v ConnectionInfoValue) String() string {
return "ConnectionInfoValue"
func (v WriteValue) String() string {
return "WriteValue"
}
func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
func (v WriteValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
@ -556,8 +919,8 @@ func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.Objec
return objVal, diags
}
func (v ConnectionInfoValue) Equal(o attr.Value) bool {
other, ok := o.(ConnectionInfoValue)
func (v WriteValue) Equal(o attr.Value) bool {
other, ok := o.(WriteValue)
if !ok {
return false
@ -582,15 +945,15 @@ func (v ConnectionInfoValue) Equal(o attr.Value) bool {
return true
}
func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type {
return ConnectionInfoType{
func (v WriteValue) Type(ctx context.Context) attr.Type {
return WriteType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
func (v WriteValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"host": basetypes.StringType{},
"port": basetypes.Int64Type{},

View file

@ -7,7 +7,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
postgresflexalphadatasource "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/datasources_gen"
@ -20,28 +19,6 @@ func mapGetInstanceResponseToModel(
m *postgresflexalpharesource.InstanceModel,
resp *postgresflex.GetInstanceResponse,
) error {
tflog.Debug(
ctx, ">>>> MSH DEBUG <<<<", map[string]interface{}{
"id": m.Id.ValueString(),
"instance_id": m.InstanceId.ValueString(),
"backup_schedule": m.BackupSchedule.ValueString(),
"flavor_id": m.FlavorId.ValueString(),
"encryption.kek_key_id": m.Encryption.KekKeyId.ValueString(),
"encryption.kek_key_ring_id": m.Encryption.KekKeyRingId.ValueString(),
"encryption.kek_key_version": m.Encryption.KekKeyVersion.ValueString(),
"encryption.service_account": m.Encryption.ServiceAccount.ValueString(),
"is_deletable": m.IsDeletable.ValueBool(),
"name": m.Name.ValueString(),
"status": m.Status.ValueString(),
"retention_days": m.RetentionDays.ValueInt64(),
"replicas": m.Replicas.ValueInt64(),
"network.instance_address": m.Network.InstanceAddress.ValueString(),
"network.router_address": m.Network.RouterAddress.ValueString(),
"version": m.Version.ValueString(),
"network.acl": m.Network.Acl.String(),
},
)
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Encryption = postgresflexalpharesource.NewEncryptionValueNull()
if resp.HasEncryption() {
@ -56,9 +33,9 @@ func mapGetInstanceResponseToModel(
)
}
isConnectionInfoIncomplete := resp.ConnectionInfo == nil ||
resp.ConnectionInfo.Host == nil || *resp.ConnectionInfo.Host == "" ||
resp.ConnectionInfo.Port == nil || *resp.ConnectionInfo.Port == 0
isConnectionInfoIncomplete := resp.ConnectionInfo == nil || resp.ConnectionInfo.Write == nil ||
resp.ConnectionInfo.Write.Host == nil || *resp.ConnectionInfo.Write.Host == "" ||
resp.ConnectionInfo.Write.Port == nil || *resp.ConnectionInfo.Write.Port == 0
if isConnectionInfoIncomplete {
m.ConnectionInfo = postgresflexalpharesource.NewConnectionInfoValueNull()
@ -66,22 +43,17 @@ func mapGetInstanceResponseToModel(
m.ConnectionInfo = postgresflexalpharesource.NewConnectionInfoValueMust(
postgresflexalpharesource.ConnectionInfoValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"host": types.StringPointerValue(resp.ConnectionInfo.Host),
"port": types.Int64PointerValue(resp.ConnectionInfo.Port),
"write": postgresflexalpharesource.NewWriteValueMust(
postgresflexalpharesource.WriteValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"host": types.StringPointerValue(resp.ConnectionInfo.Write.Host),
"port": types.Int64PointerValue(resp.ConnectionInfo.Write.Port),
},
),
},
)
}
m.ConnectionInfo.Host = types.StringValue("")
if host, ok := resp.ConnectionInfo.GetHostOk(); ok {
m.ConnectionInfo.Host = types.StringValue(host)
}
m.ConnectionInfo.Port = types.Int64Value(0)
if port, ok := resp.ConnectionInfo.GetPortOk(); ok {
m.ConnectionInfo.Port = types.Int64Value(port)
}
m.FlavorId = types.StringValue(resp.GetFlavorId())
if m.Id.IsNull() || m.Id.IsUnknown() {
m.Id = utils.BuildInternalTerraformId(
@ -187,9 +159,9 @@ func mapGetDataInstanceResponseToModel(
}
func handleConnectionInfo(ctx context.Context, m *dataSourceModel, resp *postgresflex.GetInstanceResponse) {
isConnectionInfoIncomplete := resp.ConnectionInfo == nil ||
resp.ConnectionInfo.Host == nil || *resp.ConnectionInfo.Host == "" ||
resp.ConnectionInfo.Port == nil || *resp.ConnectionInfo.Port == 0
isConnectionInfoIncomplete := resp.ConnectionInfo == nil || resp.ConnectionInfo.Write == nil ||
resp.ConnectionInfo.Write.Host == nil || *resp.ConnectionInfo.Write.Host == "" ||
resp.ConnectionInfo.Write.Port == nil || *resp.ConnectionInfo.Write.Port == 0
if isConnectionInfoIncomplete {
m.ConnectionInfo = postgresflexalphadatasource.NewConnectionInfoValueNull()
@ -197,8 +169,13 @@ func handleConnectionInfo(ctx context.Context, m *dataSourceModel, resp *postgre
m.ConnectionInfo = postgresflexalphadatasource.NewConnectionInfoValueMust(
postgresflexalphadatasource.ConnectionInfoValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"host": types.StringPointerValue(resp.ConnectionInfo.Host),
"port": types.Int64PointerValue(resp.ConnectionInfo.Port),
"write": postgresflexalphadatasource.NewWriteValueMust(
postgresflexalphadatasource.WriteValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"host": types.StringPointerValue(resp.ConnectionInfo.Write.Host),
"port": types.Int64PointerValue(resp.ConnectionInfo.Write.Port),
},
),
},
)
}

View file

@ -243,7 +243,8 @@ func (r *instanceResource) Create(
return
}
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).
WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(
ctx,
@ -327,10 +328,6 @@ func (r *instanceResource) Read(
ctx = core.InitProviderContext(ctx)
// projectId := model.ProjectId.ValueString()
// region := r.providerData.GetRegionWithOverride(model.Region)
// instanceId := model.InstanceId.ValueString()
var projectId string
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
projectId = model.ProjectId.ValueString()
@ -434,18 +431,6 @@ func (r *instanceResource) Update(
return
}
// if model.InstanceId.IsNull() || model.InstanceId.IsUnknown() {
// core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", "instanceId is null or unknown")
// return
//}
//
// if model.ProjectId.IsNull() || model.ProjectId.IsUnknown() {
// core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", "projectId is null or unknown")
// return
//}
// projectId := model.ProjectId.ValueString()
// instanceId := model.InstanceId.ValueString()
projectId := identityData.ProjectID.ValueString()
instanceId := identityData.InstanceID.ValueString()
region := model.Region.ValueString()
@ -593,7 +578,7 @@ func (r *instanceResource) ImportState(
ctx, &resp.Diagnostics,
"Error importing instance",
fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id] Got: %q",
"Expected import identifier with format [project_id],[region],[instance_id] Got: %q",
req.ID,
),
)

View file

@ -35,15 +35,27 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
},
"connection_info": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"host": schema.StringAttribute{
"write": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"host": schema.StringAttribute{
Computed: true,
Description: "The host of the instance.",
MarkdownDescription: "The host of the instance.",
},
"port": schema.Int64Attribute{
Computed: true,
Description: "The port of the instance.",
MarkdownDescription: "The port of the instance.",
},
},
CustomType: WriteType{
ObjectType: types.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
},
Computed: true,
Description: "The host of the instance.",
MarkdownDescription: "The host of the instance.",
},
"port": schema.Int64Attribute{
Computed: true,
Description: "The port of the instance.",
MarkdownDescription: "The port of the instance.",
Description: "The DNS name and port in the instance overview",
MarkdownDescription: "The DNS name and port in the instance overview",
},
},
CustomType: ConnectionInfoType{
@ -52,8 +64,8 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
},
},
Computed: true,
Description: "The DNS name and port in the instance overview",
MarkdownDescription: "The DNS name and port in the instance overview",
Description: "The connection information of the instance",
MarkdownDescription: "The connection information of the instance",
},
"encryption": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
@ -263,40 +275,22 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob
attributes := in.Attributes()
hostAttribute, ok := attributes["host"]
writeAttribute, ok := attributes["write"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
`write is missing from object`)
return nil, diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
writeVal, ok := writeAttribute.(basetypes.ObjectValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return nil, diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute))
}
if diags.HasError() {
@ -304,8 +298,7 @@ func (t ConnectionInfoType) ValueFromObject(ctx context.Context, in basetypes.Ob
}
return ConnectionInfoValue{
Host: hostVal,
Port: portVal,
Write: writeVal,
state: attr.ValueStateKnown,
}, diags
}
@ -373,40 +366,22 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[
return NewConnectionInfoValueUnknown(), diags
}
hostAttribute, ok := attributes["host"]
writeAttribute, ok := attributes["write"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
`write is missing from object`)
return NewConnectionInfoValueUnknown(), diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
writeVal, ok := writeAttribute.(basetypes.ObjectValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return NewConnectionInfoValueUnknown(), diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
fmt.Sprintf(`write expected to be basetypes.ObjectValue, was: %T`, writeAttribute))
}
if diags.HasError() {
@ -414,8 +389,7 @@ func NewConnectionInfoValue(attributeTypes map[string]attr.Type, attributes map[
}
return ConnectionInfoValue{
Host: hostVal,
Port: portVal,
Write: writeVal,
state: attr.ValueStateKnown,
}, diags
}
@ -488,12 +462,401 @@ func (t ConnectionInfoType) ValueType(ctx context.Context) attr.Value {
var _ basetypes.ObjectValuable = ConnectionInfoValue{}
type ConnectionInfoValue struct {
Write basetypes.ObjectValue `tfsdk:"write"`
state attr.ValueState
}
func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 1)
var val tftypes.Value
var err error
attrTypes["write"] = basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 1)
val, err = v.Write.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["write"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v ConnectionInfoValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v ConnectionInfoValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v ConnectionInfoValue) String() string {
return "ConnectionInfoValue"
}
func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
var write basetypes.ObjectValue
if v.Write.IsNull() {
write = types.ObjectNull(
WriteValue{}.AttributeTypes(ctx),
)
}
if v.Write.IsUnknown() {
write = types.ObjectUnknown(
WriteValue{}.AttributeTypes(ctx),
)
}
if !v.Write.IsNull() && !v.Write.IsUnknown() {
write = types.ObjectValueMust(
WriteValue{}.AttributeTypes(ctx),
v.Write.Attributes(),
)
}
attributeTypes := map[string]attr.Type{
"write": basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"write": write,
})
return objVal, diags
}
func (v ConnectionInfoValue) Equal(o attr.Value) bool {
other, ok := o.(ConnectionInfoValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Write.Equal(other.Write) {
return false
}
return true
}
func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type {
return ConnectionInfoType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"write": basetypes.ObjectType{
AttrTypes: WriteValue{}.AttributeTypes(ctx),
},
}
}
var _ basetypes.ObjectTypable = WriteType{}
type WriteType struct {
basetypes.ObjectType
}
func (t WriteType) Equal(o attr.Type) bool {
other, ok := o.(WriteType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t WriteType) String() string {
return "WriteType"
}
func (t WriteType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
hostAttribute, ok := attributes["host"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
return nil, diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return nil, diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
}
if diags.HasError() {
return nil, diags
}
return WriteValue{
Host: hostVal,
Port: portVal,
state: attr.ValueStateKnown,
}, diags
}
func NewWriteValueNull() WriteValue {
return WriteValue{
state: attr.ValueStateNull,
}
}
func NewWriteValueUnknown() WriteValue {
return WriteValue{
state: attr.ValueStateUnknown,
}
}
func NewWriteValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (WriteValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing WriteValue Attribute Value",
"While creating a WriteValue value, a missing attribute value was detected. "+
"A WriteValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid WriteValue Attribute Type",
"While creating a WriteValue value, an invalid attribute value was detected. "+
"A WriteValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("WriteValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("WriteValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra WriteValue Attribute Value",
"While creating a WriteValue value, an extra attribute value was detected. "+
"A WriteValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra WriteValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewWriteValueUnknown(), diags
}
hostAttribute, ok := attributes["host"]
if !ok {
diags.AddError(
"Attribute Missing",
`host is missing from object`)
return NewWriteValueUnknown(), diags
}
hostVal, ok := hostAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`host expected to be basetypes.StringValue, was: %T`, hostAttribute))
}
portAttribute, ok := attributes["port"]
if !ok {
diags.AddError(
"Attribute Missing",
`port is missing from object`)
return NewWriteValueUnknown(), diags
}
portVal, ok := portAttribute.(basetypes.Int64Value)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`port expected to be basetypes.Int64Value, was: %T`, portAttribute))
}
if diags.HasError() {
return NewWriteValueUnknown(), diags
}
return WriteValue{
Host: hostVal,
Port: portVal,
state: attr.ValueStateKnown,
}, diags
}
func NewWriteValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) WriteValue {
object, diags := NewWriteValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewWriteValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t WriteType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewWriteValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewWriteValueUnknown(), nil
}
if in.IsNull() {
return NewWriteValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewWriteValueMust(WriteValue{}.AttributeTypes(ctx), attributes), nil
}
func (t WriteType) ValueType(ctx context.Context) attr.Value {
return WriteValue{}
}
var _ basetypes.ObjectValuable = WriteValue{}
type WriteValue struct {
Host basetypes.StringValue `tfsdk:"host"`
Port basetypes.Int64Value `tfsdk:"port"`
state attr.ValueState
}
func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
func (v WriteValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
var val tftypes.Value
@ -538,19 +901,19 @@ func (v ConnectionInfoValue) ToTerraformValue(ctx context.Context) (tftypes.Valu
}
}
func (v ConnectionInfoValue) IsNull() bool {
func (v WriteValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v ConnectionInfoValue) IsUnknown() bool {
func (v WriteValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v ConnectionInfoValue) String() string {
return "ConnectionInfoValue"
func (v WriteValue) String() string {
return "WriteValue"
}
func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
func (v WriteValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
@ -576,8 +939,8 @@ func (v ConnectionInfoValue) ToObjectValue(ctx context.Context) (basetypes.Objec
return objVal, diags
}
func (v ConnectionInfoValue) Equal(o attr.Value) bool {
other, ok := o.(ConnectionInfoValue)
func (v WriteValue) Equal(o attr.Value) bool {
other, ok := o.(WriteValue)
if !ok {
return false
@ -602,15 +965,15 @@ func (v ConnectionInfoValue) Equal(o attr.Value) bool {
return true
}
func (v ConnectionInfoValue) Type(ctx context.Context) attr.Type {
return ConnectionInfoType{
func (v WriteValue) Type(ctx context.Context) attr.Type {
return WriteType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v ConnectionInfoValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
func (v WriteValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"host": basetypes.StringType{},
"port": basetypes.Int64Type{},

View file

@ -30,39 +30,46 @@ var testInstances []string
func init() {
sweeperName := fmt.Sprintf("%s_%s", pfx, "sweeper")
resource.AddTestSweepers(sweeperName, &resource.Sweeper{
Name: sweeperName,
F: func(region string) error {
ctx := context.Background()
apiClientConfigOptions := []config.ConfigurationOption{}
apiClient, err := postgresflexalpha2.NewAPIClient(apiClientConfigOptions...)
if err != nil {
log.Fatalln(err)
}
resource.AddTestSweepers(
sweeperName, &resource.Sweeper{
Name: sweeperName,
F: func(_ string) error { // region is passed by the testing framework
ctx := context.Background()
apiClientConfigOptions := []config.ConfigurationOption{}
apiClient, err := postgresflexalpha2.NewAPIClient(apiClientConfigOptions...)
if err != nil {
log.Fatalln(err)
}
instances, err := apiClient.ListInstancesRequest(ctx, testutils.ProjectId, testutils.Region).
Size(100).
Execute()
if err != nil {
log.Fatalln(err)
}
instances, err := apiClient.ListInstancesRequest(ctx, testutils.ProjectId, testutils.Region).
Size(100).
Execute()
if err != nil {
log.Fatalln(err)
}
for _, inst := range instances.GetInstances() {
if strings.HasPrefix(inst.GetName(), "tf-acc-") {
for _, item := range testInstances {
if inst.GetName() == item {
delErr := apiClient.DeleteInstanceRequestExecute(ctx, testutils.ProjectId, testutils.Region, inst.GetId())
if delErr != nil {
// TODO: maybe just warn?
log.Fatalln(delErr)
for _, inst := range instances.GetInstances() {
if strings.HasPrefix(inst.GetName(), "tf-acc-") {
for _, item := range testInstances {
if inst.GetName() == item {
delErr := apiClient.DeleteInstanceRequestExecute(
ctx,
testutils.ProjectId,
testutils.Region,
inst.GetId(),
)
if delErr != nil {
// TODO: maybe just warn?
log.Fatalln(delErr)
}
}
}
}
}
}
return nil
return nil
},
},
})
)
}
func TestInstanceResourceSchema(t *testing.T) {
@ -195,57 +202,67 @@ func TestAccInstance(t *testing.T) {
updSizeData := exData
updSizeData.Size = 25
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", exData.TfName)
testInstances = append(testInstances, exData.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
exData,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", exData.TfName), "name", exData.Name),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", exData.TfName), "id"),
),
resource.ParallelTest(
t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", exData.TfName)
testInstances = append(testInstances, exData.TfName)
},
// Update name and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
updNameData,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", exData.TfName), "name", updNameData.Name),
),
},
// Update size and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
updSizeData,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", exData.TfName),
"storage.size",
strconv.Itoa(int(updSizeData.Size)),
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
exData,
),
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", exData.TfName),
"name",
exData.Name,
),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", exData.TfName), "id"),
),
},
// Update name and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
updNameData,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", exData.TfName),
"name",
updNameData.Name,
),
),
},
// Update size and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
updSizeData,
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", exData.TfName),
"storage.size",
strconv.Itoa(int(updSizeData.Size)),
),
),
},
//// Import test
//{
// ResourceName: "example_resource.test",
// ImportState: true,
// ImportStateVerify: true,
// },
},
//// Import test
//{
// ResourceName: "example_resource.test",
// ImportState: true,
// ImportStateVerify: true,
// },
},
})
)
}
func TestAccInstanceWithUsers(t *testing.T) {
@ -260,29 +277,35 @@ func TestAccInstanceWithUsers(t *testing.T) {
},
}
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", data.TfName)
testInstances = append(testInstances, data.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
data,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", data.TfName), "name", data.Name),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
),
resource.ParallelTest(
t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", data.TfName)
testInstances = append(testInstances, data.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
data,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", data.TfName),
"name",
data.Name,
),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
),
},
},
},
})
)
}
func TestAccInstanceWithDatabases(t *testing.T) {
@ -306,32 +329,38 @@ func TestAccInstanceWithDatabases(t *testing.T) {
},
}
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", data.TfName)
testInstances = append(testInstances, data.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
data,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", data.TfName), "name", data.Name),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "name", dbName),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "owner", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "database", dbName), "id"),
),
resource.ParallelTest(
t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", data.TfName)
testInstances = append(testInstances, data.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
data,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(
testutils.ResStr(pfx, "instance", data.TfName),
"name",
data.Name,
),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "name", dbName),
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "owner", userName),
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "database", dbName), "id"),
),
},
},
},
})
)
}
// func setupMockServer() *httptest.Server {
@ -461,7 +490,7 @@ func TestAccInstanceWithDatabases(t *testing.T) {
// "project_id": testutils.ProjectId,
//}
//
//func configResources(backupSchedule string, _ *string) string {
// func configResources(backupSchedule string, _ *string) string {
// return fmt.Sprintf(
// `
// %s
@ -535,7 +564,7 @@ func TestAccInstanceWithDatabases(t *testing.T) {
// )
//}
//
//func TestAccPostgresFlexFlexResource(t *testing.T) {
// func TestAccPostgresFlexFlexResource(t *testing.T) {
// resource.ParallelTest(
// t, resource.TestCase{
// ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
@ -954,7 +983,7 @@ func TestAccInstanceWithDatabases(t *testing.T) {
// )
//}
//
//func testAccCheckPostgresFlexDestroy(s *terraform.State) error {
// func testAccCheckPostgresFlexDestroy(s *terraform.State) error {
// ctx := context.Background()
// var client *postgresflex.APIClient
// var err error

View file

@ -48,7 +48,7 @@ resource "stackitprivatepreview_postgresflexalpha_database" "{{ $db.Name }}" {
project_id = "{{ $db.ProjectId }}"
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.instance_id
name = "{{ $db.Name }}"
owner = "{{ $db.Owner }}"
owner = stackitprivatepreview_postgresflexalpha_user.{{ $db.Owner }}.name
}
{{ end }}
{{ end }}

View file

@ -164,16 +164,16 @@ func TestMapFieldsCreate(t *testing.T) {
},
testRegion,
resourceModel{
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
ConnectionString: types.StringNull(),
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
//ConnectionString: types.StringNull(),
},
true,
},
@ -186,16 +186,16 @@ func TestMapFieldsCreate(t *testing.T) {
},
testRegion,
resourceModel{
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("username"),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringValue("status"),
ConnectionString: types.StringNull(),
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("username"),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringValue("status"),
//ConnectionString: types.StringNull(),
},
true,
},
@ -208,16 +208,16 @@ func TestMapFieldsCreate(t *testing.T) {
},
testRegion,
resourceModel{
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
ConnectionString: types.StringNull(),
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Password: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
//ConnectionString: types.StringNull(),
},
true,
},
@ -285,15 +285,15 @@ func TestMapFields(t *testing.T) {
},
testRegion,
resourceModel{
Id: types.Int64Value(1),
UserId: types.Int64Value(int64(1)),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
ConnectionString: types.StringNull(),
Id: types.Int64Value(1),
UserId: types.Int64Value(int64(1)),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
//ConnectionString: types.StringNull(),
},
true,
},
@ -324,9 +324,9 @@ func TestMapFields(t *testing.T) {
},
),
),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
ConnectionString: types.StringNull(),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
//ConnectionString: types.StringNull(),
},
true,
},
@ -338,15 +338,15 @@ func TestMapFields(t *testing.T) {
},
testRegion,
resourceModel{
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
ConnectionString: types.StringNull(),
Id: types.Int64Value(1),
UserId: types.Int64Value(1),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Roles: types.List(types.SetNull(types.StringType)),
Region: types.StringValue(testRegion),
Status: types.StringNull(),
//ConnectionString: types.StringNull(),
},
true,
},

View file

@ -2,10 +2,12 @@ fields:
- name: 'id'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'user_id'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'instance_id'
validators:
@ -13,6 +15,7 @@ fields:
- validate.UUID
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'project_id'
validators:
@ -32,24 +35,30 @@ fields:
- name: 'password'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'host'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'port'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'region'
modifiers:
- 'RequiresReplace'
- 'RequiresReplace'
- name: 'status'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'connection_string'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'

View file

@ -5,6 +5,7 @@ import (
_ "embed"
"fmt"
"math"
"slices"
"strconv"
"strings"
"time"
@ -29,11 +30,12 @@ import (
var (
// Ensure the implementation satisfies the expected interfaces.
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.ResourceWithValidateConfig = &userResource{}
// Error message constants
extractErrorSummary = "extracting failed"
@ -138,6 +140,39 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res
resp.Schema = s
}
func (r *userResource) ValidateConfig(
ctx context.Context,
req resource.ValidateConfigRequest,
resp *resource.ValidateConfigResponse,
) {
var data resourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
var roles []string
diags := data.Roles.ElementsAs(ctx, &roles, false)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
var resRoles []string
for _, role := range roles {
if slices.Contains(resRoles, role) {
resp.Diagnostics.AddAttributeError(
path.Root("roles"),
"Attribute Configuration Error",
"defined roles MUST NOT contain duplicates",
)
return
}
resRoles = append(resRoles, role)
}
}
// Create creates the resource and sets the initial Terraform state.
func (r *userResource) Create(
ctx context.Context,
@ -217,7 +252,6 @@ func (r *userResource) Create(
model.UserId = types.Int64Value(id)
model.Password = types.StringValue(userResp.GetPassword())
model.Status = types.StringValue(userResp.GetStatus())
model.ConnectionString = types.StringValue(userResp.GetConnectionString())
waitResp, err := postgresflexalphaWait.GetUserByIdWaitHandler(
ctx,
@ -712,5 +746,6 @@ func (r *userResource) expandRoles(ctx context.Context, rolesSet types.List, dia
}
var roles []string
diags.Append(rolesSet.ElementsAs(ctx, &roles, false)...)
slices.Sort(roles)
return roles
}

View file

@ -14,11 +14,6 @@ import (
func UserResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"connection_string": schema.StringAttribute{
Computed: true,
Description: "The connection string for the user to the instance.",
MarkdownDescription: "The connection string for the user to the instance.",
},
"id": schema.Int64Attribute{
Computed: true,
Description: "The ID of the user.",
@ -80,14 +75,13 @@ func UserResourceSchema(ctx context.Context) schema.Schema {
}
type UserModel struct {
ConnectionString types.String `tfsdk:"connection_string"`
Id types.Int64 `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Roles types.List `tfsdk:"roles"`
Status types.String `tfsdk:"status"`
UserId types.Int64 `tfsdk:"user_id"`
Id types.Int64 `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Roles types.List `tfsdk:"roles"`
Status types.String `tfsdk:"status"`
UserId types.Int64 `tfsdk:"user_id"`
}

View file

@ -143,7 +143,6 @@ func (d *databaseDataSource) Read(ctx context.Context, req datasource.ReadReques
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
tflog.Info(ctx, "SQL Server Flex beta database read")
}
// handleReadError centralizes API error handling for the Read operation.

View file

@ -36,10 +36,6 @@ var (
// Define errors
errDatabaseNotFound = errors.New("database not found")
// Error message constants
extractErrorSummary = "extracting failed"
extractErrorMessage = "Extracting identity data: %v"
)
func NewDatabaseResource() resource.Resource {
@ -186,26 +182,6 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques
payLoad.Name = data.Name.ValueStringPointer()
payLoad.Owner = data.Owner.ValueStringPointer()
_, err := wait.WaitForUserWaitHandler(
ctx,
r.client,
projectId,
instanceId,
region,
data.Owner.ValueString(),
).
SetSleepBeforeWait(10 * time.Second).
WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
createErr,
fmt.Sprintf("Calling API: %v", err),
)
return
}
createResp, err := r.client.CreateDatabaseRequest(ctx, projectId, region, instanceId).
CreateDatabaseRequestPayload(payLoad).
Execute()
@ -352,24 +328,12 @@ func (r *databaseResource) Read(ctx context.Context, req resource.ReadRequest, r
return
}
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseName, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
instanceId := model.InstanceId.ValueString()
databaseName := model.DatabaseName.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
@ -445,15 +409,10 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseName, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
instanceId := model.InstanceId.ValueString()
databaseName := model.DatabaseName.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
@ -468,7 +427,9 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques
&resp.Diagnostics,
"Error deleting database",
fmt.Sprintf(
"Calling API: %v\nname: %s, region: %s, instanceId: %s", err, databaseName, region, instanceId))
"Calling API: %v\nname: %s, region: %s, instanceId: %s", err, databaseName, region, instanceId,
),
)
return
}
@ -485,7 +446,6 @@ func (r *databaseResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
@ -508,23 +468,6 @@ func (r *databaseResource) ModifyPlan(
return
}
var identityModel DatabaseResourceIdentityModel
identityModel.ProjectID = planModel.ProjectId
identityModel.Region = planModel.Region
if !planModel.InstanceId.IsNull() && !planModel.InstanceId.IsUnknown() {
identityModel.InstanceID = planModel.InstanceId
}
if !planModel.Name.IsNull() && !planModel.Name.IsUnknown() {
identityModel.DatabaseName = planModel.Name
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identityModel)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
@ -548,7 +491,7 @@ func (r *databaseResource) ImportState(
ctx, &resp.Diagnostics,
"Error importing database",
fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id],[database_name] Got: %q",
"Expected import identifier with format [project_id],[region],[instance_id],[database_name] Got: %q",
req.ID,
),
)
@ -594,46 +537,3 @@ func (r *databaseResource) ImportState(
tflog.Info(ctx, "sqlserverflexalpha database state imported")
}
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
func (r *databaseResource) extractIdentityData(
model resourceModel,
identity DatabaseResourceIdentityModel,
) (projectId, region, instanceId, databaseName string, err error) {
if !model.Name.IsNull() && !model.Name.IsUnknown() {
databaseName = model.Name.ValueString()
} else {
if identity.DatabaseName.IsNull() || identity.DatabaseName.IsUnknown() {
return "", "", "", "", fmt.Errorf("database_name not found in config")
}
databaseName = identity.DatabaseName.ValueString()
}
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
projectId = model.ProjectId.ValueString()
} else {
if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
return "", "", "", "", fmt.Errorf("project_id not found in config")
}
projectId = identity.ProjectID.ValueString()
}
if !model.Region.IsNull() && !model.Region.IsUnknown() {
region = r.providerData.GetRegionWithOverride(model.Region)
} else {
if identity.Region.IsNull() || identity.Region.IsUnknown() {
return "", "", "", "", fmt.Errorf("region not found in config")
}
region = r.providerData.GetRegionWithOverride(identity.Region)
}
if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
instanceId = model.InstanceId.ValueString()
} else {
if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() {
return "", "", "", "", fmt.Errorf("instance_id not found in config")
}
instanceId = identity.InstanceID.ValueString()
}
return projectId, region, instanceId, databaseName, nil
}

View file

@ -247,7 +247,6 @@ func toCreatePayload(
conversion.StringValueToPointer(model.Version),
),
}, nil
}
func toUpdatePayload(

View file

@ -54,7 +54,7 @@ type InstanceResourceIdentityModel struct {
}
func (r *instanceResource) Metadata(
ctx context.Context,
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
@ -64,7 +64,7 @@ func (r *instanceResource) Metadata(
//go:embed planModifiers.yaml
var modifiersFileByte []byte
func (r *instanceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := sqlserverflexalphaResGen.InstanceResourceSchema(ctx)
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
@ -147,7 +147,6 @@ func (r *instanceResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
@ -523,7 +522,7 @@ func (r *instanceResource) ImportState(
ctx, &resp.Diagnostics,
"Error importing instance",
fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id] Got: %q",
"Expected import identifier with format [project_id],[region],[instance_id] Got: %q",
req.ID,
),
)

View file

@ -241,9 +241,9 @@ func TestAccInstanceNoEncryption(t *testing.T) {
"##STACKIT_DatabaseManager##",
"##STACKIT_LoginManager##",
"##STACKIT_ProcessManager##",
"##STACKIT_ServerManager##",
"##STACKIT_SQLAgentManager##",
"##STACKIT_SQLAgentUser##",
"##STACKIT_ServerManager##",
},
},
}
@ -288,8 +288,8 @@ func TestAccInstanceNoEncryption(t *testing.T) {
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_version"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.access_scope"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.acl"),
@ -341,7 +341,7 @@ func TestAccInstanceEncryption(t *testing.T) {
{
Name: userName,
ProjectId: os.Getenv("TF_ACC_PROJECT_ID"),
Roles: []string{"##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"},
Roles: []string{"##STACKIT_DatabaseManager##", "##STACKIT_LoginManager##"},
},
}
data.Databases = []Database{
@ -389,8 +389,8 @@ func TestAccInstanceEncryption(t *testing.T) {
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_version"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.access_scope"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.acl"),

View file

@ -20,8 +20,6 @@ import (
var _ datasource.DataSource = (*userDataSource)(nil)
const errorPrefix = "[sqlserverflexalpha - User]"
func NewUserDataSource() datasource.DataSource {
return &userDataSource{}
}

View file

@ -2,6 +2,7 @@ package sqlserverflexalpha
import (
"fmt"
"slices"
"strconv"
"github.com/hashicorp/terraform-plugin-framework/attr"
@ -44,8 +45,11 @@ func mapDataSourceFields(userResp *sqlserverflexalpha.GetUserResponse, model *da
if user.Roles == nil {
model.Roles = types.List(types.SetNull(types.StringType))
} else {
resRoles := *user.Roles
slices.Sort(resRoles)
var roles []attr.Value
for _, role := range *user.Roles {
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@ -92,8 +96,11 @@ func mapFields(userResp *sqlserverflexalpha.GetUserResponse, model *resourceMode
// Map roles
if user.Roles != nil {
resRoles := *user.Roles
slices.Sort(resRoles)
var roles []attr.Value
for _, role := range *user.Roles {
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@ -139,8 +146,11 @@ func mapFieldsCreate(userResp *sqlserverflexalpha.CreateUserResponse, model *res
model.Password = types.StringValue(*user.Password)
if user.Roles != nil {
resRoles := *user.Roles
slices.Sort(resRoles)
var roles []attr.Value
for _, role := range *user.Roles {
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)

View file

@ -43,9 +43,12 @@ func TestMapDataSourceFields(t *testing.T) {
"simple_values",
&sqlserverflexalpha.GetUserResponse{
Roles: &[]string{
"role_1",
"role_2",
"",
"##STACKIT_SQLAgentUser##",
"##STACKIT_DatabaseManager##",
"##STACKIT_LoginManager##",
"##STACKIT_SQLAgentManager##",
"##STACKIT_ProcessManager##",
"##STACKIT_ServerManager##",
},
Username: utils.Ptr("username"),
Host: utils.Ptr("host"),
@ -63,9 +66,12 @@ func TestMapDataSourceFields(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
types.StringValue("##STACKIT_DatabaseManager##"),
types.StringValue("##STACKIT_LoginManager##"),
types.StringValue("##STACKIT_ProcessManager##"),
types.StringValue("##STACKIT_SQLAgentManager##"),
types.StringValue("##STACKIT_SQLAgentUser##"),
types.StringValue("##STACKIT_ServerManager##"),
},
),
),
@ -138,7 +144,7 @@ func TestMapDataSourceFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -183,8 +189,8 @@ func TestMapFieldsCreate(t *testing.T) {
&sqlserverflexalpha.CreateUserResponse{
Id: utils.Ptr(int64(2)),
Roles: &[]string{
"role_1",
"role_2",
"role_1",
"",
},
Username: utils.Ptr("username"),
@ -204,9 +210,9 @@ func TestMapFieldsCreate(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue(""),
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
},
),
),
@ -292,7 +298,7 @@ func TestMapFieldsCreate(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -332,8 +338,8 @@ func TestMapFields(t *testing.T) {
"simple_values",
&sqlserverflexalpha.GetUserResponse{
Roles: &[]string{
"role_1",
"role_2",
"role_1",
"",
},
Username: utils.Ptr("username"),
@ -350,9 +356,9 @@ func TestMapFields(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue(""),
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
},
),
),
@ -423,7 +429,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -516,7 +522,7 @@ func TestToCreatePayload(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected)
diff := cmp.Diff(tt.expected, output)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"
@ -19,7 +20,6 @@ import (
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
sqlserverflexalphagen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/user/resources_gen"
sqlserverflexalphaUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
sqlserverflexalphaWait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/sqlserverflexalpha"
@ -30,11 +30,12 @@ import (
)
var (
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.ResourceWithValidateConfig = &userResource{}
)
func NewUserResource() resource.Resource {
@ -57,7 +58,7 @@ type userResource struct {
providerData core.ProviderData
}
func (r *userResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
func (r *userResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_sqlserverflexalpha_user"
}
@ -116,7 +117,7 @@ var modifiersFileByte []byte
// Schema defines the schema for the resource.
func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := sqlserverflexalphagen.UserResourceSchema(ctx)
s := sqlserverflexalphaResGen.UserResourceSchema(ctx)
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
@ -156,6 +157,39 @@ func (r *userResource) IdentitySchema(
}
}
func (r *userResource) ValidateConfig(
ctx context.Context,
req resource.ValidateConfigRequest,
resp *resource.ValidateConfigResponse,
) {
var data resourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
var roles []string
diags := data.Roles.ElementsAs(ctx, &roles, false)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
var resRoles []string
for _, role := range roles {
if slices.Contains(resRoles, role) {
resp.Diagnostics.AddAttributeError(
path.Root("roles"),
"Attribute Configuration Error",
"defined roles MUST NOT contain duplicates",
)
return
}
resRoles = append(resRoles, role)
}
}
// Create creates the resource and sets the initial Terraform state.
func (r *userResource) Create(
ctx context.Context,
@ -186,6 +220,8 @@ func (r *userResource) Create(
if resp.Diagnostics.HasError() {
return
}
slices.Sort(roles)
}
// Generate API request body from model
@ -433,7 +469,6 @@ func (r *userResource) Delete(
// Delete existing record set
_, err = sqlserverflexalphaWait.DeleteUserWaitHandler(ctx, r.client, projectId, region, instanceId, userId).
WaitWithContext(ctx)
// err := r.client.DeleteUserRequest(ctx, arg.projectId, arg.region, arg.instanceId, userId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "User Delete Error", fmt.Sprintf("Calling API: %v", err))
return

View file

@ -0,0 +1,569 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package sqlserverflexalpha
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func VersionDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
Required: true,
Description: "The STACKIT project ID.",
MarkdownDescription: "The STACKIT project ID.",
},
"region": schema.StringAttribute{
Required: true,
Description: "The region which should be addressed",
MarkdownDescription: "The region which should be addressed",
Validators: []validator.String{
stringvalidator.OneOf(
"eu01",
),
},
},
"versions": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"beta": schema.BoolAttribute{
Computed: true,
Description: "Flag if the version is a beta version. If set the version may contain bugs and is not fully tested.",
MarkdownDescription: "Flag if the version is a beta version. If set the version may contain bugs and is not fully tested.",
},
"deprecated": schema.StringAttribute{
Computed: true,
Description: "Timestamp in RFC3339 format which says when the version will no longer be supported by STACKIT.",
MarkdownDescription: "Timestamp in RFC3339 format which says when the version will no longer be supported by STACKIT.",
},
"recommend": schema.BoolAttribute{
Computed: true,
Description: "Flag if the version is recommend by the STACKIT Team.",
MarkdownDescription: "Flag if the version is recommend by the STACKIT Team.",
},
"version": schema.StringAttribute{
Computed: true,
Description: "The sqlserver version used for the instance.",
MarkdownDescription: "The sqlserver version used for the instance.",
},
},
CustomType: VersionsType{
ObjectType: types.ObjectType{
AttrTypes: VersionsValue{}.AttributeTypes(ctx),
},
},
},
Computed: true,
Description: "A list containing available sqlserver versions.",
MarkdownDescription: "A list containing available sqlserver versions.",
},
},
}
}
type VersionModel struct {
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Versions types.List `tfsdk:"versions"`
}
var _ basetypes.ObjectTypable = VersionsType{}
type VersionsType struct {
basetypes.ObjectType
}
func (t VersionsType) Equal(o attr.Type) bool {
other, ok := o.(VersionsType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t VersionsType) String() string {
return "VersionsType"
}
func (t VersionsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
betaAttribute, ok := attributes["beta"]
if !ok {
diags.AddError(
"Attribute Missing",
`beta is missing from object`)
return nil, diags
}
betaVal, ok := betaAttribute.(basetypes.BoolValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`beta expected to be basetypes.BoolValue, was: %T`, betaAttribute))
}
deprecatedAttribute, ok := attributes["deprecated"]
if !ok {
diags.AddError(
"Attribute Missing",
`deprecated is missing from object`)
return nil, diags
}
deprecatedVal, ok := deprecatedAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`deprecated expected to be basetypes.StringValue, was: %T`, deprecatedAttribute))
}
recommendAttribute, ok := attributes["recommend"]
if !ok {
diags.AddError(
"Attribute Missing",
`recommend is missing from object`)
return nil, diags
}
recommendVal, ok := recommendAttribute.(basetypes.BoolValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`recommend expected to be basetypes.BoolValue, was: %T`, recommendAttribute))
}
versionAttribute, ok := attributes["version"]
if !ok {
diags.AddError(
"Attribute Missing",
`version is missing from object`)
return nil, diags
}
versionVal, ok := versionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`version expected to be basetypes.StringValue, was: %T`, versionAttribute))
}
if diags.HasError() {
return nil, diags
}
return VersionsValue{
Beta: betaVal,
Deprecated: deprecatedVal,
Recommend: recommendVal,
Version: versionVal,
state: attr.ValueStateKnown,
}, diags
}
func NewVersionsValueNull() VersionsValue {
return VersionsValue{
state: attr.ValueStateNull,
}
}
func NewVersionsValueUnknown() VersionsValue {
return VersionsValue{
state: attr.ValueStateUnknown,
}
}
func NewVersionsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (VersionsValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing VersionsValue Attribute Value",
"While creating a VersionsValue value, a missing attribute value was detected. "+
"A VersionsValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("VersionsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid VersionsValue Attribute Type",
"While creating a VersionsValue value, an invalid attribute value was detected. "+
"A VersionsValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("VersionsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("VersionsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra VersionsValue Attribute Value",
"While creating a VersionsValue value, an extra attribute value was detected. "+
"A VersionsValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra VersionsValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewVersionsValueUnknown(), diags
}
betaAttribute, ok := attributes["beta"]
if !ok {
diags.AddError(
"Attribute Missing",
`beta is missing from object`)
return NewVersionsValueUnknown(), diags
}
betaVal, ok := betaAttribute.(basetypes.BoolValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`beta expected to be basetypes.BoolValue, was: %T`, betaAttribute))
}
deprecatedAttribute, ok := attributes["deprecated"]
if !ok {
diags.AddError(
"Attribute Missing",
`deprecated is missing from object`)
return NewVersionsValueUnknown(), diags
}
deprecatedVal, ok := deprecatedAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`deprecated expected to be basetypes.StringValue, was: %T`, deprecatedAttribute))
}
recommendAttribute, ok := attributes["recommend"]
if !ok {
diags.AddError(
"Attribute Missing",
`recommend is missing from object`)
return NewVersionsValueUnknown(), diags
}
recommendVal, ok := recommendAttribute.(basetypes.BoolValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`recommend expected to be basetypes.BoolValue, was: %T`, recommendAttribute))
}
versionAttribute, ok := attributes["version"]
if !ok {
diags.AddError(
"Attribute Missing",
`version is missing from object`)
return NewVersionsValueUnknown(), diags
}
versionVal, ok := versionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`version expected to be basetypes.StringValue, was: %T`, versionAttribute))
}
if diags.HasError() {
return NewVersionsValueUnknown(), diags
}
return VersionsValue{
Beta: betaVal,
Deprecated: deprecatedVal,
Recommend: recommendVal,
Version: versionVal,
state: attr.ValueStateKnown,
}, diags
}
func NewVersionsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) VersionsValue {
object, diags := NewVersionsValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewVersionsValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t VersionsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewVersionsValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewVersionsValueUnknown(), nil
}
if in.IsNull() {
return NewVersionsValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewVersionsValueMust(VersionsValue{}.AttributeTypes(ctx), attributes), nil
}
func (t VersionsType) ValueType(ctx context.Context) attr.Value {
return VersionsValue{}
}
var _ basetypes.ObjectValuable = VersionsValue{}
type VersionsValue struct {
Beta basetypes.BoolValue `tfsdk:"beta"`
Deprecated basetypes.StringValue `tfsdk:"deprecated"`
Recommend basetypes.BoolValue `tfsdk:"recommend"`
Version basetypes.StringValue `tfsdk:"version"`
state attr.ValueState
}
func (v VersionsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 4)
var val tftypes.Value
var err error
attrTypes["beta"] = basetypes.BoolType{}.TerraformType(ctx)
attrTypes["deprecated"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["recommend"] = basetypes.BoolType{}.TerraformType(ctx)
attrTypes["version"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 4)
val, err = v.Beta.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["beta"] = val
val, err = v.Deprecated.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["deprecated"] = val
val, err = v.Recommend.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["recommend"] = val
val, err = v.Version.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["version"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v VersionsValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v VersionsValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v VersionsValue) String() string {
return "VersionsValue"
}
func (v VersionsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"beta": basetypes.BoolType{},
"deprecated": basetypes.StringType{},
"recommend": basetypes.BoolType{},
"version": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"beta": v.Beta,
"deprecated": v.Deprecated,
"recommend": v.Recommend,
"version": v.Version,
})
return objVal, diags
}
func (v VersionsValue) Equal(o attr.Value) bool {
other, ok := o.(VersionsValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Beta.Equal(other.Beta) {
return false
}
if !v.Deprecated.Equal(other.Deprecated) {
return false
}
if !v.Recommend.Equal(other.Recommend) {
return false
}
if !v.Version.Equal(other.Version) {
return false
}
return true
}
func (v VersionsValue) Type(ctx context.Context) attr.Type {
return VersionsType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v VersionsValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"beta": basetypes.BoolType{},
"deprecated": basetypes.StringType{},
"recommend": basetypes.BoolType{},
"version": basetypes.StringType{},
}
}

View file

@ -143,7 +143,6 @@ func (d *databaseDataSource) Read(ctx context.Context, req datasource.ReadReques
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
tflog.Info(ctx, "SQL Server Flex beta database read")
}
// handleReadError centralizes API error handling for the Read operation.

View file

@ -2,7 +2,6 @@ package sqlserverflexbeta
import (
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework/types"
@ -34,7 +33,7 @@ func mapFields(source *sqlserverflexbeta.GetDatabaseResponse, model *dataSourceM
model.Id = types.Int64Value(databaseId)
model.DatabaseName = types.StringValue(source.GetName())
model.Name = types.StringValue(source.GetName())
model.Owner = types.StringValue(strings.Trim(source.GetOwner(), "\""))
model.Owner = types.StringValue(source.GetOwner())
model.Region = types.StringValue(region)
model.ProjectId = types.StringValue(model.ProjectId.ValueString())
model.InstanceId = types.StringValue(model.InstanceId.ValueString())
@ -75,7 +74,7 @@ func mapResourceFields(source *sqlserverflexbeta.GetDatabaseResponse, model *res
model.Id = types.Int64Value(databaseId)
model.DatabaseName = types.StringValue(source.GetName())
model.Name = types.StringValue(source.GetName())
model.Owner = types.StringValue(strings.Trim(source.GetOwner(), "\""))
model.Owner = types.StringValue(source.GetOwner())
model.Region = types.StringValue(region)
model.ProjectId = types.StringValue(model.ProjectId.ValueString())
model.InstanceId = types.StringValue(model.InstanceId.ValueString())

View file

@ -2,6 +2,7 @@ fields:
- name: 'id'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'instance_id'
validators:
@ -32,13 +33,16 @@ fields:
- name: 'owner'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'database_name'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'collation_name'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'compatibility'
@ -49,3 +53,4 @@ fields:
- name: 'compatibility_level'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'

View file

@ -36,10 +36,6 @@ var (
// Define errors
errDatabaseNotFound = errors.New("database not found")
// Error message constants
extractErrorSummary = "extracting failed"
extractErrorMessage = "Extracting identity data: %v"
)
func NewDatabaseResource() resource.Resource {
@ -247,7 +243,6 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques
return
}
// TODO: is this necessary to wait for the database-> API say 200 ?
waitResp, err := wait.CreateDatabaseWaitHandler(
ctx,
r.client,
@ -310,19 +305,8 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques
return
}
database, err := r.client.GetDatabaseRequest(ctx, projectId, region, instanceId, databaseName).Execute()
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating database",
fmt.Sprintf("Getting database details after creation: %v", err),
)
return
}
// Map response body to schema
err = mapResourceFields(database, &data, region)
err = mapResourceFields(waitResp, &data, region)
if err != nil {
core.LogAndAddError(
ctx,
@ -352,24 +336,12 @@ func (r *databaseResource) Read(ctx context.Context, req resource.ReadRequest, r
return
}
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseName, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
instanceId := model.InstanceId.ValueString()
databaseName := model.DatabaseName.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
@ -424,7 +396,7 @@ func (r *databaseResource) Read(ctx context.Context, req resource.ReadRequest, r
func (r *databaseResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) {
// TODO: Check update api endpoint - not available at the moment, so return an error for now
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating database", "Database can't be updated")
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating database", "there is no way to update a database")
}
func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
@ -436,24 +408,10 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques
return
}
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId, region, instanceId, databaseName, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
instanceId := model.InstanceId.ValueString()
databaseName := model.DatabaseName.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
@ -468,10 +426,14 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques
&resp.Diagnostics,
"Error deleting database",
fmt.Sprintf(
"Calling API: %v\nname: %s, region: %s, instanceId: %s", err, databaseName, region, instanceId))
"Calling API: %v\nname: %s, region: %s, instanceId: %s", err, databaseName, region, instanceId,
),
)
return
}
// TODO: wait handler??
ctx = core.LogResponse(ctx)
resp.State.RemoveResource(ctx)
@ -485,7 +447,6 @@ func (r *databaseResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
@ -548,7 +509,7 @@ func (r *databaseResource) ImportState(
ctx, &resp.Diagnostics,
"Error importing database",
fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id],[database_name] Got: %q",
"Expected import identifier with format [project_id],[region],[instance_id],[database_name] Got: %q",
req.ID,
),
)
@ -595,45 +556,4 @@ func (r *databaseResource) ImportState(
tflog.Info(ctx, "Sqlserverflexbeta database state imported")
}
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
func (r *databaseResource) extractIdentityData(
model resourceModel,
identity DatabaseResourceIdentityModel,
) (projectId, region, instanceId, databaseName string, err error) {
if !model.Name.IsNull() && !model.Name.IsUnknown() {
databaseName = model.Name.ValueString()
} else {
if identity.DatabaseName.IsNull() || identity.DatabaseName.IsUnknown() {
return "", "", "", "", fmt.Errorf("database_name not found in config")
}
databaseName = identity.DatabaseName.ValueString()
}
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
projectId = model.ProjectId.ValueString()
} else {
if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
return "", "", "", "", fmt.Errorf("project_id not found in config")
}
projectId = identity.ProjectID.ValueString()
}
if !model.Region.IsNull() && !model.Region.IsUnknown() {
region = r.providerData.GetRegionWithOverride(model.Region)
} else {
if identity.Region.IsNull() || identity.Region.IsUnknown() {
return "", "", "", "", fmt.Errorf("region not found in config")
}
region = r.providerData.GetRegionWithOverride(identity.Region)
}
if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
instanceId = model.InstanceId.ValueString()
} else {
if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() {
return "", "", "", "", fmt.Errorf("instance_id not found in config")
}
instanceId = identity.InstanceID.ValueString()
}
return projectId, region, instanceId, databaseName, nil
}
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity mode

View file

@ -25,7 +25,7 @@ func mapResponseToModel(
) error {
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Edition = types.StringValue(string(resp.GetEdition()))
m.Encryption = handleEncryption(m, resp)
m.Encryption = handleEncryption(ctx, m, resp)
m.FlavorId = types.StringValue(resp.GetFlavorId())
m.Id = types.StringValue(resp.GetId())
m.InstanceId = types.StringValue(resp.GetId())
@ -81,7 +81,7 @@ func mapDataResponseToModel(
) error {
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Edition = types.StringValue(string(resp.GetEdition()))
m.Encryption = handleDSEncryption(m, resp)
m.Encryption = handleDSEncryption(ctx, m, resp)
m.FlavorId = types.StringValue(resp.GetFlavorId())
m.Id = types.StringValue(resp.GetId())
m.InstanceId = types.StringValue(resp.GetId())
@ -130,6 +130,7 @@ func mapDataResponseToModel(
}
func handleEncryption(
ctx context.Context,
m *sqlserverflexbetaResGen.InstanceModel,
resp *sqlserverflexbeta.GetInstanceResponse,
) sqlserverflexbetaResGen.EncryptionValue {
@ -145,23 +146,20 @@ func handleEncryption(
return m.Encryption
}
enc := sqlserverflexbetaResGen.NewEncryptionValueNull()
if kVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
enc.KekKeyId = types.StringValue(kVal)
}
if kkVal, ok := resp.Encryption.GetKekKeyRingIdOk(); ok {
enc.KekKeyRingId = types.StringValue(kkVal)
}
if kkvVal, ok := resp.Encryption.GetKekKeyVersionOk(); ok {
enc.KekKeyVersion = types.StringValue(kkvVal)
}
if sa, ok := resp.Encryption.GetServiceAccountOk(); ok {
enc.ServiceAccount = types.StringValue(sa)
}
enc := sqlserverflexbetaResGen.NewEncryptionValueMust(
sqlserverflexbetaResGen.EncryptionValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"kek_key_id": types.StringValue(resp.Encryption.GetKekKeyId()),
"kek_key_ring_id": types.StringValue(resp.Encryption.GetKekKeyRingId()),
"kek_key_version": types.StringValue(resp.Encryption.GetKekKeyVersion()),
"service_account": types.StringValue(resp.Encryption.GetServiceAccount()),
},
)
return enc
}
func handleDSEncryption(
ctx context.Context,
m *dataSourceModel,
resp *sqlserverflexbeta.GetInstanceResponse,
) sqlserverflexbetaDataGen.EncryptionValue {
@ -177,19 +175,15 @@ func handleDSEncryption(
return m.Encryption
}
enc := sqlserverflexbetaDataGen.NewEncryptionValueNull()
if kVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
enc.KekKeyId = types.StringValue(kVal)
}
if kkVal, ok := resp.Encryption.GetKekKeyRingIdOk(); ok {
enc.KekKeyRingId = types.StringValue(kkVal)
}
if kkvVal, ok := resp.Encryption.GetKekKeyVersionOk(); ok {
enc.KekKeyVersion = types.StringValue(kkvVal)
}
if sa, ok := resp.Encryption.GetServiceAccountOk(); ok {
enc.ServiceAccount = types.StringValue(sa)
}
enc := sqlserverflexbetaDataGen.NewEncryptionValueMust(
sqlserverflexbetaDataGen.EncryptionValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"kek_key_id": types.StringValue(resp.Encryption.GetKekKeyId()),
"kek_key_ring_id": types.StringValue(resp.Encryption.GetKekKeyRingId()),
"kek_key_version": types.StringValue(resp.Encryption.GetKekKeyVersion()),
"service_account": types.StringValue(resp.Encryption.GetServiceAccount()),
},
)
return enc
}
@ -208,17 +202,12 @@ func toCreatePayload(
}
var encryptionPayload *sqlserverflexbeta.CreateInstanceRequestPayloadGetEncryptionArgType = nil
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() &&
!model.Encryption.KekKeyId.IsNull() && model.Encryption.KekKeyId.IsUnknown() && model.Encryption.KekKeyId.ValueString() != "" &&
!model.Encryption.KekKeyRingId.IsNull() && !model.Encryption.KekKeyRingId.IsUnknown() && model.Encryption.KekKeyRingId.ValueString() != "" &&
!model.Encryption.KekKeyVersion.IsNull() && !model.Encryption.KekKeyVersion.IsUnknown() && model.Encryption.KekKeyVersion.ValueString() != "" &&
!model.Encryption.ServiceAccount.IsNull() && !model.Encryption.ServiceAccount.IsUnknown() && model.Encryption.ServiceAccount.ValueString() != "" {
encryptionPayload = &sqlserverflexbeta.CreateInstanceRequestPayloadGetEncryptionArgType{
KekKeyId: model.Encryption.KekKeyId.ValueStringPointer(),
KekKeyRingId: model.Encryption.KekKeyVersion.ValueStringPointer(),
KekKeyVersion: model.Encryption.KekKeyRingId.ValueStringPointer(),
ServiceAccount: model.Encryption.ServiceAccount.ValueStringPointer(),
}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
encryptionPayload = &sqlserverflexbeta.CreateInstanceRequestPayloadGetEncryptionArgType{}
encryptionPayload.KekKeyId = model.Encryption.KekKeyId.ValueStringPointer()
encryptionPayload.KekKeyRingId = model.Encryption.KekKeyRingId.ValueStringPointer()
encryptionPayload.KekKeyVersion = model.Encryption.KekKeyVersion.ValueStringPointer()
encryptionPayload.ServiceAccount = model.Encryption.ServiceAccount.ValueStringPointer()
}
networkPayload := &sqlserverflexbeta.CreateInstanceRequestPayloadGetNetworkArgType{}
@ -247,7 +236,6 @@ func toCreatePayload(
conversion.StringValueToPointer(model.Version),
),
}, nil
}
func toUpdatePayload(

View file

@ -0,0 +1,273 @@
package sqlserverflexbeta
import (
"context"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
sqlserverflexbetaPkgGen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexbeta"
sqlserverflexbetaRs "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/instance/resources_gen"
)
func Test_handleDSEncryption(t *testing.T) {
type args struct {
m *dataSourceModel
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
}
tests := []struct {
name string
args args
want sqlserverflexbetaRs.EncryptionValue
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if got := handleDSEncryption(t.Context(), tt.args.m, tt.args.resp); !reflect.DeepEqual(got, tt.want) {
t.Errorf("handleDSEncryption() = %v, want %v", got, tt.want)
}
},
)
}
}
func Test_handleEncryption(t *testing.T) {
type args struct {
m *sqlserverflexbetaRs.InstanceModel
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
}
tests := []struct {
name string
args args
want sqlserverflexbetaRs.EncryptionValue
}{
{
name: "nil response",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{},
},
want: sqlserverflexbetaRs.EncryptionValue{},
},
{
name: "nil response",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{},
},
},
want: sqlserverflexbetaRs.NewEncryptionValueNull(),
},
{
name: "response with values",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{
KekKeyId: utils.Ptr("kek_key_id"),
KekKeyRingId: utils.Ptr("kek_key_ring_id"),
KekKeyVersion: utils.Ptr("kek_key_version"),
ServiceAccount: utils.Ptr("kek_svc_acc"),
},
},
},
want: sqlserverflexbetaRs.EncryptionValue{
KekKeyId: types.StringValue("kek_key_id"),
KekKeyRingId: types.StringValue("kek_key_ring_id"),
KekKeyVersion: types.StringValue("kek_key_version"),
ServiceAccount: types.StringValue("kek_svc_acc"),
},
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if got := handleEncryption(t.Context(), tt.args.m, tt.args.resp); !reflect.DeepEqual(got, tt.want) {
t.Errorf("handleEncryption() = %v, want %v", got, tt.want)
}
},
)
}
}
func Test_mapDataResponseToModel(t *testing.T) {
type args struct {
ctx context.Context
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
m *dataSourceModel
tfDiags diag.Diagnostics
}
tests := []struct {
name string
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if err := mapDataResponseToModel(
tt.args.ctx,
tt.args.resp,
tt.args.m,
tt.args.tfDiags,
); (err != nil) != tt.wantErr {
t.Errorf("mapDataResponseToModel() error = %v, wantErr %v", err, tt.wantErr)
}
},
)
}
}
func Test_mapResponseToModel(t *testing.T) {
type args struct {
ctx context.Context
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
m *sqlserverflexbetaRs.InstanceModel
tfDiags diag.Diagnostics
}
tests := []struct {
name string
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if err := mapResponseToModel(
tt.args.ctx,
tt.args.resp,
tt.args.m,
tt.args.tfDiags,
); (err != nil) != tt.wantErr {
t.Errorf("mapResponseToModel() error = %v, wantErr %v", err, tt.wantErr)
}
},
)
}
}
func Test_toCreatePayload(t *testing.T) {
type args struct {
ctx context.Context
model *sqlserverflexbetaRs.InstanceModel
}
tests := []struct {
name string
args args
want *sqlserverflexbetaPkgGen.CreateInstanceRequestPayload
wantErr bool
}{
{
name: "simple",
args: args{
ctx: context.Background(),
model: &sqlserverflexbetaRs.InstanceModel{
Encryption: sqlserverflexbetaRs.NewEncryptionValueMust(
sqlserverflexbetaRs.EncryptionValue{}.AttributeTypes(context.Background()),
map[string]attr.Value{
"kek_key_id": types.StringValue("kek_key_id"),
"kek_key_ring_id": types.StringValue("kek_key_ring_id"),
"kek_key_version": types.StringValue("kek_key_version"),
"service_account": types.StringValue("sacc"),
},
),
Storage: sqlserverflexbetaRs.StorageValue{},
},
},
want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
BackupSchedule: nil,
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{
KekKeyId: utils.Ptr("kek_key_id"),
KekKeyRingId: utils.Ptr("kek_key_ring_id"),
KekKeyVersion: utils.Ptr("kek_key_version"),
ServiceAccount: utils.Ptr("sacc"),
},
FlavorId: nil,
Name: nil,
Network: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
RetentionDays: nil,
Storage: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadGetStorageArgType{},
Version: nil,
},
wantErr: false,
},
{
name: "nil object",
args: args{
ctx: context.Background(),
model: &sqlserverflexbetaRs.InstanceModel{
Encryption: sqlserverflexbetaRs.NewEncryptionValueNull(),
Storage: sqlserverflexbetaRs.StorageValue{},
},
},
want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
BackupSchedule: nil,
Encryption: nil,
FlavorId: nil,
Name: nil,
Network: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
RetentionDays: nil,
Storage: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadGetStorageArgType{},
Version: nil,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got, err := toCreatePayload(tt.args.ctx, tt.args.model)
if (err != nil) != tt.wantErr {
t.Errorf("toCreatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("model mismatch (-want +got):\n%s", diff)
}
},
)
}
}
func Test_toUpdatePayload(t *testing.T) {
type args struct {
ctx context.Context
m *sqlserverflexbetaRs.InstanceModel
resp *resource.UpdateResponse
}
tests := []struct {
name string
args args
want *sqlserverflexbetaPkgGen.UpdateInstanceRequestPayload
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got, err := toUpdatePayload(tt.args.ctx, tt.args.m, tt.args.resp)
if (err != nil) != tt.wantErr {
t.Errorf("toUpdatePayload() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("toUpdatePayload() got = %v, want %v", got, tt.want)
}
},
)
}
}

View file

@ -54,7 +54,7 @@ type InstanceResourceIdentityModel struct {
}
func (r *instanceResource) Metadata(
ctx context.Context,
_ context.Context,
req resource.MetadataRequest,
resp *resource.MetadataResponse,
) {
@ -64,7 +64,7 @@ func (r *instanceResource) Metadata(
//go:embed planModifiers.yaml
var modifiersFileByte []byte
func (r *instanceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := sqlserverflexbetaResGen.InstanceResourceSchema(ctx)
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
@ -147,7 +147,6 @@ func (r *instanceResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
@ -207,6 +206,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
)
return
}
// Create new Instance
createResp, err := r.client.CreateInstanceRequest(
ctx,
@ -293,13 +293,6 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
return
}
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := data.ProjectId.ValueString()
@ -523,7 +516,7 @@ func (r *instanceResource) ImportState(
ctx, &resp.Diagnostics,
"Error importing instance",
fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id] Got: %q",
"Expected import identifier with format [project_id],[region],[instance_id] Got: %q",
req.ID,
),
)

View file

@ -15,7 +15,7 @@ import (
"github.com/stackitcloud/stackit-sdk-go/core/config"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/internal/testutils"
sqlserverflexbeta2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexbeta"
sqlserverflexbetaResGen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexbeta"
sqlserverflexbeta "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/instance"
// The fwresource import alias is so there is no collision
@ -36,7 +36,7 @@ func init() {
F: func(region string) error {
ctx := context.Background()
apiClientConfigOptions := []config.ConfigurationOption{}
apiClient, err := sqlserverflexbeta2.NewAPIClient(apiClientConfigOptions...)
apiClient, err := sqlserverflexbetaResGen.NewAPIClient(apiClientConfigOptions...)
if err != nil {
log.Fatalln(err)
}
@ -228,6 +228,67 @@ func TestAccInstance(t *testing.T) {
})
}
func TestAccInstanceReApply(t *testing.T) {
exData := getExample()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
t.Logf(" ... working on instance %s", exData.TfName)
testInstances = append(testInstances, exData.TfName)
},
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
exData,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resName("instance", exData.TfName), "name", exData.Name),
resource.TestCheckResourceAttrSet(resName("instance", exData.TfName), "id"),
// TODO: check all fields
),
},
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
exData,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resName("instance", exData.TfName), "name", exData.Name),
resource.TestCheckResourceAttrSet(resName("instance", exData.TfName), "id"),
// TODO: check all fields
),
},
{
RefreshState: true,
},
// Create and verify
{
Config: testutils.StringFromTemplateMust(
"testdata/instance_template.gompl",
exData,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resName("instance", exData.TfName), "name", exData.Name),
resource.TestCheckResourceAttrSet(resName("instance", exData.TfName), "id"),
// TODO: check all fields
),
},
// Import test
{
ResourceName: resName("instance", exData.TfName),
ImportStateKind: resource.ImportBlockWithResourceIdentity,
ImportState: true,
// ImportStateVerify is not supported with plannable import blocks
// ImportStateVerify: true,
},
},
})
}
func TestAccInstanceNoEncryption(t *testing.T) {
data := getExample()
@ -241,9 +302,9 @@ func TestAccInstanceNoEncryption(t *testing.T) {
"##STACKIT_DatabaseManager##",
"##STACKIT_LoginManager##",
"##STACKIT_ProcessManager##",
"##STACKIT_ServerManager##",
"##STACKIT_SQLAgentManager##",
"##STACKIT_SQLAgentUser##",
"##STACKIT_ServerManager##",
},
},
}
@ -288,8 +349,8 @@ func TestAccInstanceNoEncryption(t *testing.T) {
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_version"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.access_scope"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.acl"),
@ -341,7 +402,7 @@ func TestAccInstanceEncryption(t *testing.T) {
{
Name: userName,
ProjectId: os.Getenv("TF_ACC_PROJECT_ID"),
Roles: []string{"##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"},
Roles: []string{"##STACKIT_DatabaseManager##", "##STACKIT_LoginManager##"},
},
}
data.Databases = []Database{
@ -389,8 +450,8 @@ func TestAccInstanceEncryption(t *testing.T) {
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_version"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
//resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.kek_key_ring_id"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "encryption.service_account"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.access_scope"),
// resource.TestCheckResourceAttrSet(resName("instance", data.TfName), "network.acl"),

View file

@ -20,8 +20,6 @@ import (
var _ datasource.DataSource = (*userDataSource)(nil)
const errorPrefix = "[Sqlserverflexbeta - User]"
func NewUserDataSource() datasource.DataSource {
return &userDataSource{}
}

View file

@ -2,6 +2,7 @@ package sqlserverflexbeta
import (
"fmt"
"slices"
"strconv"
"github.com/hashicorp/terraform-plugin-framework/attr"
@ -45,7 +46,9 @@ func mapDataSourceFields(userResp *sqlserverflexbeta.GetUserResponse, model *dat
model.Roles = types.List(types.SetNull(types.StringType))
} else {
var roles []attr.Value
for _, role := range *user.Roles {
resRoles := *user.Roles
slices.Sort(resRoles)
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@ -91,16 +94,20 @@ func mapFields(userResp *sqlserverflexbeta.GetUserResponse, model *resourceModel
model.Username = types.StringPointerValue(user.Username)
// Map roles
if user.Roles != nil {
if userResp.Roles != nil {
resRoles := *userResp.Roles
slices.Sort(resRoles)
var roles []attr.Value
for _, role := range *user.Roles {
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
rolesSet, diags := types.ListValue(types.StringType, roles)
if diags.HasError() {
return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags))
}
model.Roles = types.List(rolesSet)
model.Roles = rolesSet
}
// Ensure roles is not null
@ -139,15 +146,18 @@ func mapFieldsCreate(userResp *sqlserverflexbeta.CreateUserResponse, model *reso
model.Password = types.StringValue(*user.Password)
if user.Roles != nil {
resRoles := *user.Roles
slices.Sort(resRoles)
var roles []attr.Value
for _, role := range *user.Roles {
for _, role := range resRoles {
roles = append(roles, types.StringValue(string(role)))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
rolesList, diags := types.ListValue(types.StringType, roles)
if diags.HasError() {
return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags))
}
model.Roles = types.List(rolesSet)
model.Roles = rolesList
}
if model.Roles.IsNull() || model.Roles.IsUnknown() {
@ -175,9 +185,14 @@ func toCreatePayload(
return nil, fmt.Errorf("nil model")
}
return &sqlserverflexbeta.CreateUserRequestPayload{
Username: conversion.StringValueToPointer(model.Username),
DefaultDatabase: conversion.StringValueToPointer(model.DefaultDatabase),
Roles: &roles,
}, nil
pl := sqlserverflexbeta.CreateUserRequestPayload{
Username: conversion.StringValueToPointer(model.Username),
Roles: &roles,
}
slices.Sort(roles)
if !model.DefaultDatabase.IsNull() || !model.DefaultDatabase.IsUnknown() {
pl.DefaultDatabase = conversion.StringValueToPointer(model.DefaultDatabase)
}
return &pl, nil
}

View file

@ -63,9 +63,9 @@ func TestMapDataSourceFields(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue(""),
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
},
),
),
@ -138,7 +138,7 @@ func TestMapDataSourceFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -204,9 +204,9 @@ func TestMapFieldsCreate(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue(""),
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
},
),
),
@ -292,7 +292,7 @@ func TestMapFieldsCreate(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -332,8 +332,8 @@ func TestMapFields(t *testing.T) {
"simple_values",
&sqlserverflexbeta.GetUserResponse{
Roles: &[]string{
"role_1",
"role_2",
"role_1",
"",
},
Username: utils.Ptr("username"),
@ -350,9 +350,9 @@ func TestMapFields(t *testing.T) {
Roles: types.List(
types.SetValueMust(
types.StringType, []attr.Value{
types.StringValue(""),
types.StringValue("role_1"),
types.StringValue("role_2"),
types.StringValue(""),
},
),
),
@ -423,7 +423,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(&tt.expected, state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}

View file

@ -2,6 +2,7 @@ fields:
- name: 'id'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'instance_id'
validators:
@ -22,6 +23,7 @@ fields:
- name: 'region'
modifiers:
- 'RequiresReplace'
- 'RequiresReplace'
- name: 'user_id'
modifiers:
@ -31,10 +33,12 @@ fields:
- name: 'username'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'roles'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'password'
modifiers:

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"
@ -19,7 +20,6 @@ import (
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexbeta"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
sqlserverflexbetagen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/user/resources_gen"
sqlserverflexbetaUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/utils"
sqlserverflexbetaWait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/sqlserverflexbeta"
@ -30,11 +30,12 @@ import (
)
var (
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
_ resource.ResourceWithIdentity = &userResource{}
_ resource.ResourceWithValidateConfig = &userResource{}
)
func NewUserResource() resource.Resource {
@ -57,7 +58,7 @@ type userResource struct {
providerData core.ProviderData
}
func (r *userResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
func (r *userResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_sqlserverflexbeta_user"
}
@ -105,6 +106,26 @@ func (r *userResource) ModifyPlan(
return
}
//// TODO: verify if this is needed - START
// var planRoles []string
// diags := planModel.Roles.ElementsAs(ctx, &planRoles, false)
// resp.Diagnostics.Append(diags...)
// if diags.HasError() {
// return
//}
// slices.Sort(planRoles)
// var roles []attr.Value
// for _, role := range planRoles {
// roles = append(roles, types.StringValue(string(role)))
//}
// rolesSet, diags := types.ListValue(types.StringType, roles)
// resp.Diagnostics.Append(diags...)
// if diags.HasError() {
// return
//}
// planModel.Roles = rolesSet
//// TODO: verify if this is needed - END
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
@ -116,7 +137,7 @@ var modifiersFileByte []byte
// Schema defines the schema for the resource.
func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := sqlserverflexbetagen.UserResourceSchema(ctx)
s := sqlserverflexbetaResGen.UserResourceSchema(ctx)
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
@ -156,6 +177,39 @@ func (r *userResource) IdentitySchema(
}
}
func (r *userResource) ValidateConfig(
ctx context.Context,
req resource.ValidateConfigRequest,
resp *resource.ValidateConfigResponse,
) {
var data resourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
var roles []string
diags := data.Roles.ElementsAs(ctx, &roles, false)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
var resRoles []string
for _, role := range roles {
if slices.Contains(resRoles, role) {
resp.Diagnostics.AddAttributeError(
path.Root("roles"),
"Attribute Configuration Error",
"defined roles MUST NOT contain duplicates",
)
return
}
resRoles = append(resRoles, role)
}
}
// Create creates the resource and sets the initial Terraform state.
func (r *userResource) Create(
ctx context.Context,
@ -186,6 +240,7 @@ func (r *userResource) Create(
if resp.Diagnostics.HasError() {
return
}
slices.Sort(roles)
}
// Generate API request body from model
@ -379,7 +434,12 @@ func (r *userResource) Update(
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
// Update shouldn't be called
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", "User can't be updated")
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating user",
"an SQL server user can not be updated, only created",
)
}
// Delete deletes the resource and removes the Terraform state on success.
@ -433,7 +493,6 @@ func (r *userResource) Delete(
// Delete existing record set
_, err = sqlserverflexbetaWait.DeleteUserWaitHandler(ctx, r.client, projectId, region, instanceId, userId).
WaitWithContext(ctx)
// err := r.client.DeleteUserRequest(ctx, arg.projectId, arg.region, arg.instanceId, userId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "User Delete Error", fmt.Sprintf("Calling API: %v", err))
return

View file

@ -108,7 +108,7 @@ func CreateInstanceWaitHandler(
)
if extendedTimeout < 3 {
maxWait += time.Minute * 5
extendedTimeout = extendedTimeout + 1
extendedTimeout++
if *s.Network.AccessScope == "SNA" {
ready := true
if s.Network == nil || s.Network.InstanceAddress == nil {
@ -208,7 +208,7 @@ func PartialUpdateInstanceWaitHandler(
case InstanceStateUnknown:
return false, nil, nil
case InstanceStateFailed:
return true, s, fmt.Errorf("update failed for instance with id %s", instanceId)
return true, s, fmt.Errorf("update got status FAILURE for instance with id %s", instanceId)
}
},
)
@ -228,7 +228,7 @@ func GetUserByIdWaitHandler(
if userId > math.MaxInt32 {
return false, nil, fmt.Errorf("userId value is too big for int32")
}
userId32 := int32(userId)
userId32 := int32(userId) //nolint:gosec // we need to convert databaseId to int32 because API expects int32
s, err := a.GetUserRequestExecute(ctx, projectId, region, instanceId, userId32)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
@ -239,9 +239,11 @@ func GetUserByIdWaitHandler(
switch oapiErr.StatusCode {
case http.StatusBadGateway, http.StatusGatewayTimeout, http.StatusServiceUnavailable:
case http.StatusNotFound:
tflog.Warn(ctx, "api responded with status", map[string]interface{}{
"status": oapiErr.StatusCode,
})
tflog.Warn(
ctx, "api responded with status", map[string]interface{}{
"status": oapiErr.StatusCode,
},
)
return false, nil, nil
default:
return false, nil, err
@ -262,7 +264,7 @@ func GetDatabaseByIdWaitHandler(
) *wait.AsyncActionHandler[postgresflex.GetDatabaseResponse] {
handler := wait.New(
func() (waitFinished bool, response *postgresflex.GetDatabaseResponse, err error) {
dbId32 := int32(databaseId)
dbId32 := int32(databaseId) //nolint:gosec // we need to convert databaseId to int32 because API expects int32
s, err := a.GetDatabaseRequestExecute(ctx, projectId, region, instanceId, dbId32)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
@ -272,14 +274,18 @@ func GetDatabaseByIdWaitHandler(
}
switch oapiErr.StatusCode {
case http.StatusBadGateway, http.StatusGatewayTimeout, http.StatusServiceUnavailable:
tflog.Warn(ctx, "api responded with 50[2,3,4] status", map[string]interface{}{
"status": oapiErr.StatusCode,
})
tflog.Warn(
ctx, "api responded with 50[2,3,4] status", map[string]interface{}{
"status": oapiErr.StatusCode,
},
)
return false, nil, nil
case http.StatusNotFound:
tflog.Warn(ctx, "api responded with 404 status", map[string]interface{}{
"status": oapiErr.StatusCode,
})
tflog.Warn(
ctx, "api responded with 404 status", map[string]interface{}{
"status": oapiErr.StatusCode,
},
)
return false, nil, nil
default:
return false, nil, err

View file

@ -54,7 +54,12 @@ type APIClientInterface interface {
instanceId string,
) (*sqlserverflex.ListRolesResponse, error)
ListUsersRequest(ctx context.Context, projectId string, region string, instanceId string) sqlserverflex.ApiListUsersRequestRequest
ListUsersRequest(
ctx context.Context,
projectId string,
region string,
instanceId string,
) sqlserverflex.ApiListUsersRequestRequest
ListUsersRequestExecute(
ctx context.Context,
@ -256,7 +261,10 @@ func CreateDatabaseWaitHandler(
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("get database - could not convert error to oapierror.GenericOpenAPIError: %s", err.Error())
return false, nil, fmt.Errorf(
"get database - could not convert error to oapierror.GenericOpenAPIError: %s",
err.Error(),
)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err
@ -318,7 +326,10 @@ func WaitForUserWaitHandler(
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("Wait (list users) could not convert error to oapierror.GenericOpenAPIError: %s", err.Error())
return false, nil, fmt.Errorf(
"wait (list users) could not convert error to oapierror.GenericOpenAPIError: %s",
err.Error(),
)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err

View file

@ -116,7 +116,6 @@ func (a *apiClientInstanceMocked) GetInstanceRequestExecute(
}, nil
}
func TestCreateInstanceWaitHandler(t *testing.T) {
//stateSuccess := utils.Ptr(InstanceStateSuccess)
instanceId := utils.Ptr("foo")
tests := []struct {
desc string
@ -160,7 +159,7 @@ func TestCreateInstanceWaitHandler(t *testing.T) {
// Storage: nil,
// Version: nil,
// },
//},
// },
{
desc: "create_failed",
instanceId: *instanceId,

View file

@ -54,7 +54,12 @@ type APIClientInterface interface {
instanceId string,
) (*sqlserverflex.ListRolesResponse, error)
ListUsersRequest(ctx context.Context, projectId string, region string, instanceId string) sqlserverflex.ApiListUsersRequestRequest
ListUsersRequest(
ctx context.Context,
projectId string,
region string,
instanceId string,
) sqlserverflex.ApiListUsersRequestRequest
ListUsersRequestExecute(
ctx context.Context,
@ -85,7 +90,17 @@ func CreateInstanceWaitHandler(
func() (waitFinished bool, response *sqlserverflex.GetInstanceResponse, err error) {
s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId)
if err != nil {
return false, nil, err
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
}
switch oapiErr.StatusCode {
case http.StatusNotFound:
return false, nil, nil
default:
return false, nil, fmt.Errorf("api error: %w", err)
}
}
if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil {
return false, nil, nil
@ -151,8 +166,18 @@ func CreateInstanceWaitHandler(
return false, nil, nil
}
return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, nil, fmt.Errorf("create failed for instance with id %s", instanceId)
case strings.ToLower(InstanceStateUnknown):
return true, nil, fmt.Errorf(
"create failed for instance %s with status %s",
instanceId,
InstanceStateUnknown,
)
case strings.ToLower(InstanceStateFailed):
return true, nil, fmt.Errorf(
"create failed for instance %s with status %s",
instanceId,
InstanceStateFailed,
)
case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing):
tflog.Info(
ctx, "request is being handled", map[string]interface{}{
@ -256,7 +281,10 @@ func CreateDatabaseWaitHandler(
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("get database - could not convert error to oapierror.GenericOpenAPIError: %s", err.Error())
return false, nil, fmt.Errorf(
"get database - could not convert error to oapierror.GenericOpenAPIError: %s",
err.Error(),
)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err
@ -318,7 +346,10 @@ func WaitForUserWaitHandler(
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("Wait (list users) could not convert error to oapierror.GenericOpenAPIError: %s", err.Error())
return false, nil, fmt.Errorf(
"wait (list users) could not convert error to oapierror.GenericOpenAPIError: %s",
err.Error(),
)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err

View file

@ -116,7 +116,6 @@ func (a *apiClientInstanceMocked) GetInstanceRequestExecute(
}, nil
}
func TestCreateInstanceWaitHandler(t *testing.T) {
//stateSuccess := utils.Ptr(InstanceStateSuccess)
instanceId := utils.Ptr("foo")
tests := []struct {
desc string
@ -160,7 +159,7 @@ func TestCreateInstanceWaitHandler(t *testing.T) {
// Storage: nil,
// Version: nil,
// },
//},
// },
{
desc: "create_failed",
instanceId: *instanceId,

View file

@ -6,6 +6,7 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@ -45,6 +46,18 @@ var (
_ provider.Provider = &Provider{}
)
//nolint:unused // These constants are defined for future use in retry logic for HTTP requests, which is not yet implemented.
const (
// maxRetries is the maximum number of retries for a failed HTTP request.
maxRetries = 3
// initialDelay is the initial delay before the first retry attempt.
initialDelay = 2 * time.Second
// maxDelay is the maximum delay between retry attempts.
maxDelay = 90 * time.Second
// perTryTimeout is the timeout for each individual HTTP request attempt.
perTryTimeout = 30 * time.Second
)
// Provider is the provider implementation.
type Provider struct {
version string
@ -111,6 +124,7 @@ type providerModel struct {
// Schema defines the provider-level schema for configuration data.
func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
//nolint:gosec // These are just descriptions, not actual credentials or sensitive information.
descriptions := map[string]string{
"credentials_path": "Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`.",
"service_account_token": "Token used for authentication. If set, the token flow will be used to authenticate all operations.",
@ -466,7 +480,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
providerData.Experiments = experimentValues
}
roundTripper, err := sdkauth.SetupAuth(sdkConfig)
baseRoundTripper, err := sdkauth.SetupAuth(sdkConfig)
if err != nil {
core.LogAndAddError(
ctx,
@ -477,9 +491,19 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
return
}
//nolint:gocritic // maybe later in the code
// roundTripper := core.NewRetryRoundTripper(
// baseRoundTripper,
// maxRetries,
// initialDelay,
// maxDelay,
// perTryTimeout,
//)
// Make round tripper and custom endpoints available during DataSource and Resource
// type Configure methods.
providerData.RoundTripper = roundTripper
// providerData.RoundTripper = roundTripper
providerData.RoundTripper = baseRoundTripper
resp.DataSourceData = providerData
resp.ResourceData = providerData

View file

@ -1,20 +1,47 @@
package stackit_test
import (
"context"
_ "embed"
"fmt"
"log/slog"
"net/http"
"os"
"reflect"
"regexp"
"testing"
"time"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/joho/godotenv"
"github.com/golang-jwt/jwt/v5"
"github.com/google/go-cmp/cmp"
test "github.com/hashicorp/terraform-plugin-testing/helper/resource" //nolint:staticcheck // used for acceptance testing
"github.com/jarcoal/httpmock"
"github.com/stackitcloud/stackit-sdk-go/core/clients"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
postgresFlexAlphaFlavor "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/flavor"
"github.com/hashicorp/terraform-plugin-framework/datasource"
tfResource "github.com/hashicorp/terraform-plugin-framework/resource"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit"
postgresFlexAlphaDatabase "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database"
postgresflexalphaFlavors "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/flavors"
postgresFlexAlphaInstance "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance"
postgresFlexAlphaUser "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user"
sqlserverflexalphaDatabase "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/database"
sqlserverFlexAlphaFlavor "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/flavor"
sqlserverFlexAlphaInstance "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance"
sqlserverFlexAlphaUser "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/user"
sqlserverflexBetaDatabase "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/database"
sqlserverFlexBetaFlavor "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/flavor"
sqlserverFlexBetaInstance "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/instance"
sqlserverFlexBetaUser "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexbeta/user"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/internal/testutils"
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/helper/resource" //nolint:staticcheck // used for acceptance testing
)
//go:embed testdata/provider-credentials.tf
@ -28,56 +55,213 @@ var providerValidAttributes string
var testConfigProviderCredentials config.Variables
func setup() {
err := godotenv.Load()
if err != nil {
slog.Info("could not find .env file - not loading .env")
return
}
slog.Info("loaded .env file")
testConfigProviderCredentials = config.Variables{
"project_id": config.StringVariable(os.Getenv("TF_ACC_PROJECT_ID")),
"region": config.StringVariable(os.Getenv("TF_ACC_REGION")),
"service_account_key_path": config.StringVariable(os.Getenv("TF_ACC_SERVICE_ACCOUNT_FILE")),
"name": config.StringVariable(
fmt.Sprintf(
"tf-acc-prov%s",
acctest.RandStringFromCharSet(3, acctest.CharSetAlphaNum),
),
),
}
}
func TestMain(m *testing.M) {
setup()
testutils.Setup()
code := m.Run()
// shutdown()
os.Exit(code)
}
func TestMshTest(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
testutils.ActivateEnvironmentHttpMocks()
httpmock.RegisterResponder(
"POST", `https://service-account.api.stackit.cloud/token`,
func(_ *http.Request) (*http.Response, error) {
token := jwt.NewWithClaims(
jwt.SigningMethodHS256, jwt.MapClaims{
"foo": "bar",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
},
)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString([]byte("mySecret"))
if err != nil {
panic(err)
}
tR := clients.TokenResponseBody{
AccessToken: tokenString,
ExpiresIn: 3600,
RefreshToken: "",
Scope: "",
TokenType: "",
}
return httpmock.NewJsonResponse(http.StatusOK, tR)
},
)
httpmock.RegisterResponder(
"GET",
`https://postgres-flex-service.api.eu01.stackit.cloud/v3alpha1/projects/xyz-project-id/regions/eu01/flavors?page=1&size=25&sort=id.asc`,
func(_ *http.Request) (*http.Response, error) {
res := postgresflexalpha.GetFlavorsResponse{
Flavors: &[]postgresflexalpha.ListFlavors{
{
Cpu: nil,
Description: nil,
Id: nil,
MaxGB: nil,
Memory: nil,
MinGB: nil,
NodeType: nil,
StorageClasses: nil,
},
},
Pagination: &postgresflexalpha.Pagination{
Page: utils.Ptr(int64(1)),
Size: utils.Ptr(int64(25)),
Sort: nil,
TotalPages: utils.Ptr(int64(1)),
TotalRows: utils.Ptr(int64(0)),
},
}
return httpmock.NewJsonResponse(
http.StatusOK,
res,
)
},
)
test.Test(
t, test.TestCase{
IsUnitTest: true,
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []test.TestStep{
{
ConfigVariables: map[string]config.Variable{
"project_id": config.StringVariable("xyz-project-id"),
},
Config: fmt.Sprintf(
`
provider "stackitprivatepreview" {
default_region = "%[1]s"
service_account_key_path = "%[2]s"
}
variable "project_id" {
type = string
}
data "stackitprivatepreview_postgresflexalpha_flavor" "all" {
project_id = var.project_id
region = "eu01"
cpu = 2
ram = 4
node_type = "Single"
storage_class = "premium-perf2-stackit"
}`,
os.Getenv("TF_ACC_REGION"),
os.Getenv("TF_ACC_SERVICE_ACCOUNT_FILE"),
),
},
},
},
)
}
func TestUnitProviderHasChildDataSources_Basic(t *testing.T) {
expectedDataSources := []datasource.DataSource{
postgresFlexAlphaFlavor.NewFlavorDataSource(),
// postgresFlexAlphaFlavor.NewFlavorListDataSource,
postgresFlexAlphaDatabase.NewDatabaseDataSource(),
postgresFlexAlphaInstance.NewInstanceDataSource(),
postgresFlexAlphaUser.NewUserDataSource(),
postgresflexalphaFlavors.NewFlavorsDataSource(),
sqlserverFlexAlphaFlavor.NewFlavorDataSource(),
sqlserverFlexAlphaInstance.NewInstanceDataSource(),
sqlserverFlexAlphaUser.NewUserDataSource(),
sqlserverflexalphaDatabase.NewDatabaseDataSource(),
sqlserverflexBetaDatabase.NewDatabaseDataSource(),
sqlserverFlexBetaInstance.NewInstanceDataSource(),
sqlserverFlexBetaUser.NewUserDataSource(),
sqlserverFlexBetaFlavor.NewFlavorDataSource(),
}
provider, ok := stackit.New("testing")().(*stackit.Provider)
if !ok {
t.Fatal("could not assert provider type")
}
datasources := provider.DataSources(context.Background())
expectedMap := map[string]struct{}{}
for _, d := range expectedDataSources {
expectedMap[reflect.TypeOf(d).String()] = struct{}{}
}
actualMap := map[string]struct{}{}
for _, d := range datasources {
actualMap[reflect.TypeOf(d()).String()] = struct{}{}
}
if diff := cmp.Diff(expectedMap, actualMap); diff != "" {
t.Errorf("DataSources mismatch (-expected +actual):\n%s", diff)
}
}
func TestUnitProviderHasChildResources_Basic(t *testing.T) {
expectedResources := []tfResource.Resource{
postgresFlexAlphaInstance.NewInstanceResource(),
postgresFlexAlphaUser.NewUserResource(),
postgresFlexAlphaDatabase.NewDatabaseResource(),
sqlserverFlexAlphaInstance.NewInstanceResource(),
sqlserverFlexAlphaUser.NewUserResource(),
sqlserverflexalphaDatabase.NewDatabaseResource(),
sqlserverFlexBetaInstance.NewInstanceResource(),
sqlserverFlexBetaUser.NewUserResource(),
sqlserverflexBetaDatabase.NewDatabaseResource(),
}
provider, ok := stackit.New("testing")().(*stackit.Provider)
if !ok {
t.Fatal("could not assert provider type")
}
resources := provider.Resources(context.Background())
expectedMap := map[string]struct{}{}
for _, r := range expectedResources {
expectedMap[reflect.TypeOf(r).String()] = struct{}{}
}
actualMap := map[string]struct{}{}
for _, r := range resources {
actualMap[reflect.TypeOf(r()).String()] = struct{}{}
}
if diff := cmp.Diff(expectedMap, actualMap); diff != "" {
t.Errorf("Resources mismatch (-expected +actual):\n%s", diff)
}
}
func TestAccEnvVarServiceAccountPathValid(t *testing.T) {
t.Skip("needs refactoring")
// Check if acceptance tests should be run
if v := os.Getenv(resource.EnvTfAcc); v == "" {
t.Skipf(
"Acceptance tests skipped unless env '%s' set",
resource.EnvTfAcc)
resource.EnvTfAcc,
)
return
}
// t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(true, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
},
},
},
})
)
}
func TestAccEnvVarServiceAccountPathInvalid(t *testing.T) {
@ -85,17 +269,19 @@ func TestAccEnvVarServiceAccountPathInvalid(t *testing.T) {
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(false, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
ExpectError: regexp.MustCompile(`undefined response type, status code 401`),
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
ExpectError: regexp.MustCompile(`undefined response type, status code 401`),
},
},
},
})
)
}
func TestAccCredentialsFileValid(t *testing.T) {
@ -103,16 +289,18 @@ func TestAccCredentialsFileValid(t *testing.T) {
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(true, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
},
},
},
})
)
}
func TestAccCredentialsFileInvalid(t *testing.T) {
@ -120,17 +308,19 @@ func TestAccCredentialsFileInvalid(t *testing.T) {
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(false, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
ExpectError: regexp.MustCompile(`Jwt is not in(\r\n|\r|\n)the form of Header.Payload.Signature`),
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
PreConfig: func() { testutils.SetTemporaryHome(tempHomeFolder) },
ConfigVariables: testConfigProviderCredentials,
Config: providerCredentialConfig,
ExpectError: regexp.MustCompile(`Jwt is not in(\r\n|\r|\n)the form of Header.Payload.Signature`),
},
},
},
})
)
}
func TestAccProviderConfigureValidValues(t *testing.T) {
@ -139,43 +329,52 @@ func TestAccProviderConfigureValidValues(t *testing.T) {
if v := os.Getenv(resource.EnvTfAcc); v == "" {
t.Skipf(
"Acceptance tests skipped unless env '%s' set",
resource.EnvTfAcc)
resource.EnvTfAcc,
)
return
}
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(true, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{ // valid provider attributes
ConfigVariables: testConfigProviderCredentials,
Config: providerValidAttributes,
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
// valid provider attributes
ConfigVariables: testConfigProviderCredentials,
Config: providerValidAttributes,
},
},
},
})
)
}
func TestAccProviderConfigureAnInvalidValue(t *testing.T) {
t.Skip("needs refactoring")
// Check if acceptance tests should be run
if v := os.Getenv(resource.EnvTfAcc); v == "" {
t.Skipf(
"Acceptance tests skipped unless env '%s' set",
resource.EnvTfAcc)
resource.EnvTfAcc,
)
return
}
t.Setenv("STACKIT_CREDENTIALS_PATH", "")
tempHomeFolder := testutils.CreateTemporaryHome(true, t)
defer testutils.CleanupTemporaryHome(tempHomeFolder, t)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{ // invalid test attribute should throw an error
ConfigVariables: testConfigProviderCredentials,
Config: providerInvalidAttribute,
ExpectError: regexp.MustCompile(`An argument named "test" is not expected here\.`),
resource.Test(
t, resource.TestCase{
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
// invalid test attribute should throw an error
ConfigVariables: testConfigProviderCredentials,
Config: providerInvalidAttribute,
ExpectError: regexp.MustCompile(`An argument named "test" is not expected here\.`),
},
},
},
})
)
}

View file

@ -1,3 +1,5 @@
//go:build tools
package tools
// Format Terraform code for use in documentation.
@ -7,3 +9,11 @@ package tools
// Generate documentation.
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir .. -provider-name stackitprivatepreview
import (
_ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
_ "github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework"
_ "github.com/hashicorp/terraform-plugin-codegen-openapi/cmd/tfplugingen-openapi"
_ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
_ "golang.org/x/tools/cmd/goimports"
)