terraform-provider: validate image and microservice version (#2766)

This commit is contained in:
Adrian Stobbe 2023-12-22 10:24:13 +01:00 committed by GitHub
parent 519efe637d
commit 436e7c6d3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 13 deletions

View File

@ -846,7 +846,7 @@ func (c *Config) Validate(force bool) error {
// Because of this we can't print the offending field name in the error message, resulting in
// suboptimal UX. Adding the field name to the struct validation of Semver would make it
// impossible to use Semver for other fields.
if err := validateMicroserviceVersion(constants.BinaryVersion(), c.MicroserviceVersion); err != nil {
if err := ValidateMicroserviceVersion(constants.BinaryVersion(), c.MicroserviceVersion); err != nil {
msg := "microserviceVersion: " + msgFromCompatibilityError(err, constants.BinaryVersion().String(), c.MicroserviceVersion.String())
return &ValidationError{validationErrMsgs: []string{msg}}
}

View File

@ -690,7 +690,8 @@ func msgFromCompatibilityError(err error, binaryVersion, fieldValue string) stri
}
}
func validateMicroserviceVersion(binaryVersion, version consemver.Semver) error {
// ValidateMicroserviceVersion checks that the version of the microservice is compatible with the binary version.
func ValidateMicroserviceVersion(binaryVersion, version consemver.Semver) error {
// Major versions always have to match.
if binaryVersion.Major() != version.Major() {
return compatibility.ErrMajorMismatch

View File

@ -75,7 +75,7 @@ func TestValidateMicroserviceVersion(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
err := validateMicroserviceVersion(tc.cli, tc.services)
err := ValidateMicroserviceVersion(tc.cli, tc.services)
if tc.wantError {
assert.Error(err)
return

View File

@ -93,7 +93,11 @@ go_test(
"//internal/attestation/variant",
"//internal/config",
"//internal/constants",
"//internal/semver",
"//terraform-provider-constellation/internal/data",
"@com_github_hashicorp_terraform_plugin_framework//attr",
"@com_github_hashicorp_terraform_plugin_framework//providerserver",
"@com_github_hashicorp_terraform_plugin_framework//types/basetypes",
"@com_github_hashicorp_terraform_plugin_go//tfprotov6",
"@com_github_hashicorp_terraform_plugin_testing//helper/resource",
"@com_github_hashicorp_terraform_plugin_testing//terraform",

View File

@ -722,20 +722,11 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
}
// parse OS image version
var image imageAttribute
convertDiags = data.Image.As(ctx, &image, basetypes.ObjectAsOptions{})
image, imageSemver, convertDiags := r.getImageVersion(ctx, data)
diags.Append(convertDiags...)
if diags.HasError() {
return diags
}
imageSemver, err := semver.New(image.Version)
if err != nil {
diags.AddAttributeError(
path.Root("image").AtName("version"),
"Invalid image version",
fmt.Sprintf("Parsing image version (%s): %s", image.Version, err))
return diags
}
// parse license ID
licenseID := data.LicenseID.ValueString()
@ -948,6 +939,29 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
return diags
}
func (r *ClusterResource) getImageVersion(ctx context.Context, data *ClusterResourceModel) (imageAttribute, semver.Semver, diag.Diagnostics) {
var image imageAttribute
diags := data.Image.As(ctx, &image, basetypes.ObjectAsOptions{})
if diags.HasError() {
return imageAttribute{}, semver.Semver{}, diags
}
imageSemver, err := semver.New(image.Version)
if err != nil {
diags.AddAttributeError(
path.Root("image").AtName("version"),
"Invalid image version",
fmt.Sprintf("Parsing image version (%s): %s", image.Version, err))
return imageAttribute{}, semver.Semver{}, diags
}
if err := compatibility.BinaryWith(r.providerData.Version.String(), imageSemver.String()); err != nil {
diags.AddAttributeError(
path.Root("image").AtName("version"),
"Invalid image version",
fmt.Sprintf("Image version (%s) incompatible with provider version (%s): %s", image.Version, r.providerData.Version.String(), err))
}
return image, imageSemver, diags
}
// initRPCPayload groups the data required to run the init RPC.
type initRPCPayload struct {
csp cloudprovider.Provider // cloud service provider the cluster runs on.
@ -1178,6 +1192,12 @@ func (r *ClusterResource) getMicroserviceVersion(ctx context.Context, data *Clus
tflog.Info(ctx, fmt.Sprintf("No Microservice version specified. Using default version %s.", r.providerData.Version))
ver = r.providerData.Version
}
if err := config.ValidateMicroserviceVersion(r.providerData.Version, ver); err != nil {
diags.AddAttributeError(
path.Root("constellation_microservice_version"),
"Invalid microservice version",
fmt.Sprintf("Microservice version (%s) incompatible with provider version (%s): %s", ver, r.providerData.Version, err))
}
return ver, diags
}

View File

@ -7,14 +7,104 @@ SPDX-License-Identifier: AGPL-3.0-only
package provider
import (
"context"
"regexp"
"testing"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMicroserviceConstraint(t *testing.T) {
sut := &ClusterResource{
providerData: data.ProviderData{
Version: semver.NewFromInt(2, 15, 0, ""),
},
}
testCases := []struct {
name string
version string
expectedErrorCount int
}{
{
name: "outdated by 2 minor versions is invalid",
version: "v2.13.0",
expectedErrorCount: 1,
},
{
name: "outdated by 1 minor is allowed for upgrade",
version: "v2.14.0",
expectedErrorCount: 0,
},
{
name: "same version is valid",
version: "v2.15.0",
expectedErrorCount: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, diags := sut.getMicroserviceVersion(context.Background(), &ClusterResourceModel{
MicroserviceVersion: basetypes.NewStringValue(tc.version),
})
require.Equal(t, tc.expectedErrorCount, diags.ErrorsCount())
})
}
}
func TestViolatedImageConstraint(t *testing.T) {
sut := &ClusterResource{
providerData: data.ProviderData{
Version: semver.NewFromInt(2, 15, 0, ""),
},
}
testCases := []struct {
name string
version string
expectedErrorCount int
}{
{
name: "outdated by 2 minor versions is invalid",
version: "v2.13.0",
expectedErrorCount: 1,
},
{
name: "outdated by 1 minor is allowed for upgrade",
version: "v2.14.0",
expectedErrorCount: 0,
},
{
name: "same version is valid",
version: "v2.15.0",
expectedErrorCount: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
img := imageAttribute{
Version: tc.version,
}
input, diags := basetypes.NewObjectValueFrom(context.Background(), map[string]attr.Type{
"version": basetypes.StringType{},
"reference": basetypes.StringType{},
"short_path": basetypes.StringType{},
}, img)
require.Equal(t, 0, diags.ErrorsCount())
_, _, diags2 := sut.getImageVersion(context.Background(), &ClusterResourceModel{
Image: input,
})
require.Equal(t, tc.expectedErrorCount, diags2.ErrorsCount())
})
}
}
func TestAccClusterResourceImports(t *testing.T) {
// Set the path to the Terraform binary for acceptance testing when running under Bazel.
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }