Support automatic kubernetes updates for stackit_ske_cluster (#360)
* new field kubernets_version_min and deprecate kubernetes_version * Fix lint and tests * Update acc test * Deprecate datasource field, fix checkAllowPrivilegedContainers * Update acc test, datasource and descriptions * Update acc test * Improve descriptions, fix bug * Improve docs, fix acc test * Update docs * Update docs, fix acc test * Update stackit/internal/services/ske/cluster/resource.go Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com> * Fix links * Default ske auto-update to true --------- Co-authored-by: Diogo Ferrão <diogo.ferrao@freiheit.com>
This commit is contained in:
parent
94fbaf765c
commit
27b008a657
8 changed files with 529 additions and 160 deletions
|
|
@ -36,7 +36,8 @@ This should be used with care since it also disables a couple of other features
|
|||
- `hibernations` (Attributes List) One or more hibernation block as defined below. (see [below for nested schema](#nestedatt--hibernations))
|
||||
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`name`".
|
||||
- `kube_config` (String, Sensitive, Deprecated) Kube config file used for connecting to the cluster. This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see How to rotate SKE credentials (https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).
|
||||
- `kubernetes_version` (String) Kubernetes version.
|
||||
- `kubernetes_version` (String, Deprecated) Kubernetes version. This field is deprecated, use `kubernetes_version_used` instead
|
||||
- `kubernetes_version_min` (String) The minimum Kubernetes version, this field is always nil. SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html). To get the current kubernetes version being used for your cluster, use the `kubernetes_version_used` field.
|
||||
- `kubernetes_version_used` (String) Full Kubernetes version used. For example, if `1.22` was selected, this value may result to `1.22.15`
|
||||
- `maintenance` (Attributes) A single maintenance block as defined below (see [below for nested schema](#nestedatt--maintenance))
|
||||
- `node_pools` (Attributes List) One or more `node_pool` block as defined below. (see [below for nested schema](#nestedatt--node_pools))
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ resource "stackit_ske_cluster" "example" {
|
|||
|
||||
### Required
|
||||
|
||||
- `kubernetes_version` (String) Kubernetes version. Must only contain major and minor version (e.g. 1.22)
|
||||
- `name` (String) The cluster name.
|
||||
- `node_pools` (Attributes List) One or more `node_pool` block as defined below. (see [below for nested schema](#nestedatt--node_pools))
|
||||
- `project_id` (String) STACKIT project ID to which the cluster is associated.
|
||||
|
|
@ -53,13 +52,15 @@ This should be used with care since it also disables a couple of other features
|
|||
Deprecated as of Kubernetes 1.25 and later
|
||||
- `extensions` (Attributes) A single extensions block as defined below. (see [below for nested schema](#nestedatt--extensions))
|
||||
- `hibernations` (Attributes List) One or more hibernation block as defined below. (see [below for nested schema](#nestedatt--hibernations))
|
||||
- `kubernetes_version` (String, Deprecated) Kubernetes version. Must only contain major and minor version (e.g. 1.22). This field is deprecated, use `kubernetes_version_min instead`
|
||||
- `kubernetes_version_min` (String) The minimum Kubernetes version. This field will be used to set the kubernetes version on creation/update of the cluster and can only by incremented. A downgrade of the version requires a replace of the cluster. If unset, the latest supported Kubernetes version will be used. SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html). To get the current kubernetes version being used for your cluster, use the read-only `kubernetes_version_used` field.
|
||||
- `maintenance` (Attributes) A single maintenance block as defined below. (see [below for nested schema](#nestedatt--maintenance))
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`name`".
|
||||
- `kube_config` (String, Sensitive, Deprecated) Static token kubeconfig used for connecting to the cluster. This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see How to rotate SKE credentials (https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).
|
||||
- `kubernetes_version_used` (String) Full Kubernetes version used. For example, if 1.22 was selected, this value may result to 1.22.15
|
||||
- `kube_config` (String, Sensitive, Deprecated) Static token kubeconfig used for connecting to the cluster. This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see [How to rotate SKE credentials](https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).
|
||||
- `kubernetes_version_used` (String) Full Kubernetes version used. For example, if 1.22 was set in `kubernetes_version_min`, this value may result to 1.22.15. SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html).
|
||||
|
||||
<a id="nestedatt--node_pools"></a>
|
||||
### Nested Schema for `node_pools`
|
||||
|
|
@ -149,7 +150,10 @@ Optional:
|
|||
|
||||
Required:
|
||||
|
||||
- `enable_kubernetes_version_updates` (Boolean) Flag to enable/disable auto-updates of the Kubernetes version.
|
||||
- `enable_machine_image_version_updates` (Boolean) Flag to enable/disable auto-updates of the OS image version.
|
||||
- `end` (String) Time for maintenance window end. E.g. `01:23:45Z`, `05:00:00+02:00`.
|
||||
- `start` (String) Time for maintenance window start. E.g. `01:23:45Z`, `05:00:00+02:00`.
|
||||
|
||||
Optional:
|
||||
|
||||
- `enable_kubernetes_version_updates` (Boolean) Flag to enable/disable auto-updates of the Kubernetes version. Defaults to `true. SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html).
|
||||
|
|
|
|||
|
|
@ -94,10 +94,15 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
|||
Description: "The cluster name.",
|
||||
Required: true,
|
||||
},
|
||||
"kubernetes_version": schema.StringAttribute{
|
||||
Description: "Kubernetes version.",
|
||||
"kubernetes_version_min": schema.StringAttribute{
|
||||
Description: `The minimum Kubernetes version, this field is always nil. ` + SKEUpdateDoc + " To get the current kubernetes version being used for your cluster, use the `kubernetes_version_used` field.",
|
||||
Computed: true,
|
||||
},
|
||||
"kubernetes_version": schema.StringAttribute{
|
||||
Description: "Kubernetes version. This field is deprecated, use `kubernetes_version_used` instead",
|
||||
Computed: true,
|
||||
DeprecationMessage: "This field is always nil, use `kubernetes_version_used` to get the cluster kubernetes version. This field would cause errors when the cluster got a kubernetes version minor upgrade, either triggered by automatic or forceful updates.",
|
||||
},
|
||||
"kubernetes_version_used": schema.StringAttribute{
|
||||
Description: "Full Kubernetes version used. For example, if `1.22` was selected, this value may result to `1.22.15`",
|
||||
Computed: true,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
|
||||
|
|
@ -46,6 +48,8 @@ const (
|
|||
VersionStateSupported = "supported"
|
||||
VersionStatePreview = "preview"
|
||||
VersionStateDeprecated = "deprecated"
|
||||
|
||||
SKEUpdateDoc = "SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html)."
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
|
|
@ -59,6 +63,7 @@ type Model struct {
|
|||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
KubernetesVersionMin types.String `tfsdk:"kubernetes_version_min"`
|
||||
KubernetesVersion types.String `tfsdk:"kubernetes_version"`
|
||||
KubernetesVersionUsed types.String `tfsdk:"kubernetes_version_used"`
|
||||
AllowPrivilegedContainers types.Bool `tfsdk:"allow_privileged_containers"`
|
||||
|
|
@ -262,13 +267,35 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
"name": schema.StringAttribute{
|
||||
Description: "The cluster name.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"kubernetes_version_min": schema.StringAttribute{
|
||||
Description: "The minimum Kubernetes version. This field will be used to set the kubernetes version on creation/update of the cluster and can only by incremented. A downgrade of the version requires a replace of the cluster. If unset, the latest supported Kubernetes version will be used. " + SKEUpdateDoc + " To get the current kubernetes version being used for your cluster, use the read-only `kubernetes_version_used` field.",
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplaceIf(stringplanmodifier.RequiresReplaceIfFunc(func(ctx context.Context, sr planmodifier.StringRequest, rrifr *stringplanmodifier.RequiresReplaceIfFuncResponse) {
|
||||
if sr.StateValue.IsNull() || sr.PlanValue.IsNull() {
|
||||
return
|
||||
}
|
||||
planVersion := fmt.Sprintf("v%s", sr.PlanValue.ValueString())
|
||||
stateVersion := fmt.Sprintf("v%s", sr.StateValue.ValueString())
|
||||
|
||||
rrifr.RequiresReplace = semver.Compare(planVersion, stateVersion) < 0
|
||||
}), "Kubernetes minimum version", "If the Kubernetes version is a downgrade, the cluster will be replaced"),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.VersionNumber(),
|
||||
},
|
||||
},
|
||||
"kubernetes_version": schema.StringAttribute{
|
||||
Description: "Kubernetes version. Must only contain major and minor version (e.g. 1.22)",
|
||||
Required: true,
|
||||
Description: "Kubernetes version. Must only contain major and minor version (e.g. 1.22). This field is deprecated, use `kubernetes_version_min instead`",
|
||||
Optional: true,
|
||||
DeprecationMessage: "Use `kubernetes_version_min instead`. Setting a specific kubernetes version would cause errors when the cluster got a kubernetes version minor upgrade, either triggered by automatic or forceful updates. In those cases, this field might not represent the actual kubernetes version used in the cluster.",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplaceIf(stringplanmodifier.RequiresReplaceIfFunc(func(ctx context.Context, sr planmodifier.StringRequest, rrifr *stringplanmodifier.RequiresReplaceIfFuncResponse) {
|
||||
if sr.StateValue.IsNull() || sr.PlanValue.IsNull() {
|
||||
|
|
@ -285,7 +312,7 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
},
|
||||
},
|
||||
"kubernetes_version_used": schema.StringAttribute{
|
||||
Description: "Full Kubernetes version used. For example, if 1.22 was selected, this value may result to 1.22.15",
|
||||
Description: "Full Kubernetes version used. For example, if 1.22 was set in `kubernetes_version_min`, this value may result to 1.22.15. " + SKEUpdateDoc,
|
||||
Computed: true,
|
||||
},
|
||||
"allow_privileged_containers": schema.BoolAttribute{
|
||||
|
|
@ -422,8 +449,10 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
},
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"enable_kubernetes_version_updates": schema.BoolAttribute{
|
||||
Description: "Flag to enable/disable auto-updates of the Kubernetes version.",
|
||||
Required: true,
|
||||
Description: "Flag to enable/disable auto-updates of the Kubernetes version. Defaults to `true. " + SKEUpdateDoc,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
"enable_machine_image_version_updates": schema.BoolAttribute{
|
||||
Description: "Flag to enable/disable auto-updates of the OS image version.",
|
||||
|
|
@ -510,25 +539,40 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
|||
},
|
||||
},
|
||||
"kube_config": schema.StringAttribute{
|
||||
Description: "Static token kubeconfig used for connecting to the cluster. This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see How to rotate SKE credentials (https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).",
|
||||
Description: "Static token kubeconfig used for connecting to the cluster. This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see [How to rotate SKE credentials](https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).",
|
||||
Sensitive: true,
|
||||
Computed: true,
|
||||
DeprecationMessage: "This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see How to rotate SKE credentials (https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).",
|
||||
DeprecationMessage: "This field will be empty for clusters with Kubernetes v1.27+, or if you have obtained the kubeconfig or performed credentials rotation using the new process, either through the Portal or the SKE API. Use the stackit_ske_kubeconfig resource instead. For more information, see [How to rotate SKE credentials](https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html).",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigValidators validate the resource configuration
|
||||
func (r *clusterResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
|
||||
return []resource.ConfigValidator{
|
||||
// will raise an error if both fields are set simultaneously
|
||||
resourcevalidator.Conflicting(
|
||||
path.MatchRoot("kubernetes_version"),
|
||||
path.MatchRoot("kubernetes_version_min"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// needs to be executed inside the Create and Update methods
|
||||
// since ValidateConfig runs before variables are rendered to their value,
|
||||
// which causes errors like this: https://github.com/stackitcloud/terraform-provider-stackit/issues/201
|
||||
func checkAllowPrivilegedContainers(allowPrivilegeContainers types.Bool, kubernetesVersion types.String) diag.Diagnostics {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
// if kubernetesVersion is null, the latest one will be used and allowPriviledgeContainers will not be supported
|
||||
if kubernetesVersion.IsNull() {
|
||||
diags.AddError("'Kubernetes version' missing", "This field is required")
|
||||
if !allowPrivilegeContainers.IsNull() {
|
||||
diags.AddError("'Allow privilege containers' deprecated", "This field is deprecated as of Kubernetes 1.25 and later. Please remove this field")
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
comparison := semver.Compare(fmt.Sprintf("v%s", kubernetesVersion.ValueString()), "v1.25")
|
||||
if comparison < 0 {
|
||||
if allowPrivilegeContainers.IsNull() {
|
||||
|
|
@ -552,7 +596,12 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest
|
|||
return
|
||||
}
|
||||
|
||||
diags = checkAllowPrivilegedContainers(model.AllowPrivilegedContainers, model.KubernetesVersion)
|
||||
kubernetesVersion := model.KubernetesVersionMin
|
||||
// needed for backwards compatibility following kubernetes_version field deprecation
|
||||
if kubernetesVersion.IsNull() {
|
||||
kubernetesVersion = model.KubernetesVersion
|
||||
}
|
||||
diags = checkAllowPrivilegedContainers(model.AllowPrivilegedContainers, kubernetesVersion)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -939,12 +988,6 @@ func mapFields(ctx context.Context, cl *ske.Cluster, m *Model) error {
|
|||
)
|
||||
|
||||
if cl.Kubernetes != nil {
|
||||
// The k8s version returned by the API includes the patch version, while we only support major and minor in the kubernetes_version field
|
||||
// This prevents inconsistent state by automatic updates to the patch version in the API
|
||||
versionPreffixed := "v" + *cl.Kubernetes.Version
|
||||
majorMinorVersionPreffixed := semver.MajorMinor(versionPreffixed)
|
||||
majorMinorVersion, _ := strings.CutPrefix(majorMinorVersionPreffixed, "v")
|
||||
m.KubernetesVersion = types.StringPointerValue(utils.Ptr(majorMinorVersion))
|
||||
m.KubernetesVersionUsed = types.StringPointerValue(cl.Kubernetes.Version)
|
||||
m.AllowPrivilegedContainers = types.BoolPointerValue(cl.Kubernetes.AllowPrivilegedContainers)
|
||||
}
|
||||
|
|
@ -1331,7 +1374,7 @@ func mapExtensions(ctx context.Context, cl *ske.Cluster, m *Model) error {
|
|||
}
|
||||
|
||||
func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion) (kubernetesPayload *ske.Kubernetes, hasDeprecatedVersion bool, err error) {
|
||||
versionUsed, hasDeprecatedVersion, err := latestMatchingVersion(availableVersions, conversion.StringValueToPointer(m.KubernetesVersion))
|
||||
versionUsed, hasDeprecatedVersion, err := latestMatchingVersion(availableVersions, conversion.StringValueToPointer(m.KubernetesVersion), conversion.StringValueToPointer(m.KubernetesVersionMin))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("getting latest matching kubernetes version: %w", err)
|
||||
}
|
||||
|
|
@ -1343,41 +1386,74 @@ func toKubernetesPayload(m *Model, availableVersions []ske.KubernetesVersion) (k
|
|||
return k, hasDeprecatedVersion, nil
|
||||
}
|
||||
|
||||
func latestMatchingVersion(availableVersions []ske.KubernetesVersion, providedVersion *string) (version *string, deprecated bool, err error) {
|
||||
func latestMatchingVersion(availableVersions []ske.KubernetesVersion, providedVersion, providedVersionMin *string) (version *string, deprecated bool, err error) {
|
||||
deprecated = false
|
||||
|
||||
if availableVersions == nil {
|
||||
return nil, false, fmt.Errorf("nil available kubernetes versions")
|
||||
}
|
||||
|
||||
if providedVersion == nil {
|
||||
return nil, false, fmt.Errorf("provided version is nil")
|
||||
if providedVersionMin == nil {
|
||||
if providedVersion == nil {
|
||||
// kubernetes_version field deprecation
|
||||
// this if clause should be removed once kubernetes_version field is completely removed
|
||||
latestVersion, err := getLatestSupportedKubernetesVersion(availableVersions)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("get latest supported kubernetes version: %w", err)
|
||||
}
|
||||
return latestVersion, false, nil
|
||||
}
|
||||
// kubernetes_version field deprecation
|
||||
// kubernetes_version field value is assigned to kubernets_version_min for backwards compatibility
|
||||
providedVersionMin = providedVersion
|
||||
}
|
||||
|
||||
providedVersionPrefixed := "v" + *providedVersion
|
||||
var fullVersion bool
|
||||
versionExp := validate.FullVersionRegex
|
||||
versionRegex := regexp.MustCompile(versionExp)
|
||||
if versionRegex.MatchString(*providedVersionMin) {
|
||||
fullVersion = true
|
||||
}
|
||||
|
||||
providedVersionPrefixed := "v" + *providedVersionMin
|
||||
|
||||
if !semver.IsValid(providedVersionPrefixed) {
|
||||
return nil, false, fmt.Errorf("provided version is invalid")
|
||||
}
|
||||
|
||||
var versionUsed *string
|
||||
var state *string
|
||||
var availableVersionsArray []string
|
||||
// Get the higher available version that matches the major and minor version provided by the user
|
||||
// Get the higher available version that matches the major, minor and patch version provided by the user
|
||||
for _, v := range availableVersions {
|
||||
if v.State == nil || v.Version == nil {
|
||||
continue
|
||||
}
|
||||
availableVersionsArray = append(availableVersionsArray, *v.Version)
|
||||
vPreffixed := "v" + *v.Version
|
||||
if semver.MajorMinor(vPreffixed) == semver.MajorMinor(providedVersionPrefixed) &&
|
||||
(semver.Compare(vPreffixed, providedVersionPrefixed) == 1 || semver.Compare(vPreffixed, providedVersionPrefixed) == 0) {
|
||||
versionUsed = v.Version
|
||||
|
||||
if strings.EqualFold(*v.State, VersionStateDeprecated) {
|
||||
deprecated = true
|
||||
} else {
|
||||
deprecated = false
|
||||
if fullVersion {
|
||||
// [MAJOR].[MINOR].[PATCH] version provided, match available version
|
||||
if semver.Compare(vPreffixed, providedVersionPrefixed) == 0 {
|
||||
versionUsed = v.Version
|
||||
state = v.State
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// [MAJOR].[MINOR] version provided, get the latest patch version
|
||||
if semver.MajorMinor(vPreffixed) == semver.MajorMinor(providedVersionPrefixed) &&
|
||||
(semver.Compare(vPreffixed, providedVersionPrefixed) == 1) || (semver.Compare(vPreffixed, providedVersionPrefixed) == 0) {
|
||||
versionUsed = v.Version
|
||||
state = v.State
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if versionUsed != nil {
|
||||
if strings.EqualFold(*state, VersionStateDeprecated) {
|
||||
deprecated = true
|
||||
} else {
|
||||
deprecated = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1389,6 +1465,31 @@ func latestMatchingVersion(availableVersions []ske.KubernetesVersion, providedVe
|
|||
return versionUsed, deprecated, nil
|
||||
}
|
||||
|
||||
func getLatestSupportedKubernetesVersion(versions []ske.KubernetesVersion) (*string, error) {
|
||||
foundKubernetesVersion := false
|
||||
var latestVersion *string
|
||||
for i := range versions {
|
||||
version := versions[i]
|
||||
if *version.State != VersionStateSupported {
|
||||
continue
|
||||
}
|
||||
if latestVersion != nil {
|
||||
oldSemVer := fmt.Sprintf("v%s", *latestVersion)
|
||||
newSemVer := fmt.Sprintf("v%s", *version.Version)
|
||||
if semver.Compare(newSemVer, oldSemVer) != 1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
foundKubernetesVersion = true
|
||||
latestVersion = version.Version
|
||||
}
|
||||
if !foundKubernetesVersion {
|
||||
return nil, fmt.Errorf("no supported Kubernetes version found")
|
||||
}
|
||||
return latestVersion, nil
|
||||
}
|
||||
|
||||
func (r *clusterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var state Model
|
||||
diags := req.State.Get(ctx, &state)
|
||||
|
|
@ -1441,7 +1542,13 @@ func (r *clusterResource) Update(ctx context.Context, req resource.UpdateRequest
|
|||
return
|
||||
}
|
||||
|
||||
diags = checkAllowPrivilegedContainers(model.AllowPrivilegedContainers, model.KubernetesVersion)
|
||||
kubernetesVersion := model.KubernetesVersionMin
|
||||
// needed for backwards compatibility following kubernetes_version field deprecation
|
||||
if kubernetesVersion.IsNull() {
|
||||
kubernetesVersion = model.KubernetesVersion
|
||||
}
|
||||
|
||||
diags = checkAllowPrivilegedContainers(model.AllowPrivilegedContainers, kubernetesVersion)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func TestMapFields(t *testing.T) {
|
|||
Id: types.StringValue("pid,name"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
Name: types.StringValue("name"),
|
||||
KubernetesVersion: types.StringValue("1.2"),
|
||||
KubernetesVersion: types.StringNull(),
|
||||
KubernetesVersionUsed: types.StringValue("1.2.3"),
|
||||
AllowPrivilegedContainers: types.BoolValue(true),
|
||||
|
||||
|
|
@ -397,6 +397,7 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
description string
|
||||
availableVersions []ske.KubernetesVersion
|
||||
providedVersion *string
|
||||
providedVersionMin *string
|
||||
expectedVersionUsed *string
|
||||
expectedHasDeprecatedVersion bool
|
||||
isValid bool
|
||||
|
|
@ -421,13 +422,66 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20.1"),
|
||||
utils.Ptr("1.20.1"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"available_version_zero_patch",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.1"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.2"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20.0"),
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"available_version_with_no_provided_patch",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.1"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.2"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
utils.Ptr("1.20.2"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"available_version_no_patch",
|
||||
"available_version_no_provided_patch_2",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
|
|
@ -438,6 +492,7 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
|
|
@ -455,6 +510,7 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateDeprecated),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.19"),
|
||||
utils.Ptr("1.19.0"),
|
||||
true,
|
||||
|
|
@ -472,6 +528,7 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateDeprecated),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
|
|
@ -489,11 +546,48 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"deprecated_kubernetes_version_field",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
utils.Ptr("1.20"),
|
||||
nil,
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"nil_provided_version_get_latest",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
utils.Ptr("1.20.0"),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no_matching_available_versions",
|
||||
[]ske.KubernetesVersion{
|
||||
|
|
@ -506,14 +600,60 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.21"),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_matching_available_versions_patch",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.21.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.21.1"),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_matching_available_versions_patch_2",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.21.2"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr("1.21.1"),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no_available_version",
|
||||
[]ske.KubernetesVersion{},
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
nil,
|
||||
false,
|
||||
|
|
@ -522,6 +662,7 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
{
|
||||
"nil_available_version",
|
||||
nil,
|
||||
nil,
|
||||
utils.Ptr("1.20"),
|
||||
nil,
|
||||
false,
|
||||
|
|
@ -539,32 +680,16 @@ func TestLatestMatchingVersion(t *testing.T) {
|
|||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
utils.Ptr(""),
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"nil_provided_version",
|
||||
[]ske.KubernetesVersion{
|
||||
{
|
||||
Version: utils.Ptr("1.20.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
{
|
||||
Version: utils.Ptr("1.19.0"),
|
||||
State: utils.Ptr(VersionStateSupported),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
versionUsed, hasDeprecatedVersion, err := latestMatchingVersion(tt.availableVersions, tt.providedVersion)
|
||||
versionUsed, hasDeprecatedVersion, err := latestMatchingVersion(tt.availableVersions, tt.providedVersion, tt.providedVersionMin)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
@ -738,13 +863,13 @@ func TestCheckAllowPrivilegedContainers(t *testing.T) {
|
|||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "null_version_1",
|
||||
description: "null_version_1_flag_deprecated",
|
||||
kubernetesVersion: nil,
|
||||
allowPrivilegeContainers: nil,
|
||||
isValid: false,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "null_version_2",
|
||||
description: "null_version_2_flag_deprecated",
|
||||
kubernetesVersion: nil,
|
||||
allowPrivilegeContainers: utils.Ptr(false),
|
||||
isValid: false,
|
||||
|
|
@ -815,3 +940,72 @@ func TestCheckAllowPrivilegedContainers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestSupportedVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
listKubernetesVersion []ske.KubernetesVersion
|
||||
isValid bool
|
||||
expectedVersion *string
|
||||
}{
|
||||
{
|
||||
description: "base",
|
||||
listKubernetesVersion: []ske.KubernetesVersion{
|
||||
{
|
||||
State: utils.Ptr("supported"),
|
||||
Version: utils.Ptr("1.2.3"),
|
||||
},
|
||||
{
|
||||
State: utils.Ptr("supported"),
|
||||
Version: utils.Ptr("3.2.1"),
|
||||
},
|
||||
{
|
||||
State: utils.Ptr("not-supported"),
|
||||
Version: utils.Ptr("4.4.4"),
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
expectedVersion: utils.Ptr("3.2.1"),
|
||||
},
|
||||
{
|
||||
description: "no Kubernetes versions 1",
|
||||
listKubernetesVersion: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "no Kubernetes versions 2",
|
||||
listKubernetesVersion: []ske.KubernetesVersion{},
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "no supported Kubernetes versions",
|
||||
listKubernetesVersion: []ske.KubernetesVersion{
|
||||
{
|
||||
State: utils.Ptr("not-supported"),
|
||||
Version: utils.Ptr("1.2.3"),
|
||||
},
|
||||
},
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
version, err := getLatestSupportedKubernetesVersion(tt.listKubernetesVersion)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Errorf("failed on valid input")
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Errorf("did not fail on invalid input")
|
||||
}
|
||||
if !tt.isValid {
|
||||
return
|
||||
}
|
||||
diff := cmp.Diff(version, tt.expectedVersion)
|
||||
if diff != "" {
|
||||
t.Fatalf("Output is not as expected: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,37 +3,33 @@ package ske_test
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/ske"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/ske/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
|
||||
)
|
||||
|
||||
var projectResource = map[string]string{
|
||||
"project_id": testutil.ProjectId,
|
||||
}
|
||||
|
||||
var clusterResource = map[string]string{
|
||||
"project_id": testutil.ProjectId,
|
||||
"name": fmt.Sprintf("cl-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
|
||||
"name_min": fmt.Sprintf("cl-min-%s", acctest.RandStringFromCharSet(3, acctest.CharSetAlphaNum)),
|
||||
"kubernetes_version": "1.25",
|
||||
"kubernetes_version_used": "1.25.16",
|
||||
"kubernetes_version_new": "1.26",
|
||||
"kubernetes_version_used_new": "1.26.14",
|
||||
"kubernetes_version_min": "1.26",
|
||||
"kubernetes_version_used": "1.26.15",
|
||||
"kubernetes_version_min_new": "1.27",
|
||||
"kubernetes_version_used_new": "1.27.13",
|
||||
"nodepool_name": "np-acc-test",
|
||||
"nodepool_name_min": "np-acc-min-test",
|
||||
"nodepool_machine_type": "b1.2",
|
||||
"nodepool_os_version": "3760.2.0",
|
||||
"nodepool_os_version_min": "3760.2.0",
|
||||
"nodepool_os_version": "3815.2.1",
|
||||
"nodepool_os_version_min": "3815.2.1",
|
||||
"nodepool_os_name": "flatcar",
|
||||
"nodepool_minimum": "2",
|
||||
"nodepool_maximum": "3",
|
||||
|
|
@ -71,14 +67,10 @@ func getConfig(version string, maintenanceEnd *string) string {
|
|||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
resource "stackit_ske_project" "project" {
|
||||
project_id = "%s"
|
||||
}
|
||||
|
||||
resource "stackit_ske_cluster" "cluster" {
|
||||
project_id = stackit_ske_project.project.project_id
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
kubernetes_version = "%s"
|
||||
kubernetes_version_min = "%s"
|
||||
node_pools = [{
|
||||
name = "%s"
|
||||
machine_type = "%s"
|
||||
|
|
@ -125,15 +117,14 @@ func getConfig(version string, maintenanceEnd *string) string {
|
|||
}
|
||||
|
||||
resource "stackit_ske_kubeconfig" "kubeconfig" {
|
||||
project_id = stackit_ske_project.project.project_id
|
||||
project_id = stackit_ske_cluster.cluster.project_id
|
||||
cluster_name = stackit_ske_cluster.cluster.name
|
||||
expiration = "%s"
|
||||
}
|
||||
|
||||
resource "stackit_ske_cluster" "cluster_min" {
|
||||
project_id = stackit_ske_project.project.project_id
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
kubernetes_version = "%s"
|
||||
node_pools = [{
|
||||
name = "%s"
|
||||
machine_type = "%s"
|
||||
|
|
@ -151,7 +142,7 @@ func getConfig(version string, maintenanceEnd *string) string {
|
|||
}
|
||||
`,
|
||||
testutil.SKEProviderConfig(),
|
||||
projectResource["project_id"],
|
||||
clusterResource["project_id"],
|
||||
clusterResource["name"],
|
||||
version,
|
||||
clusterResource["nodepool_name"],
|
||||
|
|
@ -187,8 +178,8 @@ func getConfig(version string, maintenanceEnd *string) string {
|
|||
clusterResource["kubeconfig_expiration"],
|
||||
|
||||
// Minimal
|
||||
clusterResource["project_id"],
|
||||
clusterResource["name_min"],
|
||||
clusterResource["kubernetes_version_new"],
|
||||
clusterResource["nodepool_name_min"],
|
||||
clusterResource["nodepool_machine_type"],
|
||||
clusterResource["nodepool_os_version_min"],
|
||||
|
|
@ -210,17 +201,11 @@ func TestAccSKE(t *testing.T) {
|
|||
|
||||
// 1) Creation
|
||||
{
|
||||
Config: getConfig(clusterResource["kubernetes_version"], nil),
|
||||
Config: getConfig(clusterResource["kubernetes_version_min"], nil),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// project data
|
||||
resource.TestCheckResourceAttr("stackit_ske_project.project", "project_id", projectResource["project_id"]),
|
||||
// cluster data
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_ske_project.project", "project_id",
|
||||
"stackit_ske_cluster.cluster", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "name", clusterResource["name"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version", clusterResource["kubernetes_version"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_min", clusterResource["kubernetes_version_min"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_used", clusterResource["kubernetes_version_used"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.name", clusterResource["nodepool_name"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.availability_zones.#", "1"),
|
||||
|
|
@ -270,13 +255,8 @@ func TestAccSKE(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("stackit_ske_kubeconfig.kubeconfig", "expires_at"),
|
||||
|
||||
// Minimal cluster
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_ske_project.project", "project_id",
|
||||
"stackit_ske_cluster.cluster_min", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster_min", "name", clusterResource["name_min"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster_min", "kubernetes_version", clusterResource["kubernetes_version_new"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster_min", "kubernetes_version_used", clusterResource["kubernetes_version_used_new"]),
|
||||
resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster_min", "kubernetes_version_used"),
|
||||
resource.TestCheckNoResourceAttr("stackit_ske_cluster.cluster_min", "allow_privileged_containers"),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster_min", "node_pools.0.name", clusterResource["nodepool_name_min"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster_min", "node_pools.0.availability_zones.#", "1"),
|
||||
|
|
@ -299,7 +279,7 @@ func TestAccSKE(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster_min", "maintenance.enable_machine_image_version_updates"),
|
||||
resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster_min", "maintenance.start"),
|
||||
resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster_min", "maintenance.end"),
|
||||
resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster_min", "kube_config"),
|
||||
resource.TestCheckNoResourceAttr("stackit_ske_cluster.cluster_min", "kube_config"),
|
||||
),
|
||||
},
|
||||
// 2) Data source
|
||||
|
|
@ -307,11 +287,6 @@ func TestAccSKE(t *testing.T) {
|
|||
Config: fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
data "stackit_ske_project" "project" {
|
||||
project_id = "%s"
|
||||
depends_on = [stackit_ske_project.project]
|
||||
}
|
||||
|
||||
data "stackit_ske_cluster" "cluster" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
|
|
@ -325,17 +300,13 @@ func TestAccSKE(t *testing.T) {
|
|||
}
|
||||
|
||||
`,
|
||||
getConfig(clusterResource["kubernetes_version"], nil),
|
||||
projectResource["project_id"],
|
||||
getConfig(clusterResource["kubernetes_version_min"], nil),
|
||||
clusterResource["project_id"],
|
||||
clusterResource["name"],
|
||||
clusterResource["project_id"],
|
||||
clusterResource["name_min"],
|
||||
),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// project data
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_project.project", "id", projectResource["project_id"]),
|
||||
|
||||
// cluster data
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "id", fmt.Sprintf("%s,%s",
|
||||
clusterResource["project_id"],
|
||||
|
|
@ -343,7 +314,6 @@ func TestAccSKE(t *testing.T) {
|
|||
)),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "name", clusterResource["name"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "kubernetes_version", clusterResource["kubernetes_version"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "kubernetes_version_used", clusterResource["kubernetes_version_used"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.name", clusterResource["nodepool_name"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.availability_zones.#", "1"),
|
||||
|
|
@ -380,8 +350,7 @@ func TestAccSKE(t *testing.T) {
|
|||
|
||||
// Minimal cluster
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster_min", "name", clusterResource["name_min"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster_min", "kubernetes_version", clusterResource["kubernetes_version_new"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster_min", "kubernetes_version_used", clusterResource["kubernetes_version_used_new"]),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster_min", "kubernetes_version_used"),
|
||||
resource.TestCheckNoResourceAttr("data.stackit_ske_cluster.cluster_min", "allow_privileged_containers"),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster_min", "node_pools.0.name", clusterResource["nodepool_name_min"]),
|
||||
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster_min", "node_pools.0.availability_zones.#", "1"),
|
||||
|
|
@ -404,23 +373,10 @@ func TestAccSKE(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster_min", "maintenance.enable_machine_image_version_updates"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster_min", "maintenance.start"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster_min", "maintenance.end"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster_min", "kube_config"),
|
||||
resource.TestCheckNoResourceAttr("data.stackit_ske_cluster.cluster_min", "kube_config"),
|
||||
),
|
||||
},
|
||||
// 3) Import project
|
||||
{
|
||||
ResourceName: "stackit_ske_project.project",
|
||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||
_, ok := s.RootModule().Resources["stackit_ske_project.project"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackit_ske_project.project")
|
||||
}
|
||||
return testutil.ProjectId, nil
|
||||
},
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// 4) Import cluster
|
||||
// 3) Import cluster
|
||||
{
|
||||
ResourceName: "stackit_ske_cluster.cluster",
|
||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||
|
|
@ -441,9 +397,9 @@ func TestAccSKE(t *testing.T) {
|
|||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
// The fields are not provided in the SKE API when disabled, although set actively.
|
||||
ImportStateVerifyIgnore: []string{"kube_config", "extensions.argus.%", "extensions.argus.argus_instance_id", "extensions.argus.enabled", "extensions.acl.enabled", "extensions.acl.allowed_cidrs", "extensions.acl.allowed_cidrs.#", "extensions.acl.%"},
|
||||
ImportStateVerifyIgnore: []string{"kubernetes_version_min", "kube_config", "extensions.argus.%", "extensions.argus.argus_instance_id", "extensions.argus.enabled", "extensions.acl.enabled", "extensions.acl.allowed_cidrs", "extensions.acl.allowed_cidrs.#", "extensions.acl.%"},
|
||||
},
|
||||
// 5) Import minimal cluster
|
||||
// 4) Import minimal cluster
|
||||
{
|
||||
ResourceName: "stackit_ske_cluster.cluster_min",
|
||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||
|
|
@ -463,16 +419,16 @@ func TestAccSKE(t *testing.T) {
|
|||
},
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateVerifyIgnore: []string{"kube_config"},
|
||||
ImportStateVerifyIgnore: []string{"kubernetes_version_min", "kube_config"},
|
||||
},
|
||||
// 6) Update kubernetes version and maximum
|
||||
// 5) Update kubernetes version and maximum
|
||||
{
|
||||
Config: getConfig(clusterResource["kubernetes_version_new"], utils.Ptr(clusterResource["maintenance_end_new"])),
|
||||
Config: getConfig(clusterResource["kubernetes_version_min_new"], utils.Ptr(clusterResource["maintenance_end_new"])),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// cluster data
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "name", clusterResource["name"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version", clusterResource["kubernetes_version_new"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_min", clusterResource["kubernetes_version_min_new"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_used", clusterResource["kubernetes_version_used_new"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.name", clusterResource["nodepool_name"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.availability_zones.#", "1"),
|
||||
|
|
@ -509,14 +465,14 @@ func TestAccSKE(t *testing.T) {
|
|||
resource.TestCheckNoResourceAttr("stackit_ske_cluster.cluster", "kube_config"), // when using the kubeconfig resource, the kubeconfig field becomes null
|
||||
),
|
||||
},
|
||||
// 7) Downgrade kubernetes version
|
||||
// 6) Downgrade kubernetes version
|
||||
{
|
||||
Config: getConfig(clusterResource["kubernetes_version"], utils.Ptr(clusterResource["maintenance_end_new"])),
|
||||
Config: getConfig(clusterResource["kubernetes_version_min"], utils.Ptr(clusterResource["maintenance_end_new"])),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// cluster data
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "name", clusterResource["name"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version", clusterResource["kubernetes_version"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_min", clusterResource["kubernetes_version_min"]),
|
||||
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_used", clusterResource["kubernetes_version_used"]),
|
||||
),
|
||||
},
|
||||
|
|
@ -530,9 +486,7 @@ func testAccCheckSKEDestroy(s *terraform.State) error {
|
|||
var client *ske.APIClient
|
||||
var err error
|
||||
if testutil.SKECustomEndpoint == "" {
|
||||
client, err = ske.NewAPIClient(
|
||||
config.WithRegion("eu01"),
|
||||
)
|
||||
client, err = ske.NewAPIClient()
|
||||
} else {
|
||||
client, err = ske.NewAPIClient(
|
||||
config.WithEndpoint(testutil.SKECustomEndpoint),
|
||||
|
|
@ -542,34 +496,35 @@ func testAccCheckSKEDestroy(s *terraform.State) error {
|
|||
return fmt.Errorf("creating client: %w", err)
|
||||
}
|
||||
|
||||
projectsToDestroy := []string{}
|
||||
clustersToDestroy := []string{}
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "stackit_ske_project" {
|
||||
if rs.Type != "stackit_ske_cluster" {
|
||||
continue
|
||||
}
|
||||
projectsToDestroy = append(projectsToDestroy, rs.Primary.ID)
|
||||
// cluster terraform ID: = "[project_id],[cluster_name]"
|
||||
clusterName := strings.Split(rs.Primary.ID, core.Separator)[1]
|
||||
clustersToDestroy = append(clustersToDestroy, clusterName)
|
||||
}
|
||||
for _, projectId := range projectsToDestroy {
|
||||
_, err := client.GetServiceStatus(ctx, projectId).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if !ok {
|
||||
return fmt.Errorf("could not convert error to GenericOpenApiError in acc test destruction, %w", err)
|
||||
}
|
||||
if oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusForbidden {
|
||||
// Already gone
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("getting project: %w", err)
|
||||
}
|
||||
|
||||
_, err = client.DisableServiceExecute(ctx, projectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroying project %s during CheckDestroy: %w", projectId, err)
|
||||
clustersResp, err := client.ListClusters(ctx, testutil.ProjectId).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting clustersResp: %w", err)
|
||||
}
|
||||
|
||||
items := *clustersResp.Items
|
||||
for i := range items {
|
||||
if items[i].Name == nil {
|
||||
continue
|
||||
}
|
||||
_, err = wait.DisableServiceWaitHandler(ctx, client, projectId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroying project %s during CheckDestroy: waiting for deletion %w", projectId, err)
|
||||
if utils.Contains(clustersToDestroy, *items[i].Name) {
|
||||
_, err := client.DeleteClusterExecute(ctx, testutil.ProjectId, *items[i].Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroying cluster %s during CheckDestroy: %w", *items[i].Name, err)
|
||||
}
|
||||
_, err = wait.DeleteClusterWaitHandler(ctx, client, testutil.ProjectId, *items[i].Name).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroying cluster %s during CheckDestroy: waiting for deletion %w", *items[i].Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ import (
|
|||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
)
|
||||
|
||||
const (
|
||||
MajorMinorVersionRegex = `^\d+\.\d+?$`
|
||||
FullVersionRegex = `^\d+\.\d+.\d+?$`
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
description string
|
||||
markdownDescription string
|
||||
|
|
@ -137,7 +142,7 @@ func MinorVersionNumber() *Validator {
|
|||
return &Validator{
|
||||
description: description,
|
||||
validate: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
exp := `^\d+\.\d+?$`
|
||||
exp := MajorMinorVersionRegex
|
||||
r := regexp.MustCompile(exp)
|
||||
version := req.ConfigValue.ValueString()
|
||||
if !r.MatchString(version) {
|
||||
|
|
@ -151,6 +156,30 @@ func MinorVersionNumber() *Validator {
|
|||
}
|
||||
}
|
||||
|
||||
func VersionNumber() *Validator {
|
||||
description := "value must be a version number, without a leading 'v': '[MAJOR].[MINOR]' or '[MAJOR].[MINOR].[PATCH]'"
|
||||
|
||||
return &Validator{
|
||||
description: description,
|
||||
validate: func(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
minorVersionExp := MajorMinorVersionRegex
|
||||
minorVersionRegex := regexp.MustCompile(minorVersionExp)
|
||||
|
||||
versionExp := FullVersionRegex
|
||||
versionRegex := regexp.MustCompile(versionExp)
|
||||
|
||||
version := req.ConfigValue.ValueString()
|
||||
if !minorVersionRegex.MatchString(version) && !versionRegex.MatchString(version) {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
description,
|
||||
req.ConfigValue.ValueString(),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RFC3339SecondsOnly() *Validator {
|
||||
description := "value must be in RFC339 format (seconds only)"
|
||||
|
||||
|
|
|
|||
|
|
@ -363,6 +363,80 @@ func TestMinorVersionNumber(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVersionNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input string
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
"1.20",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-2",
|
||||
"1.3",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-3",
|
||||
"10.1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version",
|
||||
"1.20.1",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version-2",
|
||||
"1.20.10",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok-patch-version-3",
|
||||
"10.20.10",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok",
|
||||
"afssfdfs",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-major-version",
|
||||
"1",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not ok-version",
|
||||
"v1.20.1",
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
r := validator.StringResponse{}
|
||||
VersionNumber().ValidateString(context.Background(), validator.StringRequest{
|
||||
ConfigValue: types.StringValue(tt.input),
|
||||
}, &r)
|
||||
|
||||
if !tt.isValid && !r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && r.Diagnostics.HasError() {
|
||||
t.Fatalf("Should not have failed: %v", r.Diagnostics.Errors())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRFC3339SecondsOnly(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue