Skip to content

Commit

Permalink
Merge pull request #159 from SpiffyEight77/feat/import-storage-volume
Browse files Browse the repository at this point in the history
Add source_file to incus_storage_volume
  • Loading branch information
stgraber authored Nov 22, 2024
2 parents cebcc10 + 035c748 commit 5118735
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
12 changes: 12 additions & 0 deletions docs/resources/storage_volume.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ resource "incus_storage_volume" "volume1_copy" {
}
```

## Example to create volume from backup file

```hcl
resource "incus_storage_volume" "volume_from_backup" {
name = "restored-volume"
pool = "default"
source_file = "/path/to/volume.backup"
}
```

## Argument Reference

* `name` - **Required** - Name of the storage volume.
Expand All @@ -52,6 +62,8 @@ resource "incus_storage_volume" "volume1_copy" {

* `source_volume` - *Optional* - The source volume from which the volume will be created. See reference below.

* `source_file` - *Optional* - Path to a backup file from which the volume will be created.

The `source_volume` block supports:

* `name` - **Required** - Name of the storage volume.
Expand Down
70 changes: 70 additions & 0 deletions internal/storage/resource_storage_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package storage
import (
"context"
"fmt"
"os"
"strings"

"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
Expand Down Expand Up @@ -39,6 +41,7 @@ type StorageVolumeModel struct {
Remote types.String `tfsdk:"remote"`
Config types.Map `tfsdk:"config"`
SourceVolume types.Object `tfsdk:"source_volume"`
SourceFile types.String `tfsdk:"source_file"`

// Computed.
Location types.String `tfsdk:"location"`
Expand Down Expand Up @@ -159,6 +162,20 @@ func (r StorageVolumeResource) Schema(_ context.Context, _ resource.SchemaReques
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
},
Validators: []validator.Object{
objectvalidator.ConflictsWith(path.MatchRoot("source_file")),
},
},

"source_file": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.ConflictsWith(path.MatchRoot("source_volume")),
},
},

// Computed.
Expand Down Expand Up @@ -197,6 +214,9 @@ func (r StorageVolumeResource) Create(ctx context.Context, req resource.CreateRe
if !plan.SourceVolume.IsNull() {
r.copyStoragePoolVolume(ctx, resp, &plan)
return
} else if !plan.SourceFile.IsNull() {
r.importStoragePoolVolume(ctx, resp, &plan)
return
} else {
r.createStoragePoolVolume(ctx, resp, &plan)
return
Expand Down Expand Up @@ -302,6 +322,56 @@ func (r StorageVolumeResource) copyStoragePoolVolume(ctx context.Context, resp *
resp.Diagnostics.Append(diags...)
}

func (r StorageVolumeResource) importStoragePoolVolume(ctx context.Context, resp *resource.CreateResponse, plan *StorageVolumeModel) {
remote := plan.Remote.ValueString()
project := plan.Project.ValueString()
target := plan.Target.ValueString()
sourceFile := plan.SourceFile.ValueString()
server, err := r.provider.InstanceServer(remote, project, target)
if err != nil {
resp.Diagnostics.Append(errors.NewInstanceServerError(err))
return
}

poolName := plan.Pool.ValueString()
volName := plan.Name.ValueString()

file, err := os.Open(sourceFile)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to open source file %q", sourceFile), err.Error())
return
}
defer file.Close()

args := incus.StorageVolumeBackupArgs{
Name: volName,
BackupFile: file,
}

var opImport incus.Operation

if strings.HasSuffix(file.Name(), ".iso") {
opImport, err = server.CreateStoragePoolVolumeFromISO(poolName, args)
} else {
opImport, err = server.CreateStoragePoolVolumeFromBackup(poolName, args)
}

if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to create storage volume from file %q", volName), err.Error())
return
}

err = opImport.Wait()
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to create storage volume from file %q", volName), err.Error())
return
}

// Update Terraform state.
diags := r.SyncState(ctx, &resp.State, server, *plan)
resp.Diagnostics.Append(diags...)
}

func (r StorageVolumeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state StorageVolumeModel

Expand Down
79 changes: 79 additions & 0 deletions internal/storage/resource_storage_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package storage_test

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"

petname "github.com/dustinkirkland/golang-petname"
Expand Down Expand Up @@ -237,6 +240,66 @@ func TestAccStorageVolume_sourceVolume(t *testing.T) {
})
}

func TestAccStorageVolume_sourceFile(t *testing.T) {
poolName := petname.Generate(2, "-")
volumeName := petname.Generate(2, "-")

// Create temporary directory for backup file in /tmp
tempDir := "/tmp/incus-volume-test"
err := os.MkdirAll(tempDir, 0755)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)

backupFile := filepath.Join(tempDir, "backup.tar.gz")

resource.Test(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)

// Create test volume
cmd := exec.Command("incus", "storage", "volume", "create", "default", "test_volume_create")
if err := cmd.Run(); err != nil {
t.Fatal(err)
}

// Export volume backup
cmd = exec.Command("incus", "storage", "volume", "export", "default", "test_volume_create", backupFile)
if err := cmd.Run(); err != nil {
t.Fatal(err)
}

// Delete test volume
cmd = exec.Command("incus", "storage", "volume", "delete", "default", "test_volume_create")
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
},
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccStorageVolume_sourceFile(poolName, volumeName, backupFile),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("incus_storage_pool.pool1", "name", poolName),
resource.TestCheckResourceAttr("incus_storage_pool.pool1", "driver", "lvm"),
resource.TestCheckResourceAttr("incus_storage_volume.volume1", "name", volumeName),
resource.TestCheckResourceAttr("incus_storage_volume.volume1", "pool", poolName),
resource.TestCheckResourceAttr("incus_storage_volume.volume1", "source_file", backupFile),
),
},
{
Config: testAccStorageVolume_sourceFile(poolName, volumeName, backupFile),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
})
}

func testAccStorageVolume_basic(poolName, volumeName string) string {
return fmt.Sprintf(`
resource "incus_storage_pool" "pool1" {
Expand Down Expand Up @@ -369,3 +432,19 @@ resource "incus_storage_volume" "volume1_copy" {
`,
poolName, volumeName)
}

func testAccStorageVolume_sourceFile(poolName, volumeName, sourceFile string) string {
return fmt.Sprintf(`
resource "incus_storage_pool" "pool1" {
name = "%[1]s"
driver = "lvm"
}
resource "incus_storage_volume" "volume1" {
name = "%[2]s"
pool = incus_storage_pool.pool1.name
source_file = "%[3]s"
}
`,
poolName, volumeName, sourceFile)
}

0 comments on commit 5118735

Please sign in to comment.