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 .secrets
pkg_gen pkg_gen
/release/

View file

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

View file

@ -3,6 +3,7 @@ package {{.PackageName}}
import ( import (
"context" "context"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
@ -17,6 +18,7 @@ var (
_ resource.ResourceWithConfigure = &{{.NameCamel}}Resource{} _ resource.ResourceWithConfigure = &{{.NameCamel}}Resource{}
_ resource.ResourceWithImportState = &{{.NameCamel}}Resource{} _ resource.ResourceWithImportState = &{{.NameCamel}}Resource{}
_ resource.ResourceWithModifyPlan = &{{.NameCamel}}Resource{} _ resource.ResourceWithModifyPlan = &{{.NameCamel}}Resource{}
_ resource.ResourceWithIdentity = &{{.NameCamel}}Resource{}
) )
func New{{.NamePascal}}Resource() resource.Resource { func New{{.NamePascal}}Resource() resource.Resource {
@ -28,6 +30,13 @@ type {{.NameCamel}}Resource struct{
providerData core.ProviderData 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) { func (r *{{.NameCamel}}Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_{{.PackageName}}_{{.NameSnake}}" 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) 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. // Configure adds the provider configured client to the resource.
func (r *{{.NameCamel}}Resource) Configure( func (r *{{.NameCamel}}Resource) Configure(
ctx context.Context, ctx context.Context,
@ -81,6 +107,19 @@ func (r *{{.NameCamel}}Resource) Create(ctx context.Context, req resource.Create
// Example data value setting // Example data value setting
data.{{.NameCamel | ucfirst}}Id = types.StringValue("id-from-response") 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 // Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) 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 // Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...) 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() { if resp.Diagnostics.HasError() {
return return
} }

View file

@ -5,13 +5,29 @@ import (
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd/build" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd/build"
) )
func NewBuildCmd() *cobra.Command { var (
return &cobra.Command{ skipCleanup bool
Use: "build", skipClone bool
Short: "Build the necessary boilerplate", )
Long: `...`,
RunE: func(cmd *cobra.Command, args []string) error { var buildCmd = &cobra.Command{
return build.Build() 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 ( import (
"log" "log"
"log/slog"
"os" "os"
"github.com/MatusOllah/slogcolor"
cc "github.com/ivanpirog/coloredcobra"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/cmd/cmd"
) )
func main() { func main() {
rootCmd := cmd.NewRootCmd() slog.SetDefault(slog.New(slogcolor.NewHandler(os.Stderr, slogcolor.DefaultOptions)))
//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")
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.SetOut(os.Stdout)
rootCmd.AddCommand( rootCmd.AddCommand(

View file

@ -30,11 +30,12 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
### Read-Only ### 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. - `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)) - `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. - `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. - `flavor_id` (String) The id of the instance flavor.
- `id` (String) The ID of the instance. - `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not. - `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" page_title: "stackitprivatepreview_sqlserverflexalpha_instance Data Source - stackitprivatepreview"
subcategory: "" subcategory: ""
description: |- description: |-
SQLServer Flex ALPHA instance resource schema. Must have a region specified in the provider configuration.
--- ---
# stackitprivatepreview_sqlserverflexalpha_instance (Data Source) # stackitprivatepreview_sqlserverflexalpha_instance (Data Source)
SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.
## Example Usage ## Example Usage
@ -24,61 +24,48 @@ data "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
### Required ### Required
- `instance_id` (String) ID of the SQLServer Flex instance. - `instance_id` (String) The ID of the instance.
- `project_id` (String) STACKIT project ID to which the instance is associated. - `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only ### Read-Only
- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *") - `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` (String) Edition of the MSSQL server instance
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption)) - `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor)) - `flavor_id` (String) The id of the instance flavor.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`". - `id` (String) The ID of the instance.
- `is_deletable` (Boolean) - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) Instance name. - `name` (String) The name of the instance.
- `network` (Attributes) The network block. (see [below for nested schema](#nestedatt--network)) - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) - `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) - `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `status` (String) - `status` (String)
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `version` (String) - `version` (String) The sqlserver version used for the instance.
<a id="nestedatt--encryption"></a> <a id="nestedatt--encryption"></a>
### Nested Schema for `encryption` ### Nested Schema for `encryption`
Read-Only: Read-Only:
- `key_id` (String) STACKIT KMS - Key ID of the encryption key to use. - `kek_key_id` (String) The key identifier
- `key_version` (String) STACKIT KMS - Key version to use in the encryption key. - `kek_key_ring_id` (String) The keyring identifier
- `keyring_id` (String) STACKIT KMS - KeyRing ID of the encryption key to use. - `kek_key_version` (String) The key version
- `service_account` (String) - `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> <a id="nestedatt--network"></a>
### Nested Schema for `network` ### Nested Schema for `network`
Read-Only: Read-Only:
- `access_scope` (String) The access scope of the instance. (e.g. SNA) - `access_scope` (String) The network access scope of the instance
- `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. ⚠️ **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.
- `router_address` (String) The returned router IP address of the SQLServer Flex instance. - `acl` (List of String) List of IPV4 cidr.
- `instance_address` (String)
- `router_address` (String)
<a id="nestedatt--storage"></a> <a id="nestedatt--storage"></a>
@ -86,5 +73,5 @@ Read-Only:
Read-Only: Read-Only:
- `class` (String) - `class` (String) The storage class for the storage.
- `size` (Number) - `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. - `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. - `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID. - `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed - `region` (String) The region which should be addressed
### Read-Only ### 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)) - `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. - `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
@ -77,6 +78,9 @@ Required:
Optional: Optional:
- `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped. - `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped.
Read-Only:
- `instance_address` (String) - `instance_address` (String)
- `router_address` (String) - `router_address` (String)

View file

@ -3,12 +3,12 @@
page_title: "stackitprivatepreview_sqlserverflexalpha_instance Resource - stackitprivatepreview" page_title: "stackitprivatepreview_sqlserverflexalpha_instance Resource - stackitprivatepreview"
subcategory: "" subcategory: ""
description: |- description: |-
SQLServer Flex ALPHA instance resource schema. Must have a region specified in the provider configuration.
--- ---
# stackitprivatepreview_sqlserverflexalpha_instance (Resource) # stackitprivatepreview_sqlserverflexalpha_instance (Resource)
SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.
## Example Usage ## Example Usage
@ -41,41 +41,55 @@ import {
### Required ### Required
- `flavor_id` (String) - `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.
- `name` (String) Instance name. - `flavor_id` (String) The id of the instance flavor.
- `network` (Attributes) The network block. (see [below for nested schema](#nestedatt--network)) - `name` (String) The name of the instance.
- `project_id` (String) STACKIT project ID to which the instance is associated. - `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 ### Optional
- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *") - `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption)) - `instance_id` (String) The ID of the instance.
- `is_deletable` (Boolean) - `project_id` (String) The STACKIT project ID.
- `region` (String) The resource region. If not defined, the provider region is used. - `region` (String) The region which should be addressed
- `retention_days` (Number)
- `status` (String)
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
- `version` (String)
### Read-Only ### Read-Only
- `edition` (String) - `edition` (String) Edition of the MSSQL server instance
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`". - `id` (String) The ID of the instance.
- `instance_id` (String) ID of the SQLServer Flex instance. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `replicas` (Number) - `replicas` (Number) How many replicas the instance should have.
- `status` (String)
<a id="nestedatt--network"></a> <a id="nestedatt--network"></a>
### Nested Schema for `network` ### Nested Schema for `network`
Required: Required:
- `access_scope` (String) The access scope of the instance. (SNA | PUBLIC) - `acl` (List of String) List of IPV4 cidr.
- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
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: Read-Only:
- `instance_address` (String) The returned instance IP address of the SQLServer Flex instance. - `instance_address` (String)
- `router_address` (String) The returned router IP address of the SQLServer Flex instance. - `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> <a id="nestedatt--encryption"></a>
@ -83,16 +97,7 @@ Read-Only:
Required: Required:
- `key_id` (String) STACKIT KMS - Key ID of the encryption key to use. - `kek_key_id` (String) The key identifier
- `key_version` (String) STACKIT KMS - Key version to use in the encryption key. - `kek_key_ring_id` (String) The keyring identifier
- `keyring_id` (String) STACKIT KMS - KeyRing ID of the encryption key to use. - `kek_key_version` (String) The key version
- `service_account` (String) - `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 go 1.25.6
require ( require (
github.com/MatusOllah/slogcolor v1.7.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/terraform-plugin-framework v1.17.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-log v0.10.0
github.com/hashicorp/terraform-plugin-testing v1.14.0 github.com/hashicorp/terraform-plugin-testing v1.14.0
github.com/iancoleman/strcase v0.3.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/ldez/go-git-cmd-wrapper/v2 v2.9.1
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/stackitcloud/stackit-sdk-go/core v0.21.0 github.com/stackitcloud/stackit-sdk-go/core v0.21.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 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 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/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 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 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/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 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/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 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= 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/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 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 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 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 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.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/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 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 #keyring_id = stackit_kms_keyring.keyring.keyring_id
#key_version = 1 #key_version = 1
# key with scope public # key with scope public
key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b" kek_key_id = "fe039bcf-8d7b-431a-801d-9e81371a6b7b"
# key_id = var.key_id # key_id = var.key_id
keyring_id = var.keyring_id kek_key_ring_id = var.keyring_id
key_version = var.key_version kek_key_version = var.key_version
service_account = var.sa_email service_account = var.sa_email
} }
network = { network = {

View file

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

View file

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

View file

@ -7,21 +7,17 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/hashicorp/terraform-plugin-framework/types"
"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/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" 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" 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/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog" "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/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/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. // 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. // Schema defines the schema for the data source.
func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{ //descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.", // "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`\".", // "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.", // "instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", // "project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.", // "name": "Instance name.",
"access_scope": "The access scope of the instance. (e.g. SNA)", // "access_scope": "The access scope of the instance. (e.g. SNA)",
"acl": "The Access Control List (ACL) for the SQLServer Flex 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 * * *")`, // "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.", // "region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.", // "encryption": "The encryption block.",
"network": "The network block.", // "network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.", // "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_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.", // "key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account 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.", // "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router IP address of the SQLServer Flex instance.", // "router_address": "The returned router IP address of the SQLServer Flex instance.",
} //}
resp.Schema = schema.Schema{ resp.Schema = sqlserverflexalpha.InstanceDataSourceSchema(ctx)
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{ //resp.Schema = schema.Schema{
"id": schema.StringAttribute{ // Description: descriptions["main"],
Description: descriptions["id"], // Attributes: map[string]schema.Attribute{
Computed: true, // "id": schema.StringAttribute{
}, // Description: descriptions["id"],
"instance_id": schema.StringAttribute{ // Computed: true,
Description: descriptions["instance_id"], // },
Required: true, // "instance_id": schema.StringAttribute{
Validators: []validator.String{ // Description: descriptions["instance_id"],
validate.UUID(), // Required: true,
validate.NoSeparator(), // Validators: []validator.String{
}, // validate.UUID(),
}, // validate.NoSeparator(),
"project_id": schema.StringAttribute{ // },
Description: descriptions["project_id"], // },
Required: true, // "project_id": schema.StringAttribute{
Validators: []validator.String{ // Description: descriptions["project_id"],
validate.UUID(), // Required: true,
validate.NoSeparator(), // Validators: []validator.String{
}, // validate.UUID(),
}, // validate.NoSeparator(),
"name": schema.StringAttribute{ // },
Description: descriptions["name"], // },
Computed: true, // "name": schema.StringAttribute{
}, // Description: descriptions["name"],
"backup_schedule": schema.StringAttribute{ // Computed: true,
Description: descriptions["backup_schedule"], // },
Computed: true, // "backup_schedule": schema.StringAttribute{
}, // Description: descriptions["backup_schedule"],
"is_deletable": schema.BoolAttribute{ // Computed: true,
Description: descriptions["is_deletable"], // },
Computed: true, // "is_deletable": schema.BoolAttribute{
}, // Description: descriptions["is_deletable"],
"flavor": schema.SingleNestedAttribute{ // Computed: true,
Computed: true, // },
Attributes: map[string]schema.Attribute{ // "flavor": schema.SingleNestedAttribute{
"id": schema.StringAttribute{ // Computed: true,
Computed: true, // Attributes: map[string]schema.Attribute{
}, // "id": schema.StringAttribute{
"description": schema.StringAttribute{ // Computed: true,
Computed: true, // },
}, // "description": schema.StringAttribute{
"cpu": schema.Int64Attribute{ // Computed: true,
Computed: true, // },
}, // "cpu": schema.Int64Attribute{
"ram": schema.Int64Attribute{ // Computed: true,
Computed: true, // },
}, // "ram": schema.Int64Attribute{
"node_type": schema.StringAttribute{ // Computed: true,
Computed: true, // },
}, // "node_type": schema.StringAttribute{
}, // Computed: true,
}, // },
"replicas": schema.Int64Attribute{ // },
Computed: true, // },
}, // "replicas": schema.Int64Attribute{
"storage": schema.SingleNestedAttribute{ // Computed: true,
Computed: true, // },
Attributes: map[string]schema.Attribute{ // "storage": schema.SingleNestedAttribute{
"class": schema.StringAttribute{ // Computed: true,
Computed: true, // Attributes: map[string]schema.Attribute{
}, // "class": schema.StringAttribute{
"size": schema.Int64Attribute{ // Computed: true,
Computed: true, // },
}, // "size": schema.Int64Attribute{
}, // Computed: true,
}, // },
"version": schema.StringAttribute{ // },
Computed: true, // },
}, // "version": schema.StringAttribute{
"status": schema.StringAttribute{ // Computed: true,
Computed: true, // },
}, // "status": schema.StringAttribute{
"edition": schema.StringAttribute{ // Computed: true,
Computed: true, // },
}, // "edition": schema.StringAttribute{
"retention_days": schema.Int64Attribute{ // Computed: true,
Computed: true, // },
}, // "retention_days": schema.Int64Attribute{
"region": schema.StringAttribute{ // Computed: true,
// the region cannot be found, so it has to be passed // },
Optional: true, // "region": schema.StringAttribute{
Description: descriptions["region"], // // the region cannot be found, so it has to be passed
}, // Optional: true,
"encryption": schema.SingleNestedAttribute{ // Description: descriptions["region"],
Computed: true, // },
Attributes: map[string]schema.Attribute{ // "encryption": schema.SingleNestedAttribute{
"key_id": schema.StringAttribute{ // Computed: true,
Description: descriptions["key_id"], // Attributes: map[string]schema.Attribute{
Computed: true, // "key_id": schema.StringAttribute{
}, // Description: descriptions["key_id"],
"key_version": schema.StringAttribute{ // Computed: true,
Description: descriptions["key_version"], // },
Computed: true, // "key_version": schema.StringAttribute{
}, // Description: descriptions["key_version"],
"keyring_id": schema.StringAttribute{ // Computed: true,
Description: descriptions["keyring_id"], // },
Computed: true, // "keyring_id": schema.StringAttribute{
}, // Description: descriptions["keyring_id"],
"service_account": schema.StringAttribute{ // Computed: true,
Description: descriptions["service_account"], // },
Computed: true, // "service_account": schema.StringAttribute{
}, // Description: descriptions["service_account"],
}, // Computed: true,
Description: descriptions["encryption"], // },
}, // },
"network": schema.SingleNestedAttribute{ // Description: descriptions["encryption"],
Computed: true, // },
Attributes: map[string]schema.Attribute{ // "network": schema.SingleNestedAttribute{
"access_scope": schema.StringAttribute{ // Computed: true,
Description: descriptions["access_scope"], // Attributes: map[string]schema.Attribute{
Computed: true, // "access_scope": schema.StringAttribute{
}, // Description: descriptions["access_scope"],
"instance_address": schema.StringAttribute{ // Computed: true,
Description: descriptions["instance_address"], // },
Computed: true, // "instance_address": schema.StringAttribute{
}, // Description: descriptions["instance_address"],
"router_address": schema.StringAttribute{ // Computed: true,
Description: descriptions["router_address"], // },
Computed: true, // "router_address": schema.StringAttribute{
}, // Description: descriptions["router_address"],
"acl": schema.ListAttribute{ // Computed: true,
Description: descriptions["acl"], // },
ElementType: types.StringType, // "acl": schema.ListAttribute{
Computed: true, // Description: descriptions["acl"],
}, // ElementType: types.StringType,
}, // Computed: true,
Description: descriptions["network"], // },
}, // },
}, // Description: descriptions["network"],
} // },
// },
//}
} }
// Read refreshes the Terraform state with the latest data. // 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 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) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -253,34 +251,35 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
var storage = &storageModel{} //var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() { //if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{}) // diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...) // resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { // if resp.Diagnostics.HasError() {
return // 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{} err = mapResponseToModel(ctx, instanceResp, &model, resp.Diagnostics)
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() { //err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
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)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return return

View file

@ -6,202 +6,299 @@ import (
"math" "math"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha" 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/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" sqlserverflexResGen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
) )
func mapFields( func mapResponseToModel(
ctx context.Context, ctx context.Context,
resp *sqlserverflex.GetInstanceResponse, resp *sqlserverflex.GetInstanceResponse,
model *Model, m *sqlserverflexResGen.InstanceModel,
storage *storageModel, tfDiags diag.Diagnostics,
encryption *encryptionModel,
network *networkModel,
region string,
) error { ) error {
if resp == nil { m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
return fmt.Errorf("response input is nil") m.Edition = types.StringValue(string(resp.GetEdition()))
} m.Encryption = handleEncryption(m, resp)
if model == nil { m.FlavorId = types.StringValue(resp.GetFlavorId())
return fmt.Errorf("model input is nil") m.Id = types.StringValue(resp.GetId())
} m.InstanceId = types.StringValue(resp.GetId())
instance := resp m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
m.Name = types.StringValue(resp.GetName())
var instanceId string netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
if model.InstanceId.ValueString() != "" { tfDiags.Append(diags...)
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() { if diags.HasError() {
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags)) return fmt.Errorf(
"error converting network acl response value",
)
} }
net, diags := sqlserverflexResGen.NewNetworkValue(
var encryptionValues map[string]attr.Value sqlserverflexResGen.NetworkValue{}.AttributeTypes(ctx),
if instance.Encryption == nil { map[string]attr.Value{
encryptionValues = map[string]attr.Value{ "access_scope": types.StringValue(string(resp.Network.GetAccessScope())),
"keyring_id": encryption.KeyRingId, "acl": netAcl,
"key_id": encryption.KeyId, "instance_address": types.StringValue(resp.Network.GetInstanceAddress()),
"key_version": encryption.KeyVersion, "router_address": types.StringValue(resp.Network.GetRouterAddress()),
"service_account": encryption.ServiceAccount, },
} )
} else { tfDiags.Append(diags...)
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() { 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 stor, diags := sqlserverflexResGen.NewStorageValue(
if instance.Network == nil { sqlserverflexResGen.StorageValue{}.AttributeTypes(ctx),
networkValues = map[string]attr.Value{ map[string]attr.Value{
"acl": network.ACL, "class": types.StringValue(resp.Storage.GetClass()),
"access_scope": network.AccessScope, "size": types.Int64Value(resp.Storage.GetSize()),
"instance_address": network.InstanceAddress, },
"router_address": network.RouterAddress, )
} tfDiags.Append(diags...)
} 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() { 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()) m.Version = types.StringValue(string(resp.GetVersion()))
// 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 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( func toCreatePayload(
model *Model, ctx context.Context,
storage *storageModel, model *sqlserverflexResGen.InstanceModel,
encryption *encryptionModel,
network *networkModel,
) (*sqlserverflex.CreateInstanceRequestPayload, error) { ) (*sqlserverflex.CreateInstanceRequestPayload, error) {
if model == nil { if model == nil {
return nil, fmt.Errorf("nil model") return nil, fmt.Errorf("nil model")
} }
storagePayload := &sqlserverflex.CreateInstanceRequestPayloadGetStorageArgType{} storagePayload := &sqlserverflex.CreateInstanceRequestPayloadGetStorageArgType{}
if storage != nil { if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
storagePayload.Class = conversion.StringValueToPointer(storage.Class) storagePayload.Class = model.Storage.Class.ValueStringPointer()
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size) storagePayload.Size = model.Storage.Size.ValueInt64Pointer()
} }
var encryptionPayload *sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType var encryptionPayload *sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType = nil
if encryption != nil && if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() &&
!encryption.KeyId.IsNull() && !encryption.KeyId.IsUnknown() && !model.Encryption.KekKeyId.IsNull() && model.Encryption.KekKeyId.IsUnknown() && model.Encryption.KekKeyId.ValueString() != "" &&
!encryption.KeyRingId.IsNull() && !encryption.KeyRingId.IsUnknown() && !model.Encryption.KekKeyRingId.IsNull() && !model.Encryption.KekKeyRingId.IsUnknown() && model.Encryption.KekKeyRingId.ValueString() != "" &&
!encryption.KeyVersion.IsNull() && !encryption.KeyVersion.IsUnknown() && !model.Encryption.KekKeyVersion.IsNull() && !model.Encryption.KekKeyVersion.IsUnknown() && model.Encryption.KekKeyVersion.ValueString() != "" &&
!encryption.ServiceAccount.IsNull() && !encryption.ServiceAccount.IsUnknown() { !model.Encryption.ServiceAccount.IsNull() && !model.Encryption.ServiceAccount.IsUnknown() && model.Encryption.ServiceAccount.ValueString() != "" {
encryptionPayload = &sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType{ encryptionPayload = &sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType{
KekKeyId: conversion.StringValueToPointer(encryption.KeyId), KekKeyId: model.Encryption.KekKeyId.ValueStringPointer(),
KekKeyRingId: conversion.StringValueToPointer(encryption.KeyVersion), KekKeyRingId: model.Encryption.KekKeyVersion.ValueStringPointer(),
KekKeyVersion: conversion.StringValueToPointer(encryption.KeyRingId), KekKeyVersion: model.Encryption.KekKeyRingId.ValueStringPointer(),
ServiceAccount: conversion.StringValueToPointer(encryption.ServiceAccount), ServiceAccount: model.Encryption.ServiceAccount.ValueStringPointer(),
}
}
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.CreateInstanceRequestPayloadGetNetworkArgType{} networkPayload := &sqlserverflex.CreateInstanceRequestPayloadGetNetworkArgType{}
if network != nil { if !model.Network.IsNull() && !model.Network.IsUnknown() {
networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope)) networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(
networkPayload.Acl = &aclElements 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{ return &sqlserverflex.CreateInstanceRequestPayload{
@ -216,66 +313,80 @@ func toCreatePayload(
}, nil }, nil
} }
//nolint:unused // TODO: remove if not needed later ////nolint:unused // TODO: remove if not needed later
func toUpdatePartiallyPayload( //func toUpdatePartiallyPayload(
model *Model, // model *Model,
storage *storageModel, // storage *storageModel,
network *networkModel, // network *networkModel,
) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) { //) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
if model == nil { // if model == nil {
return nil, fmt.Errorf("nil model") // return nil, fmt.Errorf("nil model")
} // }
//
storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{} // storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
if storage != nil { // if storage != nil {
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size) // storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
} // }
//
var aclElements []string // var aclElements []string
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() { // if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
aclElements = make([]string, 0, len(network.ACL.Elements())) // aclElements = make([]string, 0, len(network.ACL.Elements()))
diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false) // diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
if diags.HasError() { // if diags.HasError() {
return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags)) // return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
} // }
} // }
//
networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{} // networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{}
if network != nil { // if network != nil {
networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope)) // networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
networkPayload.Acl = &aclElements // networkPayload.Acl = &aclElements
} // }
//
if model.Replicas.ValueInt64() > math.MaxInt32 { // if model.Replicas.ValueInt64() > math.MaxInt32 {
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64()) // return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
} // }
replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above // replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
return &sqlserverflex.UpdateInstancePartiallyRequestPayload{ // return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), // BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
FlavorId: conversion.StringValueToPointer(model.FlavorId), // FlavorId: conversion.StringValueToPointer(model.FlavorId),
Name: conversion.StringValueToPointer(model.Name), // Name: conversion.StringValueToPointer(model.Name),
Network: networkPayload, // Network: networkPayload,
Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount), // Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays), // RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
Storage: storagePayload, // Storage: storagePayload,
Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)), // Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
}, nil // }, nil
} //}
// TODO: check func with his args // TODO: check func with his args
func toUpdatePayload( func toUpdatePayload(
_ *Model, ctx context.Context,
_ *storageModel, m *sqlserverflexResGen.InstanceModel,
_ *networkModel, resp *resource.UpdateResponse,
) (*sqlserverflex.UpdateInstanceRequestPayload, error) { ) (*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{ return &sqlserverflex.UpdateInstanceRequestPayload{
BackupSchedule: nil, BackupSchedule: m.BackupSchedule.ValueStringPointer(),
FlavorId: nil, FlavorId: m.FlavorId.ValueStringPointer(),
Name: nil, Name: m.Name.ValueStringPointer(),
Network: nil, Network: &sqlserverflex.CreateInstanceRequestPayloadNetwork{
Replicas: nil, AccessScope: sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(m.Network.AccessScope.ValueStringPointer()),
RetentionDays: nil, Acl: &netAcl,
Storage: nil, },
Version: nil, Replicas: &replVal,
RetentionDays: m.RetentionDays.ValueInt64Pointer(),
Storage: &sqlserverflex.StorageUpdate{Size: m.Storage.Size.ValueInt64Pointer()},
Version: sqlserverflex.UpdateInstanceRequestPayloadGetVersionAttributeType(m.Version.ValueStringPointer()),
}, nil }, nil
} }

View file

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

View file

@ -4,35 +4,25 @@ package sqlserverflex
import ( import (
"context" "context"
_ "embed"
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"time" "time"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" 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" 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/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "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/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/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/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" wait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/sqlserverflexalpha"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
@ -44,6 +34,7 @@ var (
_ resource.ResourceWithConfigure = &instanceResource{} _ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{} _ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{} _ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.ResourceWithIdentity = &instanceResource{}
) )
//nolint:unused // TODO: remove if not needed later //nolint:unused // TODO: remove if not needed later
@ -52,63 +43,10 @@ var validNodeTypes []string = []string{
"Replica", "Replica",
} }
type Model struct { type InstanceResourceIdentityModel struct {
Id types.String `tfsdk:"id"` // needed by TF ProjectID types.String `tfsdk:"project_id"`
InstanceId types.String `tfsdk:"instance_id"` Region types.String `tfsdk:"region"`
ProjectId types.String `tfsdk:"project_id"` InstanceID types.String `tfsdk:"instance_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{},
} }
// NewInstanceResource is a helper function to simplify the provider implementation. // NewInstanceResource is a helper function to simplify the provider implementation.
@ -154,17 +92,21 @@ func (r *instanceResource) ModifyPlan(
req resource.ModifyPlanRequest, req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse, resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors // skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() { if req.Config.Raw.IsNull() {
return return
} }
var configModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
var planModel Model if req.Plan.Raw.IsNull() {
return
}
var planModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@ -175,257 +117,303 @@ func (r *instanceResource) ModifyPlan(
return 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)...) resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
} }
//go:embed planModifiers.yaml
var modifiersFileByte []byte
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{ //descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.", // "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`\".", // "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.", // "instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", // "project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.", // "name": "Instance name.",
"access_scope": "The access scope of the instance. (SNA | PUBLIC)", // "access_scope": "The access scope of the instance. (SNA | PUBLIC)",
"flavor_id": "The flavor ID of the instance.", // "flavor_id": "The flavor ID of the instance.",
"acl": "The Access Control List (ACL) for the SQLServer Flex 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 * * *")`, // "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.", // "region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.", // "encryption": "The encryption block.",
"replicas": "The number of replicas of the SQLServer Flex instance.", // "replicas": "The number of replicas of the SQLServer Flex instance.",
"network": "The network block.", // "network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.", // "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_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.", // "key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account 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.", // "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router 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{ err = postgresflexUtils.AddPlanModifiersToResourceSchema(fields, &schema)
Description: descriptions["main"], if err != nil {
Attributes: map[string]schema.Attribute{ resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
"id": schema.StringAttribute{ return
Description: descriptions["id"], }
Computed: true, resp.Schema = schema
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(), //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{ "region": identityschema.StringAttribute{
Description: descriptions["instance_id"], RequiredForImport: true, // can be defaulted by the provider configuration
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
}, },
"project_id": schema.StringAttribute{ "instance_id": identityschema.StringAttribute{
Description: descriptions["project_id"], RequiredForImport: true, // can be defaulted by the provider configuration
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"],
}, },
}, },
} }
@ -437,49 +425,29 @@ func (r *instanceResource) Create(
req resource.CreateRequest, req resource.CreateRequest,
resp *resource.CreateResponse, resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId := identityData.ProjectID.ValueString()
region := model.Region.ValueString() region := identityData.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region) 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 // Generate API request body from model
payload, err := toCreatePayload(&model, storage, encryption, network) payload, err := toCreatePayload(ctx, &model)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -503,6 +471,18 @@ func (r *instanceResource) Create(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
instanceId := *createResp.Id 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( utils.SetAndLogStateFields(
ctx, &resp.Diagnostics, &resp.State, map[string]any{ ctx, &resp.Diagnostics, &resp.State, map[string]any{
"id": utils.BuildInternalTerraformId(projectId, region, instanceId), "id": utils.BuildInternalTerraformId(projectId, region, instanceId),
@ -521,7 +501,11 @@ func (r *instanceResource) Create(
projectId, projectId,
instanceId, instanceId,
region, region,
).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx) ).SetSleepBeforeWait(
30 * time.Second,
).SetTimeout(
90 * time.Minute,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -543,7 +527,8 @@ func (r *instanceResource) Create(
} }
// Map response body to schema // 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 { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -560,11 +545,6 @@ func (r *instanceResource) Create(
return 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") tflog.Info(ctx, "SQLServer Flex instance created")
} }
@ -574,13 +554,20 @@ func (r *instanceResource) Read(
req resource.ReadRequest, req resource.ReadRequest,
resp *resource.ReadResponse, resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
@ -591,33 +578,6 @@ func (r *instanceResource) Read(
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) 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() instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
if err != nil { 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 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) ctx = core.LogResponse(ctx)
// Map response body to schema // 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 { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -648,6 +609,17 @@ func (r *instanceResource) Read(
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return 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") tflog.Info(ctx, "SQLServer Flex instance read")
} }
@ -658,7 +630,7 @@ func (r *instanceResource) Update(
resp *resource.UpdateResponse, resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan // Retrieve values from plan
var model Model var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -675,35 +647,8 @@ func (r *instanceResource) Update(
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) 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 // Generate API request body from model
payload, err := toUpdatePayload(&model, storage, network) payload, err := toUpdatePayload(ctx, &model, resp)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -739,7 +684,8 @@ func (r *instanceResource) Update(
} }
// Map response body to schema // 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 { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -764,7 +710,7 @@ func (r *instanceResource) Delete(
resp *resource.DeleteResponse, resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state // Retrieve values from state
var model Model var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { 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.", 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.", 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{ "encryption": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
"kek_key_id": schema.StringAttribute{ "kek_key_id": schema.StringAttribute{
@ -73,6 +78,11 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "The ID of the instance.", Description: "The ID of the instance.",
MarkdownDescription: "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{ "name": schema.StringAttribute{
Required: true, Required: true,
Description: "The name of the instance.", Description: "The name of the instance.",
@ -99,6 +109,12 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "List of IPV4 cidr.", Description: "List of IPV4 cidr.",
MarkdownDescription: "List of IPV4 cidr.", MarkdownDescription: "List of IPV4 cidr.",
}, },
"instance_address": schema.StringAttribute{
Computed: true,
},
"router_address": schema.StringAttribute{
Computed: true,
},
}, },
CustomType: NetworkType{ CustomType: NetworkType{
ObjectType: types.ObjectType{ 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{ "retention_days": schema.Int64Attribute{
Required: true, Required: true,
Description: "The days for how long the backup files should be stored before cleaned up. 30 to 365", 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", 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{ "storage": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{ "class": schema.StringAttribute{
@ -169,15 +193,19 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
type InstanceModel struct { type InstanceModel struct {
BackupSchedule types.String `tfsdk:"backup_schedule"` BackupSchedule types.String `tfsdk:"backup_schedule"`
Edition types.String `tfsdk:"edition"`
Encryption EncryptionValue `tfsdk:"encryption"` Encryption EncryptionValue `tfsdk:"encryption"`
FlavorId types.String `tfsdk:"flavor_id"` FlavorId types.String `tfsdk:"flavor_id"`
Id types.String `tfsdk:"id"` Id types.String `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"` InstanceId types.String `tfsdk:"instance_id"`
IsDeletable types.Bool `tfsdk:"is_deletable"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Network NetworkValue `tfsdk:"network"` Network NetworkValue `tfsdk:"network"`
ProjectId types.String `tfsdk:"project_id"` ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"` Region types.String `tfsdk:"region"`
Replicas types.Int64 `tfsdk:"replicas"`
RetentionDays types.Int64 `tfsdk:"retention_days"` RetentionDays types.Int64 `tfsdk:"retention_days"`
Status types.String `tfsdk:"status"`
Storage StorageValue `tfsdk:"storage"` Storage StorageValue `tfsdk:"storage"`
Version types.String `tfsdk:"version"` 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)) 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() { if diags.HasError() {
return nil, diags return nil, diags
} }
return NetworkValue{ return NetworkValue{
AccessScope: accessScopeVal, AccessScope: accessScopeVal,
Acl: aclVal, Acl: aclVal,
state: attr.ValueStateKnown, InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags }, 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)) 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() { if diags.HasError() {
return NewNetworkValueUnknown(), diags return NewNetworkValueUnknown(), diags
} }
return NetworkValue{ return NetworkValue{
AccessScope: accessScopeVal, AccessScope: accessScopeVal,
Acl: aclVal, Acl: aclVal,
state: attr.ValueStateKnown, InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags }, diags
} }
@ -921,13 +1025,15 @@ func (t NetworkType) ValueType(ctx context.Context) attr.Value {
var _ basetypes.ObjectValuable = NetworkValue{} var _ basetypes.ObjectValuable = NetworkValue{}
type NetworkValue struct { type NetworkValue struct {
AccessScope basetypes.StringValue `tfsdk:"access_scope"` AccessScope basetypes.StringValue `tfsdk:"access_scope"`
Acl basetypes.ListValue `tfsdk:"acl"` Acl basetypes.ListValue `tfsdk:"acl"`
state attr.ValueState 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) { 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 val tftypes.Value
var err error var err error
@ -936,12 +1042,14 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
attrTypes["acl"] = basetypes.ListType{ attrTypes["acl"] = basetypes.ListType{
ElemType: types.StringType, ElemType: types.StringType,
}.TerraformType(ctx) }.TerraformType(ctx)
attrTypes["instance_address"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["router_address"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes} objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state { switch v.state {
case attr.ValueStateKnown: case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 2) vals := make(map[string]tftypes.Value, 4)
val, err = v.AccessScope.ToTerraformValue(ctx) val, err = v.AccessScope.ToTerraformValue(ctx)
@ -959,6 +1067,22 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
vals["acl"] = val 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 { if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err return tftypes.NewValue(objectType, tftypes.UnknownValue), err
} }
@ -1006,6 +1130,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{ "acl": basetypes.ListType{
ElemType: types.StringType, ElemType: types.StringType,
}, },
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}), diags }), diags
} }
@ -1014,6 +1140,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{ "acl": basetypes.ListType{
ElemType: types.StringType, ElemType: types.StringType,
}, },
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
} }
if v.IsNull() { if v.IsNull() {
@ -1027,8 +1155,10 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
objVal, diags := types.ObjectValue( objVal, diags := types.ObjectValue(
attributeTypes, attributeTypes,
map[string]attr.Value{ map[string]attr.Value{
"access_scope": v.AccessScope, "access_scope": v.AccessScope,
"acl": aclVal, "acl": aclVal,
"instance_address": v.InstanceAddress,
"router_address": v.RouterAddress,
}) })
return objVal, diags return objVal, diags
@ -1057,6 +1187,14 @@ func (v NetworkValue) Equal(o attr.Value) bool {
return false return false
} }
if !v.InstanceAddress.Equal(other.InstanceAddress) {
return false
}
if !v.RouterAddress.Equal(other.RouterAddress) {
return false
}
return true return true
} }
@ -1074,6 +1212,8 @@ func (v NetworkValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
"acl": basetypes.ListType{ "acl": basetypes.ListType{
ElemType: types.StringType, 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)) { switch strings.ToLower(string(*s.Status)) {
case strings.ToLower(InstanceStateSuccess): case strings.ToLower(InstanceStateSuccess):
if s.Network.InstanceAddress == nil { if *s.Network.AccessScope == "SNA" {
tflog.Info(ctx, "Waiting for instance_address") if s.Network.InstanceAddress == nil {
return false, nil, nil tflog.Info(ctx, "Waiting for instance_address")
} return false, nil, nil
if s.Network.RouterAddress == nil { }
tflog.Info(ctx, "Waiting for router_address") if s.Network.RouterAddress == nil {
return false, nil, nil tflog.Info(ctx, "Waiting for router_address")
return false, nil, nil
}
} }
return true, s, nil return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("create failed for instance with id %s", instanceId) 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: default:
tflog.Info(ctx, "Wait (create) received unknown status", map[string]interface{}{ tflog.Info(ctx, "Wait (create) received unknown status", map[string]interface{}{
"instanceId": instanceId, "instanceId": instanceId,
@ -63,8 +70,6 @@ func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return false, s, nil return false, s, nil
} }
}) })
handler.SetTimeout(45 * time.Minute)
handler.SetSleepBeforeWait(15 * time.Second)
return handler return handler
} }
@ -83,6 +88,11 @@ func UpdateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return true, s, nil return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("update failed for instance with id %s", instanceId) 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: default:
tflog.Info(ctx, "Wait (update) received unknown status", map[string]interface{}{ tflog.Info(ctx, "Wait (update) received unknown status", map[string]interface{}{
"instanceId": instanceId, "instanceId": instanceId,