fix: refactor sqlserver to handle encryption correctly (#31)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 6s
Publish / Publish provider (push) Successful in 6m59s

## 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: #31
Reviewed-by: Andre_Harms <andre.harms@stackit.cloud>
This commit is contained in:
Marcel_Henselin 2026-02-04 07:30:00 +00:00
parent 80d1d12278
commit 4549ba63e5
Signed by: tf-provider.git.onstackit.cloud
GPG key ID: 6D7E8A1ED8955A9C
23 changed files with 4203 additions and 1413 deletions

1
.gitignore vendored
View file

@ -45,3 +45,4 @@ dist
.secrets
pkg_gen
/release/

View file

@ -35,10 +35,15 @@ type version struct {
minor int
}
func Build() error {
type Builder struct {
SkipClone bool
SkipCleanup bool
}
func (b *Builder) Build() error {
slog.Info("Starting Builder")
slog.Info("Checking needed commands available")
slog.Info(" ... Checking needed commands available")
err := checkCommands([]string{"tfplugingen-framework", "tfplugingen-openapi"})
if err != nil {
return err
@ -51,28 +56,33 @@ func Build() error {
if root == nil || *root == "" {
return fmt.Errorf("unable to determine root directory from git")
}
slog.Info("Using root directory", "dir", *root)
slog.Info(" ... using root directory", "dir", *root)
slog.Info("Cleaning up old generator directory")
err = os.RemoveAll(path.Join(*root, GEN_REPO_NAME))
if err != nil {
return err
}
if !b.SkipCleanup {
slog.Info("Cleaning up old generator directory")
err = os.RemoveAll(path.Join(*root, GEN_REPO_NAME))
if err != nil {
return err
}
slog.Info("Cleaning up old packages directory")
err = os.RemoveAll(path.Join(*root, "pkg_gen"))
if err != nil {
return err
slog.Info("Cleaning up old packages directory")
err = os.RemoveAll(path.Join(*root, "pkg_gen"))
if err != nil {
return err
}
}
slog.Info("Creating generator dir", "dir", fmt.Sprintf("%s/%s", *root, GEN_REPO_NAME))
genDir, err := createGeneratorDir(*root, GEN_REPO, GEN_REPO_NAME)
if err != nil {
return err
genDir := path.Join(*root, GEN_REPO_NAME)
if !b.SkipClone {
err = createGeneratorDir(GEN_REPO, genDir, b.SkipClone)
if err != nil {
return err
}
}
slog.Info("Creating oas dir", "dir", fmt.Sprintf("%s/%s", *root, OAS_REPO_NAME))
repoDir, err := createRepoDir(genDir, OAS_REPO, OAS_REPO_NAME)
slog.Info("Creating oas repo dir", "dir", fmt.Sprintf("%s/%s", *root, OAS_REPO_NAME))
repoDir, err := createRepoDir(genDir, OAS_REPO, OAS_REPO_NAME, b.SkipClone)
if err != nil {
return fmt.Errorf("%s", err.Error())
}
@ -118,12 +128,6 @@ func Build() error {
}
}
slog.Info("Cleaning up", "dir", repoDir)
err = os.RemoveAll(filepath.Dir(repoDir))
if err != nil {
return fmt.Errorf("%s", err.Error())
}
slog.Info("Changing dir", "dir", genDir)
err = os.Chdir(genDir)
if err != nil {
@ -191,30 +195,16 @@ func Build() error {
if item.IsDir() {
slog.Info(" -> package", "name", item.Name())
tgtDir := path.Join(*root, "pkg_gen", item.Name())
// no backup needed as we generate new
//bakName := fmt.Sprintf("%s.%s", item.Name(), time.Now().Format("20060102-150405"))
//if _, err = os.Stat(tgtDir); !os.IsNotExist(err) {
// err = os.Rename(
// tgtDir,
// path.Join(*root, "pkg", bakName),
// )
// if err != nil {
// return err
// }
//}
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
}
// wait is placed outside now
//if _, err = os.Stat(path.Join(*root, "pkg", bakName, "wait")); !os.IsNotExist(err) {
// slog.Info(" Copying wait subfolder")
// err = os.Rename(path.Join(*root, "pkg", bakName, "wait"), path.Join(tgtDir, "wait"))
// if err != nil {
// return err
// }
//}
}
}
@ -238,17 +228,25 @@ func Build() error {
return err
}
slog.Info("Finally removing temporary files and directories")
//err = os.RemoveAll(path.Join(*root, "generated"))
//if err != nil {
// slog.Error("RemoveAll", "dir", path.Join(*root, "generated"), "err", err)
// return err
//}
if !b.SkipCleanup {
slog.Info("Finally removing temporary files and directories")
err = os.RemoveAll(path.Join(*root, "generated"))
if err != nil {
slog.Error("RemoveAll", "dir", path.Join(*root, "generated"), "err", err)
return err
}
err = os.RemoveAll(path.Join(*root, GEN_REPO_NAME))
if err != nil {
slog.Error("RemoveAll", "dir", path.Join(*root, GEN_REPO_NAME), "err", err)
return err
err = os.RemoveAll(path.Join(*root, GEN_REPO_NAME))
if err != nil {
slog.Error("RemoveAll", "dir", path.Join(*root, GEN_REPO_NAME), "err", err)
return err
}
slog.Info("Cleaning up", "dir", repoDir)
err = os.RemoveAll(filepath.Dir(repoDir))
if err != nil {
return fmt.Errorf("%s", err.Error())
}
}
slog.Info("Done")
@ -421,6 +419,7 @@ func generateServiceFiles(rootDir, generatorDir string) error {
continue
}
// TODO: use const of supported versions
if svcVersion.Name() != "alpha" && svcVersion.Name() != "beta" {
continue
}
@ -442,7 +441,7 @@ func generateServiceFiles(rootDir, generatorDir string) error {
fileName := matches[0][0]
resource := matches[0][1]
slog.Info(
"Found service spec",
" found service spec",
"name",
specFile.Name(),
"service",
@ -451,136 +450,147 @@ func generateServiceFiles(rootDir, generatorDir string) error {
resource,
)
//for _, part := range []string{"alpha", "beta"} {
oasFile := path.Join(generatorDir, "oas", fmt.Sprintf("%s%s.json", service.Name(), svcVersion))
if _, err = os.Stat(oasFile); !os.IsNotExist(err) {
slog.Info("found matching oas", "svc", service.Name(), "version", svcVersion.Name())
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)
if err != nil {
return err
}
oasFile := path.Join(generatorDir, "oas", fmt.Sprintf("%s%s.json", service.Name(), svcVersion.Name()))
if _, oasErr := os.Stat(oasFile); os.IsNotExist(oasErr) {
slog.Warn(" coulc not find matching oas", "svc", service.Name(), "version", svcVersion.Name())
continue
}
// slog.Info("Generating openapi spec json")
specJsonFile := path.Join(rootDir, "generated", "specs", fmt.Sprintf("%s_%s_spec.json", scName, resource))
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)
if err != nil {
return err
}
var stdOut, stdErr bytes.Buffer
// slog.Info("Generating openapi spec json")
specJsonFile := path.Join(rootDir, "generated", "specs", fmt.Sprintf("%s_%s_spec.json", scName, resource))
// noqa:gosec
cmd := exec.Command(
"tfplugingen-openapi",
"generate",
"--config",
path.Join(rootDir, "service_specs", fileName),
"--output",
specJsonFile,
oasFile,
var stdOut, stdErr bytes.Buffer
// noqa:gosec
cmd := exec.Command(
"tfplugingen-openapi",
"generate",
"--config",
path.Join(rootDir, "service_specs", service.Name(), svcVersion.Name(), fileName),
"--output",
specJsonFile,
oasFile,
)
cmd.Stdout = &stdOut
cmd.Stderr = &stdErr
if err = cmd.Start(); err != nil {
slog.Error(
"tfplugingen-openapi generate",
"error",
err,
"stdOut",
stdOut.String(),
"stdErr",
stdErr.String(),
)
cmd.Stdout = &stdOut
cmd.Stderr = &stdErr
return err
}
if err = cmd.Start(); err != nil {
slog.Error("tfplugingen-openapi generate", "error", err)
return err
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())
return fmt.Errorf("%s", stdErr.String())
}
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())
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-openapi generate", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
return err
}
}
// slog.Info("Creating terraform svc resource files folder")
tgtFolder := path.Join(rootDir, "generated", "internal", "services", scName, resource, "resources_gen")
err = os.MkdirAll(tgtFolder, 0755)
if err != nil {
slog.Error("tfplugingen-openapi generate", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
return err
}
}
if stdOut.Len() > 0 {
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)
if err != nil {
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
cmd2 := exec.Command(
"tfplugingen-framework",
"generate",
"resources",
"--input",
specJsonFile,
"--output",
tgtFolder,
"--package",
scName,
)
cmd2.Stdout = &stdOut
cmd2.Stderr = &stdErr
if err = cmd2.Start(); err != nil {
slog.Error("tfplugingen-framework generate resources", "error", err)
return err
}
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())
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
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)
if err != nil {
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
cmd3 := exec.Command(
"tfplugingen-framework",
"generate",
"data-sources",
"--input",
specJsonFile,
"--output",
tgtFolder,
"--package",
scName,
)
var stdOut3, stdErr3 bytes.Buffer
cmd3.Stdout = &stdOut3
cmd3.Stderr = &stdErr3
if err = cmd3.Start(); err != nil {
slog.Error("tfplugingen-framework generate data-sources", "error", err)
return err
}
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())
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-framework generate data-sources", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
cmd2 := exec.Command(
"tfplugingen-framework",
"generate",
"resources",
"--input",
specJsonFile,
"--output",
tgtFolder,
"--package",
scName,
)
cmd2.Stdout = &stdOut
cmd2.Stderr = &stdErr
if err = cmd2.Start(); err != nil {
slog.Error("tfplugingen-framework generate resources", "error", err)
return err
}
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())
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
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)
if err != nil {
return err
}
// slog.Info("Generating terraform svc resource files")
// noqa:gosec
cmd3 := exec.Command(
"tfplugingen-framework",
"generate",
"data-sources",
"--input",
specJsonFile,
"--output",
tgtFolder,
"--package",
scName,
)
var stdOut3, stdErr3 bytes.Buffer
cmd3.Stdout = &stdOut3
cmd3.Stderr = &stdErr3
if err = cmd3.Start(); err != nil {
slog.Error("tfplugingen-framework generate data-sources", "error", err)
return err
}
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())
return fmt.Errorf("%s", stdErr.String())
}
if err != nil {
slog.Error("tfplugingen-framework generate data-sources", "err", err, "stdout", stdOut.String(), "stderr", stdErr.String())
return err
}
}
}
//}
}
}
}
@ -593,7 +603,7 @@ func checkCommands(commands []string) error {
if !commandExists(commandName) {
return fmt.Errorf("missing command %s", commandName)
}
slog.Info("found", "command", commandName)
slog.Info(" found", "command", commandName)
}
return nil
}
@ -726,32 +736,41 @@ func handleVersion(service string, match []string) (*string, *version, error) {
return &resStr, &version{verString: verString, major: majVer, minor: minVer}, nil
}
func createRepoDir(root, repoUrl, repoName string) (string, error) {
oasTmpDir, err := os.MkdirTemp(root, "oas-tmp")
if err != nil {
return "", err
}
targetDir := path.Join(oasTmpDir, repoName)
_, err = git.Clone(
clone.Repository(repoUrl),
clone.Directory(targetDir),
)
if err != nil {
return "", err
func createRepoDir(root, repoUrl, repoName string, skipClone bool) (string, error) {
targetDir := path.Join(root, repoName)
if !skipClone {
if fileExists(targetDir) {
slog.Warn("target dir exists - skipping", "targetDir", targetDir)
return targetDir, nil
}
_, err := git.Clone(
clone.Repository(repoUrl),
clone.Directory(targetDir),
)
if err != nil {
return "", err
}
}
return targetDir, nil
}
func createGeneratorDir(root, repoUrl, repoName string) (string, error) {
targetDir := path.Join(root, repoName)
_, err := git.Clone(
clone.Repository(repoUrl),
clone.Directory(targetDir),
)
if err != nil {
return "", err
func createGeneratorDir(repoUrl, targetDir string, skipClone bool) error {
if !skipClone {
if fileExists(targetDir) {
remErr := os.RemoveAll(targetDir)
if remErr != nil {
return remErr
}
}
_, cloneErr := git.Clone(
clone.Repository(repoUrl),
clone.Directory(targetDir),
)
if cloneErr != nil {
return cloneErr
}
}
return targetDir, nil
return nil
}
func getRoot() (*string, error) {

View file

@ -3,6 +3,7 @@ package {{.PackageName}}
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
@ -17,6 +18,7 @@ var (
_ resource.ResourceWithConfigure = &{{.NameCamel}}Resource{}
_ resource.ResourceWithImportState = &{{.NameCamel}}Resource{}
_ resource.ResourceWithModifyPlan = &{{.NameCamel}}Resource{}
_ resource.ResourceWithIdentity = &{{.NameCamel}}Resource{}
)
func New{{.NamePascal}}Resource() resource.Resource {
@ -28,6 +30,13 @@ type {{.NameCamel}}Resource struct{
providerData core.ProviderData
}
type InstanceResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"`
// TODO: implement further needed parts
}
func (r *{{.NameCamel}}Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_{{.PackageName}}_{{.NameSnake}}"
}
@ -36,6 +45,23 @@ func (r *{{.NameCamel}}Resource) Schema(ctx context.Context, req resource.Schema
resp.Schema = {{.PackageName}}Gen.{{.NamePascal}}ResourceSchema(ctx)
}
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
resp.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{
RequiredForImport: true, // must be set during import by the practitioner
},
"region": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
"instance_id": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
},
}
}
// Configure adds the provider configured client to the resource.
func (r *{{.NameCamel}}Resource) Configure(
ctx context.Context,
@ -81,6 +107,19 @@ func (r *{{.NameCamel}}Resource) Create(ctx context.Context, req resource.Create
// Example data value setting
data.{{.NameCamel | ucfirst}}Id = types.StringValue("id-from-response")
// TODO: Set data returned by API in identity
identity := InstanceResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
// TODO: implement wait handler if needed
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@ -93,6 +132,13 @@ func (r *{{.NameCamel}}Resource) Read(ctx context.Context, req resource.ReadRequ
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
if resp.Diagnostics.HasError() {
return
}

View file

@ -5,13 +5,29 @@ import (
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd/build"
)
func NewBuildCmd() *cobra.Command {
return &cobra.Command{
Use: "build",
Short: "Build the necessary boilerplate",
Long: `...`,
RunE: func(cmd *cobra.Command, args []string) error {
return build.Build()
},
}
var (
skipCleanup bool
skipClone bool
)
var buildCmd = &cobra.Command{
Use: "build",
Short: "Build the necessary boilerplate",
Long: `...`,
RunE: func(cmd *cobra.Command, args []string) error {
b := build.Builder{
SkipClone: skipClone,
SkipCleanup: skipCleanup,
}
return b.Build()
},
}
func NewBuildCmd() *cobra.Command {
return buildCmd
}
func init() { // nolint: gochecknoinits
buildCmd.Flags().BoolVarP(&skipCleanup, "skip-clean", "c", false, "Skip cleanup steps")
buildCmd.Flags().BoolVarP(&skipClone, "skip-clone", "g", false, "Skip cloning from git")
}

View file

@ -2,17 +2,27 @@ package main
import (
"log"
"log/slog"
"os"
"github.com/MatusOllah/slogcolor"
cc "github.com/ivanpirog/coloredcobra"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd"
)
func main() {
rootCmd := cmd.NewRootCmd()
//rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
//rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
//rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
slog.SetDefault(slog.New(slogcolor.NewHandler(os.Stderr, slogcolor.DefaultOptions)))
rootCmd := cmd.NewRootCmd()
cc.Init(&cc.Config{
RootCmd: rootCmd,
Headings: cc.HiCyan + cc.Bold + cc.Underline,
Commands: cc.HiYellow + cc.Bold,
Example: cc.Italic,
ExecName: cc.Bold,
Flags: cc.Bold,
})
rootCmd.SetOut(os.Stdout)
rootCmd.AddCommand(

View file

@ -30,11 +30,12 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
### 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))
**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.
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not.

View file

@ -3,12 +3,12 @@
page_title: "stackitprivatepreview_sqlserverflexalpha_instance Data Source - stackitprivatepreview"
subcategory: ""
description: |-
SQLServer Flex ALPHA instance resource schema. Must have a region specified in the provider configuration.
---
# stackitprivatepreview_sqlserverflexalpha_instance (Data Source)
SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.
## Example Usage
@ -24,61 +24,48 @@ data "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
### Required
- `instance_id` (String) ID of the SQLServer Flex instance.
- `project_id` (String) STACKIT project ID to which the instance is associated.
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
- `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 backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")
- `edition` (String)
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption))
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`".
- `is_deletable` (Boolean)
- `name` (String) Instance name.
- `network` (Attributes) The network block. (see [below for nested schema](#nestedatt--network))
- `replicas` (Number)
- `retention_days` (Number)
- `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) The ID of the instance.
- `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) (see [below for nested schema](#nestedatt--storage))
- `version` (String)
- `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.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Read-Only:
- `key_id` (String) STACKIT KMS - Key ID of the encryption key to use.
- `key_version` (String) STACKIT KMS - Key version to use in the encryption key.
- `keyring_id` (String) STACKIT KMS - KeyRing ID of the encryption key to use.
- `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--flavor"></a>
### Nested Schema for `flavor`
Read-Only:
- `cpu` (Number)
- `description` (String)
- `id` (String)
- `node_type` (String)
- `ram` (Number)
<a id="nestedatt--network"></a>
### Nested Schema for `network`
Read-Only:
- `access_scope` (String) The access scope of the instance. (e.g. SNA)
- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
- `instance_address` (String) The returned instance IP address of the SQLServer Flex instance.
- `router_address` (String) The returned router IP address of the SQLServer Flex instance.
- `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>
@ -86,5 +73,5 @@ Read-Only:
Read-Only:
- `class` (String)
- `size` (Number)
- `class` (String) The storage class for the storage.
- `size` (Number) The storage size in Gigabytes.

View file

@ -55,13 +55,14 @@ import {
- `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))
**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.
@ -77,6 +78,9 @@ Required:
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)

View file

@ -3,12 +3,12 @@
page_title: "stackitprivatepreview_sqlserverflexalpha_instance Resource - stackitprivatepreview"
subcategory: ""
description: |-
SQLServer Flex ALPHA instance resource schema. Must have a region specified in the provider configuration.
---
# stackitprivatepreview_sqlserverflexalpha_instance (Resource)
SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.
## Example Usage
@ -41,41 +41,55 @@ import {
### Required
- `flavor_id` (String)
- `name` (String) Instance name.
- `network` (Attributes) The network block. (see [below for nested schema](#nestedatt--network))
- `project_id` (String) STACKIT project ID to which the instance is associated.
- `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
- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption))
- `is_deletable` (Boolean)
- `region` (String) The resource region. If not defined, the provider region is used.
- `retention_days` (Number)
- `status` (String)
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
- `version` (String)
- `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)
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`".
- `instance_id` (String) ID of the SQLServer Flex instance.
- `replicas` (Number)
- `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:
- `access_scope` (String) The access scope of the instance. (SNA | PUBLIC)
- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
- `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) The returned instance IP address of the SQLServer Flex instance.
- `router_address` (String) The returned router IP address of the SQLServer Flex instance.
- `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>
@ -83,16 +97,7 @@ Read-Only:
Required:
- `key_id` (String) STACKIT KMS - Key ID of the encryption key to use.
- `key_version` (String) STACKIT KMS - Key version to use in the encryption key.
- `keyring_id` (String) STACKIT KMS - KeyRing ID of the encryption key to use.
- `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--storage"></a>
### Nested Schema for `storage`
Optional:
- `class` (String)
- `size` (Number)

2
go.mod
View file

@ -3,6 +3,7 @@ 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/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/terraform-plugin-framework v1.17.0
@ -11,6 +12,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.10.0
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/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

9
go.sum
View file

@ -1,5 +1,7 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/MatusOllah/slogcolor v1.7.0 h1:Nrd7yBPv2EBEEBEwl7WEPRmMd1ozZzw2jm8SLMYDbKs=
github.com/MatusOllah/slogcolor v1.7.0/go.mod h1:5y1H50XuQIBvuYTJlmokWi+4FuPiJN5L7Z0jM4K4bYA=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
@ -13,6 +15,7 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@ -106,8 +109,11 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4=
github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
@ -155,8 +161,10 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -268,5 +276,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -34,10 +34,10 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "msh-sna-001" {
#keyring_id = stackit_kms_keyring.keyring.keyring_id
#key_version = 1
# key with scope public
key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
kek_key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
# key_id = var.key_id
keyring_id = var.keyring_id
key_version = var.key_version
kek_key_ring_id = var.keyring_id
kek_key_version = var.key_version
service_account = var.sa_email
}
network = {

View file

@ -18,6 +18,11 @@ resources:
method: DELETE
data_sources:
database:
read:
path: /v3alpha1/projects/{projectId}/regions/{region}/instances/{instanceId}/databases/{databaseId}
method: GET
databases:
read:
path: /v3alpha1/projects/{projectId}/regions/{region}/instances/{instanceId}/databases

View file

@ -1,4 +1,3 @@
provider:
name: stackitprivatepreview
@ -18,6 +17,11 @@ resources:
method: DELETE
data_sources:
instances:
read:
path: /v3alpha1/projects/{projectId}/regions/{region}/instances
method: GET
instance:
read:
path: /v3alpha1/projects/{projectId}/regions/{region}/instances/{instanceId}

View file

@ -7,21 +7,17 @@ import (
"fmt"
"net/http"
"github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
sqlserverflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/datasources_gen"
sqlserverflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
sqlserverflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
// Ensure the implementation satisfies the expected interfaces.
@ -62,165 +58,167 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi
}
// Schema defines the schema for the data source.
func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"access_scope": "The access scope of the instance. (e.g. SNA)",
"acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
"backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
"region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.",
"network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
"key_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account to use in the encryption key.",
"instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router IP address of the SQLServer Flex instance.",
}
func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
//descriptions := map[string]string{
// "main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
// "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
// "instance_id": "ID of the SQLServer Flex instance.",
// "project_id": "STACKIT project ID to which the instance is associated.",
// "name": "Instance name.",
// "access_scope": "The access scope of the instance. (e.g. SNA)",
// "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
// "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
// "region": "The resource region. If not defined, the provider region is used.",
// "encryption": "The encryption block.",
// "network": "The network block.",
// "keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
// "key_id": "STACKIT KMS - Key ID of the encryption key to use.",
// "key_version": "STACKIT KMS - Key version to use in the encryption key.",
// "service:account": "STACKIT KMS - service account to use in the encryption key.",
// "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
// "router_address": "The returned router IP address of the SQLServer Flex instance.",
//}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Computed: true,
},
"backup_schedule": schema.StringAttribute{
Description: descriptions["backup_schedule"],
Computed: true,
},
"is_deletable": schema.BoolAttribute{
Description: descriptions["is_deletable"],
Computed: true,
},
"flavor": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"description": schema.StringAttribute{
Computed: true,
},
"cpu": schema.Int64Attribute{
Computed: true,
},
"ram": schema.Int64Attribute{
Computed: true,
},
"node_type": schema.StringAttribute{
Computed: true,
},
},
},
"replicas": schema.Int64Attribute{
Computed: true,
},
"storage": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Computed: true,
},
"size": schema.Int64Attribute{
Computed: true,
},
},
},
"version": schema.StringAttribute{
Computed: true,
},
"status": schema.StringAttribute{
Computed: true,
},
"edition": schema.StringAttribute{
Computed: true,
},
"retention_days": schema.Int64Attribute{
Computed: true,
},
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
Description: descriptions["region"],
},
"encryption": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Computed: true,
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Computed: true,
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Computed: true,
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Computed: true,
},
},
Description: descriptions["encryption"],
},
"network": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Description: descriptions["access_scope"],
Computed: true,
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Computed: true,
},
},
Description: descriptions["network"],
},
},
}
resp.Schema = sqlserverflexalpha.InstanceDataSourceSchema(ctx)
//resp.Schema = schema.Schema{
// Description: descriptions["main"],
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Description: descriptions["id"],
// Computed: true,
// },
// "instance_id": schema.StringAttribute{
// Description: descriptions["instance_id"],
// Required: true,
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "project_id": schema.StringAttribute{
// Description: descriptions["project_id"],
// Required: true,
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "name": schema.StringAttribute{
// Description: descriptions["name"],
// Computed: true,
// },
// "backup_schedule": schema.StringAttribute{
// Description: descriptions["backup_schedule"],
// Computed: true,
// },
// "is_deletable": schema.BoolAttribute{
// Description: descriptions["is_deletable"],
// Computed: true,
// },
// "flavor": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Computed: true,
// },
// "description": schema.StringAttribute{
// Computed: true,
// },
// "cpu": schema.Int64Attribute{
// Computed: true,
// },
// "ram": schema.Int64Attribute{
// Computed: true,
// },
// "node_type": schema.StringAttribute{
// Computed: true,
// },
// },
// },
// "replicas": schema.Int64Attribute{
// Computed: true,
// },
// "storage": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "class": schema.StringAttribute{
// Computed: true,
// },
// "size": schema.Int64Attribute{
// Computed: true,
// },
// },
// },
// "version": schema.StringAttribute{
// Computed: true,
// },
// "status": schema.StringAttribute{
// Computed: true,
// },
// "edition": schema.StringAttribute{
// Computed: true,
// },
// "retention_days": schema.Int64Attribute{
// Computed: true,
// },
// "region": schema.StringAttribute{
// // the region cannot be found, so it has to be passed
// Optional: true,
// Description: descriptions["region"],
// },
// "encryption": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "key_id": schema.StringAttribute{
// Description: descriptions["key_id"],
// Computed: true,
// },
// "key_version": schema.StringAttribute{
// Description: descriptions["key_version"],
// Computed: true,
// },
// "keyring_id": schema.StringAttribute{
// Description: descriptions["keyring_id"],
// Computed: true,
// },
// "service_account": schema.StringAttribute{
// Description: descriptions["service_account"],
// Computed: true,
// },
// },
// Description: descriptions["encryption"],
// },
// "network": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "access_scope": schema.StringAttribute{
// Description: descriptions["access_scope"],
// Computed: true,
// },
// "instance_address": schema.StringAttribute{
// Description: descriptions["instance_address"],
// Computed: true,
// },
// "router_address": schema.StringAttribute{
// Description: descriptions["router_address"],
// Computed: true,
// },
// "acl": schema.ListAttribute{
// Description: descriptions["acl"],
// ElementType: types.StringType,
// Computed: true,
// },
// },
// Description: descriptions["network"],
// },
// },
//}
}
// Read refreshes the Terraform state with the latest data.
func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -253,34 +251,35 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = core.LogResponse(ctx)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
//var storage = &storageModel{}
//if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
// diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
//
//var encryption = &encryptionModel{}
//if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
// diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
//
//var network = &networkModel{}
//if !model.Network.IsNull() && !model.Network.IsUnknown() {
// diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, instanceResp, &model, resp.Diagnostics)
//err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return

View file

@ -6,202 +6,299 @@ import (
"math"
"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"
sqlserverflex "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"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
sqlserverflexResGen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
)
func mapFields(
func mapResponseToModel(
ctx context.Context,
resp *sqlserverflex.GetInstanceResponse,
model *Model,
storage *storageModel,
encryption *encryptionModel,
network *networkModel,
region string,
m *sqlserverflexResGen.InstanceModel,
tfDiags diag.Diagnostics,
) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
instance := resp
var instanceId string
if model.InstanceId.ValueString() != "" {
instanceId = model.InstanceId.ValueString()
} else if instance.Id != nil {
instanceId = *instance.Id
} else {
return fmt.Errorf("instance id not present")
}
var storageValues map[string]attr.Value
if instance.Storage == nil {
storageValues = map[string]attr.Value{
"class": storage.Class,
"size": storage.Size,
}
} else {
storageValues = map[string]attr.Value{
"class": types.StringValue(*instance.Storage.Class),
"size": types.Int64PointerValue(instance.Storage.Size),
}
}
storageObject, diags := types.ObjectValue(storageTypes, storageValues)
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Edition = types.StringValue(string(resp.GetEdition()))
m.Encryption = handleEncryption(m, resp)
m.FlavorId = types.StringValue(resp.GetFlavorId())
m.Id = types.StringValue(resp.GetId())
m.InstanceId = types.StringValue(resp.GetId())
m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
m.Name = types.StringValue(resp.GetName())
netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
return fmt.Errorf(
"error converting network acl response value",
)
}
var encryptionValues map[string]attr.Value
if instance.Encryption == nil {
encryptionValues = map[string]attr.Value{
"keyring_id": encryption.KeyRingId,
"key_id": encryption.KeyId,
"key_version": encryption.KeyVersion,
"service_account": encryption.ServiceAccount,
}
} else {
encryptionValues = map[string]attr.Value{
"keyring_id": types.StringValue(*instance.Encryption.KekKeyRingId),
"key_id": types.StringValue(*instance.Encryption.KekKeyId),
"key_version": types.StringValue(*instance.Encryption.KekKeyVersion),
"service_account": types.StringValue(*instance.Encryption.ServiceAccount),
}
}
encryptionObject, diags := types.ObjectValue(encryptionTypes, encryptionValues)
net, diags := sqlserverflexResGen.NewNetworkValue(
sqlserverflexResGen.NetworkValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"access_scope": types.StringValue(string(resp.Network.GetAccessScope())),
"acl": netAcl,
"instance_address": types.StringValue(resp.Network.GetInstanceAddress()),
"router_address": types.StringValue(resp.Network.GetRouterAddress()),
},
)
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags))
return fmt.Errorf(
"error converting network response value",
"access_scope",
types.StringValue(string(resp.Network.GetAccessScope())),
"acl",
netAcl,
"instance_address",
types.StringValue(resp.Network.GetInstanceAddress()),
"router_address",
types.StringValue(resp.Network.GetRouterAddress()),
)
}
m.Network = net
m.Replicas = types.Int64Value(int64(resp.GetReplicas()))
m.RetentionDays = types.Int64Value(resp.GetRetentionDays())
m.Status = types.StringValue(string(resp.GetStatus()))
var networkValues map[string]attr.Value
if instance.Network == nil {
networkValues = map[string]attr.Value{
"acl": network.ACL,
"access_scope": network.AccessScope,
"instance_address": network.InstanceAddress,
"router_address": network.RouterAddress,
}
} else {
aclList, diags := types.ListValueFrom(ctx, types.StringType, *instance.Network.Acl)
if diags.HasError() {
return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
}
var routerAddress string
if instance.Network.RouterAddress != nil {
routerAddress = *instance.Network.RouterAddress
diags.AddWarning("field missing while mapping fields", "router_address was empty in API response")
}
if instance.Network.InstanceAddress == nil {
return fmt.Errorf("creating network: no instance address returned")
}
networkValues = map[string]attr.Value{
"acl": aclList,
"access_scope": types.StringValue(string(*instance.Network.AccessScope)),
"instance_address": types.StringValue(*instance.Network.InstanceAddress),
"router_address": types.StringValue(routerAddress),
}
}
networkObject, diags := types.ObjectValue(networkTypes, networkValues)
stor, diags := sqlserverflexResGen.NewStorageValue(
sqlserverflexResGen.StorageValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"class": types.StringValue(resp.Storage.GetClass()),
"size": types.Int64Value(resp.Storage.GetSize()),
},
)
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating network: %w", core.DiagsToError(diags))
return fmt.Errorf("error converting storage response value")
}
m.Storage = stor
simplifiedModelBackupSchedule := utils.SimplifyBackupSchedule(model.BackupSchedule.ValueString())
// If the value returned by the API is different from the one in the model after simplification,
// we update the model so that it causes an error in Terraform
if simplifiedModelBackupSchedule != types.StringPointerValue(instance.BackupSchedule).ValueString() {
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
}
if instance.Replicas == nil {
return fmt.Errorf("instance has no replicas set")
}
if instance.RetentionDays == nil {
return fmt.Errorf("instance has no retention days set")
}
if instance.Version == nil {
return fmt.Errorf("instance has no version set")
}
if instance.Edition == nil {
return fmt.Errorf("instance has no edition set")
}
if instance.Status == nil {
return fmt.Errorf("instance has no status set")
}
if instance.IsDeletable == nil {
return fmt.Errorf("instance has no IsDeletable set")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name)
model.FlavorId = types.StringPointerValue(instance.FlavorId)
model.Replicas = types.Int64Value(int64(*instance.Replicas))
model.Storage = storageObject
model.Version = types.StringValue(string(*instance.Version))
model.Edition = types.StringValue(string(*instance.Edition))
model.Region = types.StringValue(region)
model.Encryption = encryptionObject
model.Network = networkObject
model.RetentionDays = types.Int64Value(*instance.RetentionDays)
model.Status = types.StringValue(string(*instance.Status))
model.IsDeletable = types.BoolValue(*instance.IsDeletable)
m.Version = types.StringValue(string(resp.GetVersion()))
return nil
}
func handleEncryption(
m *sqlserverflexResGen.InstanceModel,
resp *sqlserverflex.GetInstanceResponse,
) sqlserverflexResGen.EncryptionValue {
if !resp.HasEncryption() ||
resp.Encryption == nil ||
resp.Encryption.KekKeyId == nil ||
resp.Encryption.KekKeyRingId == nil ||
resp.Encryption.KekKeyVersion == nil ||
resp.Encryption.ServiceAccount == nil {
if m.Encryption.IsNull() || m.Encryption.IsUnknown() {
return sqlserverflexResGen.NewEncryptionValueNull()
}
return m.Encryption
}
enc := sqlserverflexResGen.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)
}
return enc
}
//func mapFields(
// ctx context.Context,
// resp *sqlserverflex.GetInstanceResponse,
// model *Model,
// storage *storageModel,
// encryption *encryptionModel,
// network *networkModel,
// region string,
//) error {
// if resp == nil {
// return fmt.Errorf("response input is nil")
// }
// if model == nil {
// return fmt.Errorf("model input is nil")
// }
// instance := resp
//
// var instanceId string
// if model.InstanceId.ValueString() != "" {
// instanceId = model.InstanceId.ValueString()
// } else if instance.Id != nil {
// instanceId = *instance.Id
// } else {
// return fmt.Errorf("instance id not present")
// }
//
// var storageValues map[string]attr.Value
// if instance.Storage == nil {
// storageValues = map[string]attr.Value{
// "class": storage.Class,
// "size": storage.Size,
// }
// } else {
// storageValues = map[string]attr.Value{
// "class": types.StringValue(*instance.Storage.Class),
// "size": types.Int64PointerValue(instance.Storage.Size),
// }
// }
// storageObject, diags := types.ObjectValue(storageTypes, storageValues)
// if diags.HasError() {
// return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
// }
//
// var encryptionValues map[string]attr.Value
// if instance.Encryption == nil {
// encryptionValues = map[string]attr.Value{
// "keyring_id": encryption.KeyRingId,
// "key_id": encryption.KeyId,
// "key_version": encryption.KeyVersion,
// "service_account": encryption.ServiceAccount,
// }
// } else {
// encryptionValues = map[string]attr.Value{
// "keyring_id": types.StringValue(*instance.Encryption.KekKeyRingId),
// "key_id": types.StringValue(*instance.Encryption.KekKeyId),
// "key_version": types.StringValue(*instance.Encryption.KekKeyVersion),
// "service_account": types.StringValue(*instance.Encryption.ServiceAccount),
// }
// }
// encryptionObject, diags := types.ObjectValue(encryptionTypes, encryptionValues)
// if diags.HasError() {
// return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags))
// }
//
// var networkValues map[string]attr.Value
// if instance.Network == nil {
// networkValues = map[string]attr.Value{
// "acl": network.ACL,
// "access_scope": network.AccessScope,
// "instance_address": network.InstanceAddress,
// "router_address": network.RouterAddress,
// }
// } else {
// aclList, diags := types.ListValueFrom(ctx, types.StringType, *instance.Network.Acl)
// if diags.HasError() {
// return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
// }
//
// var routerAddress string
// if instance.Network.RouterAddress != nil {
// routerAddress = *instance.Network.RouterAddress
// diags.AddWarning("field missing while mapping fields", "router_address was empty in API response")
// }
// if instance.Network.InstanceAddress == nil {
// return fmt.Errorf("creating network: no instance address returned")
// }
// networkValues = map[string]attr.Value{
// "acl": aclList,
// "access_scope": types.StringValue(string(*instance.Network.AccessScope)),
// "instance_address": types.StringValue(*instance.Network.InstanceAddress),
// "router_address": types.StringValue(routerAddress),
// }
// }
// networkObject, diags := types.ObjectValue(networkTypes, networkValues)
// if diags.HasError() {
// return fmt.Errorf("creating network: %w", core.DiagsToError(diags))
// }
//
// simplifiedModelBackupSchedule := utils.SimplifyBackupSchedule(model.BackupSchedule.ValueString())
// // If the value returned by the API is different from the one in the model after simplification,
// // we update the model so that it causes an error in Terraform
// if simplifiedModelBackupSchedule != types.StringPointerValue(instance.BackupSchedule).ValueString() {
// model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
// }
//
// if instance.Replicas == nil {
// return fmt.Errorf("instance has no replicas set")
// }
//
// if instance.RetentionDays == nil {
// return fmt.Errorf("instance has no retention days set")
// }
//
// if instance.Version == nil {
// return fmt.Errorf("instance has no version set")
// }
//
// if instance.Edition == nil {
// return fmt.Errorf("instance has no edition set")
// }
//
// if instance.Status == nil {
// return fmt.Errorf("instance has no status set")
// }
//
// if instance.IsDeletable == nil {
// return fmt.Errorf("instance has no IsDeletable set")
// }
//
// model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
// model.InstanceId = types.StringValue(instanceId)
// model.Name = types.StringPointerValue(instance.Name)
// model.FlavorId = types.StringPointerValue(instance.FlavorId)
// model.Replicas = types.Int64Value(int64(*instance.Replicas))
// model.Storage = storageObject
// model.Version = types.StringValue(string(*instance.Version))
// model.Edition = types.StringValue(string(*instance.Edition))
// model.Region = types.StringValue(region)
// model.Encryption = encryptionObject
// model.Network = networkObject
// model.RetentionDays = types.Int64Value(*instance.RetentionDays)
// model.Status = types.StringValue(string(*instance.Status))
// model.IsDeletable = types.BoolValue(*instance.IsDeletable)
// return nil
//}
func toCreatePayload(
model *Model,
storage *storageModel,
encryption *encryptionModel,
network *networkModel,
ctx context.Context,
model *sqlserverflexResGen.InstanceModel,
) (*sqlserverflex.CreateInstanceRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
storagePayload := &sqlserverflex.CreateInstanceRequestPayloadGetStorageArgType{}
if storage != nil {
storagePayload.Class = conversion.StringValueToPointer(storage.Class)
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
storagePayload.Class = model.Storage.Class.ValueStringPointer()
storagePayload.Size = model.Storage.Size.ValueInt64Pointer()
}
var encryptionPayload *sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType
if encryption != nil &&
!encryption.KeyId.IsNull() && !encryption.KeyId.IsUnknown() &&
!encryption.KeyRingId.IsNull() && !encryption.KeyRingId.IsUnknown() &&
!encryption.KeyVersion.IsNull() && !encryption.KeyVersion.IsUnknown() &&
!encryption.ServiceAccount.IsNull() && !encryption.ServiceAccount.IsUnknown() {
var encryptionPayload *sqlserverflex.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 = &sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType{
KekKeyId: conversion.StringValueToPointer(encryption.KeyId),
KekKeyRingId: conversion.StringValueToPointer(encryption.KeyVersion),
KekKeyVersion: conversion.StringValueToPointer(encryption.KeyRingId),
ServiceAccount: conversion.StringValueToPointer(encryption.ServiceAccount),
}
}
var aclElements []string
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
aclElements = make([]string, 0, len(network.ACL.Elements()))
diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
if diags.HasError() {
return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
KekKeyId: model.Encryption.KekKeyId.ValueStringPointer(),
KekKeyRingId: model.Encryption.KekKeyVersion.ValueStringPointer(),
KekKeyVersion: model.Encryption.KekKeyRingId.ValueStringPointer(),
ServiceAccount: model.Encryption.ServiceAccount.ValueStringPointer(),
}
}
networkPayload := &sqlserverflex.CreateInstanceRequestPayloadGetNetworkArgType{}
if network != nil {
networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
networkPayload.Acl = &aclElements
if !model.Network.IsNull() && !model.Network.IsUnknown() {
networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(
model.Network.AccessScope.ValueStringPointer(),
)
var resList []string
diags := model.Network.Acl.ElementsAs(ctx, &resList, false)
if diags.HasError() {
return nil, fmt.Errorf("error converting network acl list")
}
networkPayload.Acl = &resList
}
return &sqlserverflex.CreateInstanceRequestPayload{
@ -216,66 +313,80 @@ func toCreatePayload(
}, nil
}
//nolint:unused // TODO: remove if not needed later
func toUpdatePartiallyPayload(
model *Model,
storage *storageModel,
network *networkModel,
) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
if storage != nil {
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
}
var aclElements []string
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
aclElements = make([]string, 0, len(network.ACL.Elements()))
diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
if diags.HasError() {
return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
}
}
networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{}
if network != nil {
networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
networkPayload.Acl = &aclElements
}
if model.Replicas.ValueInt64() > math.MaxInt32 {
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
}
replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
FlavorId: conversion.StringValueToPointer(model.FlavorId),
Name: conversion.StringValueToPointer(model.Name),
Network: networkPayload,
Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
Storage: storagePayload,
Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
}, nil
}
////nolint:unused // TODO: remove if not needed later
//func toUpdatePartiallyPayload(
// model *Model,
// storage *storageModel,
// network *networkModel,
//) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
// if model == nil {
// return nil, fmt.Errorf("nil model")
// }
//
// storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
// if storage != nil {
// storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
// }
//
// var aclElements []string
// if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
// aclElements = make([]string, 0, len(network.ACL.Elements()))
// diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
// if diags.HasError() {
// return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
// }
// }
//
// networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{}
// if network != nil {
// networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
// networkPayload.Acl = &aclElements
// }
//
// if model.Replicas.ValueInt64() > math.MaxInt32 {
// return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
// }
// replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
// return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
// BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
// FlavorId: conversion.StringValueToPointer(model.FlavorId),
// Name: conversion.StringValueToPointer(model.Name),
// Network: networkPayload,
// Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
// RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
// Storage: storagePayload,
// Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
// }, nil
//}
// TODO: check func with his args
func toUpdatePayload(
_ *Model,
_ *storageModel,
_ *networkModel,
ctx context.Context,
m *sqlserverflexResGen.InstanceModel,
resp *resource.UpdateResponse,
) (*sqlserverflex.UpdateInstanceRequestPayload, error) {
if m.Replicas.ValueInt64() > math.MaxUint32 {
return nil, fmt.Errorf("replicas value is too big for uint32")
}
replVal := sqlserverflex.Replicas(uint32(m.Replicas.ValueInt64()))
var netAcl []string
diags := m.Network.Acl.ElementsAs(ctx, &netAcl, false)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return nil, fmt.Errorf("error converting model network acl value")
}
return &sqlserverflex.UpdateInstanceRequestPayload{
BackupSchedule: nil,
FlavorId: nil,
Name: nil,
Network: nil,
Replicas: nil,
RetentionDays: nil,
Storage: nil,
Version: nil,
BackupSchedule: m.BackupSchedule.ValueStringPointer(),
FlavorId: m.FlavorId.ValueStringPointer(),
Name: m.Name.ValueStringPointer(),
Network: &sqlserverflex.CreateInstanceRequestPayloadNetwork{
AccessScope: sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(m.Network.AccessScope.ValueStringPointer()),
Acl: &netAcl,
},
Replicas: &replVal,
RetentionDays: m.RetentionDays.ValueInt64Pointer(),
Storage: &sqlserverflex.StorageUpdate{Size: m.Storage.Size.ValueInt64Pointer()},
Version: sqlserverflex.UpdateInstanceRequestPayloadGetVersionAttributeType(m.Version.ValueStringPointer()),
}, nil
}

View file

@ -21,7 +21,6 @@ fields:
- name: 'name'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'backup_schedule'
modifiers:
@ -31,24 +30,28 @@ fields:
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.kek_key_version'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.kek_key_ring_id'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.service_account'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'network.access_scope'
@ -76,6 +79,7 @@ fields:
- name: 'region'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'retention_days'

View file

@ -4,35 +4,25 @@ package sqlserverflex
import (
"context"
_ "embed"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
sqlserverflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
sqlserverflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"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"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
wait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/sqlserverflexalpha"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
@ -44,6 +34,7 @@ var (
_ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.ResourceWithIdentity = &instanceResource{}
)
//nolint:unused // TODO: remove if not needed later
@ -52,63 +43,10 @@ var validNodeTypes []string = []string{
"Replica",
}
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
BackupSchedule types.String `tfsdk:"backup_schedule"`
FlavorId types.String `tfsdk:"flavor_id"`
Encryption types.Object `tfsdk:"encryption"`
IsDeletable types.Bool `tfsdk:"is_deletable"`
Storage types.Object `tfsdk:"storage"`
Status types.String `tfsdk:"status"`
Version types.String `tfsdk:"version"`
Replicas types.Int64 `tfsdk:"replicas"`
Region types.String `tfsdk:"region"`
Network types.Object `tfsdk:"network"`
Edition types.String `tfsdk:"edition"`
RetentionDays types.Int64 `tfsdk:"retention_days"`
}
type encryptionModel struct {
KeyRingId types.String `tfsdk:"keyring_id"`
KeyId types.String `tfsdk:"key_id"`
KeyVersion types.String `tfsdk:"key_version"`
ServiceAccount types.String `tfsdk:"service_account"`
}
var encryptionTypes = map[string]attr.Type{
"keyring_id": basetypes.StringType{},
"key_id": basetypes.StringType{},
"key_version": basetypes.StringType{},
"service_account": basetypes.StringType{},
}
type networkModel struct {
ACL types.List `tfsdk:"acl"`
AccessScope types.String `tfsdk:"access_scope"`
InstanceAddress types.String `tfsdk:"instance_address"`
RouterAddress types.String `tfsdk:"router_address"`
}
var networkTypes = map[string]attr.Type{
"acl": basetypes.ListType{ElemType: types.StringType},
"access_scope": basetypes.StringType{},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
// Struct corresponding to Model.Storage
type storageModel struct {
Class types.String `tfsdk:"class"`
Size types.Int64 `tfsdk:"size"`
}
// Types corresponding to storageModel
var storageTypes = map[string]attr.Type{
"class": basetypes.StringType{},
"size": basetypes.Int64Type{},
type InstanceResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"`
}
// NewInstanceResource is a helper function to simplify the provider implementation.
@ -154,17 +92,21 @@ func (r *instanceResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
var configModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
if req.Plan.Raw.IsNull() {
return
}
var planModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
@ -175,257 +117,303 @@ func (r *instanceResource) ModifyPlan(
return
}
var identityModel InstanceResourceIdentityModel
identityModel.ProjectID = planModel.ProjectId
identityModel.Region = planModel.Region
if !planModel.InstanceId.IsNull() && !planModel.InstanceId.IsUnknown() {
identityModel.InstanceID = planModel.InstanceId
}
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
}
}
//go:embed planModifiers.yaml
var modifiersFileByte []byte
// Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"access_scope": "The access scope of the instance. (SNA | PUBLIC)",
"flavor_id": "The flavor ID of the instance.",
"acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
"backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
"region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.",
"replicas": "The number of replicas of the SQLServer Flex instance.",
"network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
"key_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account to use in the encryption key.",
"instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router IP address of the SQLServer Flex instance.",
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
//descriptions := map[string]string{
// "main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
// "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
// "instance_id": "ID of the SQLServer Flex instance.",
// "project_id": "STACKIT project ID to which the instance is associated.",
// "name": "Instance name.",
// "access_scope": "The access scope of the instance. (SNA | PUBLIC)",
// "flavor_id": "The flavor ID of the instance.",
// "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
// "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
// "region": "The resource region. If not defined, the provider region is used.",
// "encryption": "The encryption block.",
// "replicas": "The number of replicas of the SQLServer Flex instance.",
// "network": "The network block.",
// "keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
// "key_id": "STACKIT KMS - Key ID of the encryption key to use.",
// "key_version": "STACKIT KMS - Key version to use in the encryption key.",
// "service:account": "STACKIT KMS - service account to use in the encryption key.",
// "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
// "router_address": "The returned router IP address of the SQLServer Flex instance.",
//}
schema := sqlserverflexalpha2.InstanceResourceSchema(ctx)
fields, err := postgresflexUtils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
resp.Diagnostics.AddError("error during read modifiers config file", err.Error())
return
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
err = postgresflexUtils.AddPlanModifiersToResourceSchema(fields, &schema)
if err != nil {
resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
return
}
resp.Schema = schema
//resp.Schema = schema.Schema{
// Description: descriptions["main"],
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Description: descriptions["id"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "instance_id": schema.StringAttribute{
// Description: descriptions["instance_id"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "project_id": schema.StringAttribute{
// Description: descriptions["project_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "name": schema.StringAttribute{
// Description: descriptions["name"],
// Required: true,
// Validators: []validator.String{
// stringvalidator.LengthAtLeast(1),
// stringvalidator.RegexMatches(
// regexp.MustCompile("^[a-z]([-a-z0-9]*[a-z0-9])?$"),
// "must start with a letter, must have lower case letters, numbers or hyphens, and no hyphen at the end",
// ),
// },
// },
// "backup_schedule": schema.StringAttribute{
// Description: descriptions["backup_schedule"],
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "is_deletable": schema.BoolAttribute{
// Description: descriptions["is_deletable"],
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Bool{
// boolplanmodifier.UseStateForUnknown(),
// },
// },
// "flavor_id": schema.StringAttribute{
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// Required: true,
// },
// "replicas": schema.Int64Attribute{
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// "storage": schema.SingleNestedAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Object{
// objectplanmodifier.UseStateForUnknown(),
// },
// Attributes: map[string]schema.Attribute{
// "class": schema.StringAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "size": schema.Int64Attribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// },
// },
// "version": schema.StringAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "edition": schema.StringAttribute{
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "retention_days": schema.Int64Attribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// "region": schema.StringAttribute{
// Optional: true,
// // must be computed to allow for storing the override value from the provider
// Computed: true,
// Description: descriptions["region"],
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// },
// "status": schema.StringAttribute{
// Optional: true,
// // must be computed to allow for storing the override value from the provider
// Computed: true,
// Description: descriptions["status"],
// },
// "encryption": schema.SingleNestedAttribute{
// Optional: true,
// PlanModifiers: []planmodifier.Object{
// objectplanmodifier.RequiresReplace(),
// objectplanmodifier.UseStateForUnknown(),
// },
// Attributes: map[string]schema.Attribute{
// "key_id": schema.StringAttribute{
// Description: descriptions["key_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "key_version": schema.StringAttribute{
// Description: descriptions["key_version"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "keyring_id": schema.StringAttribute{
// Description: descriptions["keyring_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "service_account": schema.StringAttribute{
// Description: descriptions["service_account"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// },
// Description: descriptions["encryption"],
// },
// "network": schema.SingleNestedAttribute{
// Required: true,
// Attributes: map[string]schema.Attribute{
// "access_scope": schema.StringAttribute{
// Description: descriptions["access_scope"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "acl": schema.ListAttribute{
// Description: descriptions["acl"],
// ElementType: types.StringType,
// Required: true,
// PlanModifiers: []planmodifier.List{
// listplanmodifier.UseStateForUnknown(),
// },
// },
// "instance_address": schema.StringAttribute{
// Description: descriptions["instance_address"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "router_address": schema.StringAttribute{
// Description: descriptions["router_address"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// },
// Description: descriptions["network"],
// },
// },
//}
}
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
resp.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{
RequiredForImport: true, // must be set during import by the practitioner
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
"region": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(
regexp.MustCompile("^[a-z]([-a-z0-9]*[a-z0-9])?$"),
"must start with a letter, must have lower case letters, numbers or hyphens, and no hyphen at the end",
),
},
},
"backup_schedule": schema.StringAttribute{
Description: descriptions["backup_schedule"],
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"is_deletable": schema.BoolAttribute{
Description: descriptions["is_deletable"],
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"flavor_id": schema.StringAttribute{
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Required: true,
},
"replicas": schema.Int64Attribute{
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"storage": schema.SingleNestedAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"size": schema.Int64Attribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
},
},
"version": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"edition": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"retention_days": schema.Int64Attribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"status": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["status"],
},
"encryption": schema.SingleNestedAttribute{
Optional: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
objectplanmodifier.UseStateForUnknown(),
},
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
},
Description: descriptions["encryption"],
},
"network": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Description: descriptions["access_scope"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Required: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
Description: descriptions["network"],
"instance_id": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
},
}
@ -437,49 +425,29 @@ func (r *instanceResource) Create(
req resource.CreateRequest,
resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
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 := model.ProjectId.ValueString()
region := model.Region.ValueString()
projectId := identityData.ProjectID.ValueString()
region := identityData.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Generate API request body from model
payload, err := toCreatePayload(&model, storage, encryption, network)
payload, err := toCreatePayload(ctx, &model)
if err != nil {
core.LogAndAddError(
ctx,
@ -503,6 +471,18 @@ func (r *instanceResource) Create(
ctx = core.LogResponse(ctx)
instanceId := *createResp.Id
// Set data returned by API in identity
identity := InstanceResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
utils.SetAndLogStateFields(
ctx, &resp.Diagnostics, &resp.State, map[string]any{
"id": utils.BuildInternalTerraformId(projectId, region, instanceId),
@ -521,7 +501,11 @@ func (r *instanceResource) Create(
projectId,
instanceId,
region,
).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
).SetSleepBeforeWait(
30 * time.Second,
).SetTimeout(
90 * time.Minute,
).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(
ctx,
@ -543,7 +527,8 @@ func (r *instanceResource) Create(
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
// err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, waitResp, &model, resp.Diagnostics)
if err != nil {
core.LogAndAddError(
ctx,
@ -560,11 +545,6 @@ func (r *instanceResource) Create(
return
}
// After the instance creation, database might not be ready to accept connections immediately.
// That is why we add a sleep
// TODO - can get removed?
time.Sleep(120 * time.Second)
tflog.Info(ctx, "SQLServer Flex instance created")
}
@ -574,13 +554,20 @@ func (r *instanceResource) Read(
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
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 := model.ProjectId.ValueString()
@ -591,33 +578,6 @@ func (r *instanceResource) Read(
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
@ -632,7 +592,8 @@ func (r *instanceResource) Read(
ctx = core.LogResponse(ctx)
// Map response body to schema
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
// err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, instanceResp, &model, resp.Diagnostics)
if err != nil {
core.LogAndAddError(
ctx,
@ -648,6 +609,17 @@ func (r *instanceResource) Read(
if resp.Diagnostics.HasError() {
return
}
// Set data returned by API in identity
identity := InstanceResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "SQLServer Flex instance read")
}
@ -658,7 +630,7 @@ func (r *instanceResource) Update(
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -675,35 +647,8 @@ func (r *instanceResource) Update(
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Generate API request body from model
payload, err := toUpdatePayload(&model, storage, network)
payload, err := toUpdatePayload(ctx, &model, resp)
if err != nil {
core.LogAndAddError(
ctx,
@ -739,7 +684,8 @@ func (r *instanceResource) Update(
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, waitResp, &model, resp.Diagnostics)
// err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(
ctx,
@ -764,7 +710,7 @@ func (r *instanceResource) Delete(
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {

View file

@ -1,280 +0,0 @@
package sqlserverflex
import (
"context"
"reflect"
"testing"
"github.com/hashicorp/terraform-plugin-framework/resource"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
)
func TestNewInstanceResource(t *testing.T) {
tests := []struct {
name string
want resource.Resource
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewInstanceResource(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewInstanceResource() = %v, want %v", got, tt.want)
}
})
}
}
func Test_instanceResource_Configure(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ConfigureRequest
resp *resource.ConfigureResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Configure(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Create(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.CreateRequest
resp *resource.CreateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Create(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Delete(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.DeleteRequest
resp *resource.DeleteResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Delete(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_ImportState(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ImportStateRequest
resp *resource.ImportStateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.ImportState(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Metadata(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
in0 context.Context
req resource.MetadataRequest
resp *resource.MetadataResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Metadata(tt.args.in0, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_ModifyPlan(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ModifyPlanRequest
resp *resource.ModifyPlanResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.ModifyPlan(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Read(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ReadRequest
resp *resource.ReadResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Read(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Schema(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
in0 context.Context
in1 resource.SchemaRequest
resp *resource.SchemaResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Schema(tt.args.in0, tt.args.in1, tt.args.resp)
})
}
}
func Test_instanceResource_Update(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.UpdateRequest
resp *resource.UpdateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Update(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}

View file

@ -26,6 +26,11 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
MarkdownDescription: "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
},
"edition": schema.StringAttribute{
Computed: true,
Description: "Edition of the MSSQL server instance",
MarkdownDescription: "Edition of the MSSQL server instance",
},
"encryption": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"kek_key_id": schema.StringAttribute{
@ -73,6 +78,11 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.",
},
"is_deletable": schema.BoolAttribute{
Computed: true,
Description: "Whether the instance can be deleted or not.",
MarkdownDescription: "Whether the instance can be deleted or not.",
},
"name": schema.StringAttribute{
Required: true,
Description: "The name of the instance.",
@ -99,6 +109,12 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "List of IPV4 cidr.",
MarkdownDescription: "List of IPV4 cidr.",
},
"instance_address": schema.StringAttribute{
Computed: true,
},
"router_address": schema.StringAttribute{
Computed: true,
},
},
CustomType: NetworkType{
ObjectType: types.ObjectType{
@ -126,11 +142,19 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
),
},
},
"replicas": schema.Int64Attribute{
Computed: true,
Description: "How many replicas the instance should have.",
MarkdownDescription: "How many replicas the instance should have.",
},
"retention_days": schema.Int64Attribute{
Required: true,
Description: "The days for how long the backup files should be stored before cleaned up. 30 to 365",
MarkdownDescription: "The days for how long the backup files should be stored before cleaned up. 30 to 365",
},
"status": schema.StringAttribute{
Computed: true,
},
"storage": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
@ -169,15 +193,19 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
type InstanceModel struct {
BackupSchedule types.String `tfsdk:"backup_schedule"`
Edition types.String `tfsdk:"edition"`
Encryption EncryptionValue `tfsdk:"encryption"`
FlavorId types.String `tfsdk:"flavor_id"`
Id types.String `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"`
IsDeletable types.Bool `tfsdk:"is_deletable"`
Name types.String `tfsdk:"name"`
Network NetworkValue `tfsdk:"network"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Replicas types.Int64 `tfsdk:"replicas"`
RetentionDays types.Int64 `tfsdk:"retention_days"`
Status types.String `tfsdk:"status"`
Storage StorageValue `tfsdk:"storage"`
Version types.String `tfsdk:"version"`
}
@ -732,14 +760,52 @@ func (t NetworkType) ValueFromObject(ctx context.Context, in basetypes.ObjectVal
fmt.Sprintf(`acl expected to be basetypes.ListValue, was: %T`, aclAttribute))
}
instanceAddressAttribute, ok := attributes["instance_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`instance_address is missing from object`)
return nil, diags
}
instanceAddressVal, ok := instanceAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`instance_address expected to be basetypes.StringValue, was: %T`, instanceAddressAttribute))
}
routerAddressAttribute, ok := attributes["router_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`router_address is missing from object`)
return nil, diags
}
routerAddressVal, ok := routerAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router_address expected to be basetypes.StringValue, was: %T`, routerAddressAttribute))
}
if diags.HasError() {
return nil, diags
}
return NetworkValue{
AccessScope: accessScopeVal,
Acl: aclVal,
state: attr.ValueStateKnown,
AccessScope: accessScopeVal,
Acl: aclVal,
InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags
}
@ -842,14 +908,52 @@ func NewNetworkValue(attributeTypes map[string]attr.Type, attributes map[string]
fmt.Sprintf(`acl expected to be basetypes.ListValue, was: %T`, aclAttribute))
}
instanceAddressAttribute, ok := attributes["instance_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`instance_address is missing from object`)
return NewNetworkValueUnknown(), diags
}
instanceAddressVal, ok := instanceAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`instance_address expected to be basetypes.StringValue, was: %T`, instanceAddressAttribute))
}
routerAddressAttribute, ok := attributes["router_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`router_address is missing from object`)
return NewNetworkValueUnknown(), diags
}
routerAddressVal, ok := routerAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router_address expected to be basetypes.StringValue, was: %T`, routerAddressAttribute))
}
if diags.HasError() {
return NewNetworkValueUnknown(), diags
}
return NetworkValue{
AccessScope: accessScopeVal,
Acl: aclVal,
state: attr.ValueStateKnown,
AccessScope: accessScopeVal,
Acl: aclVal,
InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags
}
@ -921,13 +1025,15 @@ func (t NetworkType) ValueType(ctx context.Context) attr.Value {
var _ basetypes.ObjectValuable = NetworkValue{}
type NetworkValue struct {
AccessScope basetypes.StringValue `tfsdk:"access_scope"`
Acl basetypes.ListValue `tfsdk:"acl"`
state attr.ValueState
AccessScope basetypes.StringValue `tfsdk:"access_scope"`
Acl basetypes.ListValue `tfsdk:"acl"`
InstanceAddress basetypes.StringValue `tfsdk:"instance_address"`
RouterAddress basetypes.StringValue `tfsdk:"router_address"`
state attr.ValueState
}
func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
attrTypes := make(map[string]tftypes.Type, 4)
var val tftypes.Value
var err error
@ -936,12 +1042,14 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
attrTypes["acl"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["instance_address"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["router_address"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 2)
vals := make(map[string]tftypes.Value, 4)
val, err = v.AccessScope.ToTerraformValue(ctx)
@ -959,6 +1067,22 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
vals["acl"] = val
val, err = v.InstanceAddress.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["instance_address"] = val
val, err = v.RouterAddress.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["router_address"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
@ -1006,6 +1130,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}), diags
}
@ -1014,6 +1140,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
if v.IsNull() {
@ -1027,8 +1155,10 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"access_scope": v.AccessScope,
"acl": aclVal,
"access_scope": v.AccessScope,
"acl": aclVal,
"instance_address": v.InstanceAddress,
"router_address": v.RouterAddress,
})
return objVal, diags
@ -1057,6 +1187,14 @@ func (v NetworkValue) Equal(o attr.Value) bool {
return false
}
if !v.InstanceAddress.Equal(other.InstanceAddress) {
return false
}
if !v.RouterAddress.Equal(other.RouterAddress) {
return false
}
return true
}
@ -1074,6 +1212,8 @@ func (v NetworkValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
}

View file

@ -44,17 +44,24 @@ func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
}
switch strings.ToLower(string(*s.Status)) {
case strings.ToLower(InstanceStateSuccess):
if s.Network.InstanceAddress == nil {
tflog.Info(ctx, "Waiting for instance_address")
return false, nil, nil
}
if s.Network.RouterAddress == nil {
tflog.Info(ctx, "Waiting for router_address")
return false, nil, nil
if *s.Network.AccessScope == "SNA" {
if s.Network.InstanceAddress == nil {
tflog.Info(ctx, "Waiting for instance_address")
return false, nil, nil
}
if s.Network.RouterAddress == nil {
tflog.Info(ctx, "Waiting for router_address")
return false, nil, nil
}
}
return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("create failed for instance with id %s", instanceId)
case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing):
tflog.Info(ctx, "request is being handled", map[string]interface{}{
"status": *s.Status,
})
return false, nil, nil
default:
tflog.Info(ctx, "Wait (create) received unknown status", map[string]interface{}{
"instanceId": instanceId,
@ -63,8 +70,6 @@ func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return false, s, nil
}
})
handler.SetTimeout(45 * time.Minute)
handler.SetSleepBeforeWait(15 * time.Second)
return handler
}
@ -83,6 +88,11 @@ func UpdateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("update failed for instance with id %s", instanceId)
case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing):
tflog.Info(ctx, "request is being handled", map[string]interface{}{
"status": *s.Status,
})
return false, nil, nil
default:
tflog.Info(ctx, "Wait (update) received unknown status", map[string]interface{}{
"instanceId": instanceId,