diff --git a/internal/instance/resource_instance.go b/internal/instance/resource_instance.go index de0b40f..1f9db3d 100644 --- a/internal/instance/resource_instance.go +++ b/internal/instance/resource_instance.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -116,6 +117,21 @@ func (r InstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, re PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.Expressions{ + path.MatchRoot("source_instance"), + path.MatchRoot("source_file"), + }... + ), + stringvalidator.AtLeastOneOf( + path.Expressions{ + path.MatchRoot("image"), + path.MatchRoot("source_instance"), + path.MatchRoot("source_file"), + }... + ), + }, }, "ephemeral": schema.BoolAttribute{ @@ -205,6 +221,20 @@ func (r InstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, re PlanModifiers: []planmodifier.Object{ objectplanmodifier.RequiresReplace(), }, + Validators: []validator.Object{ + objectvalidator.ConflictsWith( + path.Expressions{ + path.MatchRoot("image"), + path.MatchRoot("source_file"), + }...), + objectvalidator.AtLeastOneOf( + path.Expressions{ + path.MatchRoot("image"), + path.MatchRoot("source_instance"), + path.MatchRoot("source_file"), + }... + ), + }, }, "source_file": schema.StringAttribute{ @@ -212,9 +242,28 @@ func (r InstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, re PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, - Validators: []validator.String{ + Validators: []validator.String{ stringvalidator.LengthAtLeast(1), - }, + stringvalidator.ConflictsWith( + path.Expressions{ + path.MatchRoot("image"), + path.MatchRoot("source_instance"), + path.MatchRoot("description"), + path.MatchRoot("type"), + path.MatchRoot("ephemeral"), + path.MatchRoot("profiles"), + path.MatchRoot("file"), + path.MatchRoot("config"), + }... + ), + stringvalidator.AtLeastOneOf( + path.Expressions{ + path.MatchRoot("image"), + path.MatchRoot("source_instance"), + path.MatchRoot("source_file"), + }... + ), + }, }, // Computed. @@ -383,29 +432,7 @@ func (r InstanceResource) ValidateConfig(ctx context.Context, req resource.Valid ) } - if !atMostOneOf(config.Image, config.SourceFile, config.SourceInstance) { - resp.Diagnostics.AddError( - "Invalid Configuration", - "At most one of image, source_file and source_instance can be set.", - ) - return - } - if !config.SourceFile.IsNull() { - // Instances from source_file are mutually exclusive with a series of other attributes. - if !config.Description.IsNull() || - !config.Type.IsNull() || - !config.Ephemeral.IsNull() || - !config.Profiles.IsNull() || - !config.Files.IsNull() || - !config.Config.IsNull() { - resp.Diagnostics.AddError( - "Invalid Configuration", - "Attribute source_file is mutually exclusive with description, type, ephemeral, profiles, file and config.", - ) - return - } - // With `incus import`, a storage pool can be provided optionally. // In order to support the same behavior with source_file, // a single device entry of type `disk` is allowed with exactly two properties