diff --git a/docs/resources/machine.md b/docs/resources/machine.md index d78c9102..700a8fb8 100644 --- a/docs/resources/machine.md +++ b/docs/resources/machine.md @@ -34,6 +34,7 @@ resource "juju_machine" "this_machine" { - `constraints` (String) Machine constraints that overwrite those available from 'juju get-model-constraints' and provider's defaults. - `disks` (String) Storage constraints for disks to attach to the machine(s). - `name` (String) A name for the machine resource in Terraform. +- `placement` (String) Additional information about how to allocate the machine in the cloud. - `private_key_file` (String) The file path to read the private key from. - `public_key_file` (String) The file path to read the public key from. - `series` (String, Deprecated) The operating system series to install on the new machine(s). diff --git a/internal/juju/machines.go b/internal/juju/machines.go index ca16d423..1373adda 100644 --- a/internal/juju/machines.go +++ b/internal/juju/machines.go @@ -19,6 +19,7 @@ import ( "github.com/juju/juju/cmd/juju/common" "github.com/juju/juju/core/base" "github.com/juju/juju/core/constraints" + "github.com/juju/juju/core/instance" "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/manual" @@ -37,6 +38,7 @@ type CreateMachineInput struct { Constraints string Disks string Base string + Placement string Series string InstanceId string @@ -106,6 +108,22 @@ func (c machinesClient) CreateMachine(ctx context.Context, input *CreateMachineI var machineParams params.AddMachineParams + placement := input.Placement + if placement != "" { + machineParams.Placement, err = instance.ParsePlacement(placement) + if err == instance.ErrPlacementScopeMissing { + modelUUID, err := c.ModelUUID(input.ModelName) + if err != nil { + return nil, err + } + placement = modelUUID + ":" + placement + machineParams.Placement, err = instance.ParsePlacement(placement) + if err != nil { + return nil, err + } + } + } + if input.Constraints == "" { modelConstraints, err := modelConfigAPIClient.GetModelConstraints() if err != nil { @@ -300,11 +318,19 @@ func (c machinesClient) ReadMachine(input ReadMachineInput) (ReadMachineResponse return response, err } - machineStatus, exists := status.Machines[input.ID] + machineIDParts := strings.Split(input.ID, "/") + machineStatus, exists := status.Machines[machineIDParts[0]] if !exists { return response, fmt.Errorf("no status returned for machine: %s", input.ID) } c.Tracef("ReadMachine:Machine status result", map[string]interface{}{"machineStatus": machineStatus}) + if len(machineIDParts) > 1 { + // check for containers + machineStatus, exists = machineStatus.Containers[input.ID] + if !exists { + return response, fmt.Errorf("no status returned for container in machine: %s", input.ID) + } + } response.ID = machineStatus.Id response.Base, response.Series, err = baseAndSeriesFromParams(&machineStatus.Base) if err != nil { diff --git a/internal/provider/resource_machine.go b/internal/provider/resource_machine.go index df0fcfa0..7e7f9085 100644 --- a/internal/provider/resource_machine.go +++ b/internal/provider/resource_machine.go @@ -46,6 +46,7 @@ type machineResourceModel struct { Disks types.String `tfsdk:"disks"` Base types.String `tfsdk:"base"` Series types.String `tfsdk:"series"` + Placement types.String `tfsdk:"placement"` MachineID types.String `tfsdk:"machine_id"` SSHAddress types.String `tfsdk:"ssh_address"` PublicKeyFile types.String `tfsdk:"public_key_file"` @@ -87,6 +88,7 @@ const ( ConstraintsKey = "constraints" DisksKey = "disks" SeriesKey = "series" + PlacementKey = "placement" BaseKey = "base" MachineIDKey = "machine_id" SSHAddressKey = "ssh_address" @@ -169,6 +171,20 @@ func (r *machineResource) Schema(_ context.Context, req resource.SchemaRequest, }, DeprecationMessage: "Configure base instead. This attribute will be removed in the next major version of the provider.", }, + PlacementKey: schema.StringAttribute{ + Description: "Additional information about how to allocate the machine in the cloud.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expressions{ + path.MatchRoot(SSHAddressKey), + path.MatchRoot(ConstraintsKey), + }...), + }, + }, MachineIDKey: schema.StringAttribute{ Description: "The id of the machine Juju creates.", Computed: true, @@ -257,6 +273,7 @@ func (r *machineResource) Create(ctx context.Context, req resource.CreateRequest Base: data.Base.ValueString(), Series: data.Series.ValueString(), SSHAddress: data.SSHAddress.ValueString(), + Placement: data.Placement.ValueString(), PublicKeyFile: data.PublicKeyFile.ValueString(), PrivateKeyFile: data.PrivateKeyFile.ValueString(), }) diff --git a/internal/provider/resource_machine_test.go b/internal/provider/resource_machine_test.go index 8038162c..519a52a3 100644 --- a/internal/provider/resource_machine_test.go +++ b/internal/provider/resource_machine_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + internaltesting "github.com/juju/terraform-provider-juju/internal/testing" ) func TestAcc_ResourceMachine(t *testing.T) { @@ -64,6 +65,33 @@ func TestAcc_ResourceMachine_Minimal(t *testing.T) { }) } +func TestAcc_ResourceMachine_WithPlacement(t *testing.T) { + if testingCloud != LXDCloudTesting { + t.Skip(t.Name() + " only runs with LXD") + } + modelName := acctest.RandomWithPrefix("tf-test-machine") + resourceName := "juju_machine.this_machine_1" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: frameworkProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccResourceMachineWithPlacement(modelName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "model", modelName), + resource.TestCheckResourceAttr(resourceName, "machine_id", "0/lxd/0"), + resource.TestCheckResourceAttr(resourceName, "placement", "lxd:0"), + ), + }, + { + ImportStateVerify: false, + ImportState: true, + ResourceName: resourceName, + }, + }, + }) +} + func testAccResourceMachineBasicMinimal(modelName string) string { return fmt.Sprintf(` resource "juju_model" "this" { @@ -173,3 +201,27 @@ resource "juju_machine" "this_machine" { } `, modelName, IP, pubKeyPath, privKeyPath) } + +func testAccResourceMachineWithPlacement(modelName string) string { + return internaltesting.GetStringFromTemplateWithData( + "testAccResourceMachineWithPlacement", + ` +resource "juju_model" "{{.ModelName}}" { + name = "{{.ModelName}}" +} + +resource "juju_machine" "this_machine" { + name = "manually_provisioned_machine" + model = juju_model.{{.ModelName}}.name +} + +resource "juju_machine" "this_machine_1" { + model = juju_model.{{.ModelName}}.name + name = "this_machine" + placement = "lxd:0" + depends_on = [juju_machine.this_machine] + } +`, internaltesting.TemplateData{ + "ModelName": modelName, + }) +}