fix: temp file collisions (#235)
* fix: use temp directory for file conversions * fix: update doc --------- Signed-off-by: matttrach <matt.trachier@suse.com>
This commit is contained in:
parent
1d94e1ad26
commit
0b40f575e6
|
|
@ -0,0 +1,19 @@
|
|||
# Multiple Snapshot Use Case
|
||||
|
||||
This is an example of snapshotting the same file multiple times.
|
||||
This show the same operations as the basic example,
|
||||
but it shows that you can have multiple snapshots working in parallel on the same file without collisions.
|
||||
|
||||
# Updating the snapshot
|
||||
|
||||
To get the snapshot to update you can send in the "update" argument and change it.
|
||||
The snapshot will update on that apply and remain static until the update argument is changed again.
|
||||
|
||||
# Base 64 Decode
|
||||
|
||||
Notice that the snapshot outputs use base64decode to return the actual file's value.
|
||||
|
||||
# Snapshots are Sensitive
|
||||
|
||||
You could achieve the goals of this resource using a terraform_data with some life-cycle options, except for this part.
|
||||
The Snapshot resource's "snapshot" attribute is sensitive, this keeps sensitive or long files from being spewed into the logs.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
terraform {
|
||||
backend "local" {}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
provider "file" {}
|
||||
|
||||
locals {
|
||||
update = (var.update == "" ? "code-change-necessary" : var.update)
|
||||
pesky_id = uuid()
|
||||
name = var.name
|
||||
directory = var.directory
|
||||
count = 5
|
||||
files = [for i in range(local.count) : format("%s_%d", local.name, i)]
|
||||
}
|
||||
# on first update the pesky_id and the snapshot will match
|
||||
# on subsequent updates the snapshot will remain as the first id and the pesky_id will change
|
||||
# when the update input is changed, then the snapshot will match again
|
||||
|
||||
resource "file_local" "snapshot_use_case_multiple" {
|
||||
name = local.name
|
||||
directory = local.directory
|
||||
contents = local.pesky_id
|
||||
}
|
||||
|
||||
resource "file_local_snapshot" "use_case_multiple" {
|
||||
depends_on = [
|
||||
file_local.snapshot_use_case_multiple,
|
||||
]
|
||||
for_each = toset(local.files)
|
||||
name = local.name
|
||||
directory = local.directory
|
||||
update_trigger = local.update
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
output "pesky_id" {
|
||||
value = local.pesky_id
|
||||
}
|
||||
|
||||
output "snapshots" {
|
||||
value = [
|
||||
for s in file_local_snapshot.use_case_multiple : base64decode(s.snapshot)
|
||||
]
|
||||
sensitive = true
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
variable "update" {
|
||||
type = string
|
||||
default = "code-change-necessary"
|
||||
}
|
||||
|
||||
variable "directory" {
|
||||
type = string
|
||||
default = "."
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
default = "snapshot_resource_use_case_multiple.txt"
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
file = {
|
||||
source = "rancher/file"
|
||||
version = ">= 0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
go.mod
1
go.mod
|
|
@ -4,6 +4,7 @@ go 1.24.0
|
|||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.0
|
||||
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ type FileClient interface {
|
|||
Compress(directory string, name string, compressedName string) error
|
||||
Encode(directory string, name string, encodedName string) error
|
||||
Hash(directory string, name string) (string, error) // Sha256Hash, error
|
||||
Copy(currentPath string, newPath string) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type MemoryFileClient struct {
|
||||
|
|
@ -83,3 +84,9 @@ func (c *MemoryFileClient) Hash(directory string, name string) (string, error) {
|
|||
|
||||
return hashString, nil
|
||||
}
|
||||
|
||||
func (c *MemoryFileClient) Copy(currentPath string, newPath string) error {
|
||||
c.file["directory"] = filepath.Dir(newPath)
|
||||
c.file["name"] = filepath.Base(newPath)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,3 +142,24 @@ func (c *OsFileClient) Hash(directory string, name string) (string, error) {
|
|||
hexContents := hex.EncodeToString(contentsHash)
|
||||
return hexContents, nil
|
||||
}
|
||||
|
||||
func (c *OsFileClient) Copy(currentPath string, newPath string) error {
|
||||
srcFile, err := os.Open(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(newPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ package file_local_snapshot
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
|
|
@ -132,9 +135,24 @@ func (r *LocalSnapshotResource) Create(ctx context.Context, req resource.CreateR
|
|||
pDir := plan.Directory.ValueString()
|
||||
pCompress := plan.Compress.ValueBool()
|
||||
|
||||
tempDir := filepath.Join(os.TempDir(), "terraform-provider-file", "snapshot", uuid.New().String())
|
||||
err := os.MkdirAll(tempDir, os.ModePerm)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error creating temporary directory: ", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// copy the file to a temporary directory to prevent issues with encoding and compressing large files
|
||||
err = r.client.Copy(filepath.Join(pDir, pName), filepath.Join(tempDir, pName))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error copying file to temporary directory: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
name := pName
|
||||
if pCompress {
|
||||
err := r.client.Compress(pDir, pName, "compressed_"+pName)
|
||||
err := r.client.Compress(tempDir, pName, "compressed_"+pName)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error compressing file: ", err.Error())
|
||||
return
|
||||
|
|
@ -142,19 +160,19 @@ func (r *LocalSnapshotResource) Create(ctx context.Context, req resource.CreateR
|
|||
name = "compressed_" + pName
|
||||
}
|
||||
|
||||
err := r.client.Encode(pDir, name, "encoded_"+pName)
|
||||
err = r.client.Encode(tempDir, name, "encoded_"+pName)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error encoding file: ", err.Error())
|
||||
return
|
||||
}
|
||||
_, encodedContents, err := r.client.Read(pDir, "encoded_"+pName)
|
||||
_, encodedContents, err := r.client.Read(tempDir, "encoded_"+pName)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||
return
|
||||
}
|
||||
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||
|
||||
hash, err := r.client.Hash(pDir, pName)
|
||||
hash, err := r.client.Hash(tempDir, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error hashing file: ", err.Error())
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
util "github.com/rancher/terraform-provider-file/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSnapshotMultipleBasic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "local_snapshot_multiple"
|
||||
repoRoot, err := util.GetRepoRoot(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting git root directory: %v", err)
|
||||
}
|
||||
exampleDir := filepath.Join(repoRoot, "examples", "use-cases", directory)
|
||||
testDir := filepath.Join(repoRoot, "test", "data", id)
|
||||
|
||||
err = util.Setup(t, id, "test/data")
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, &terraform.Options{})
|
||||
t.Fatalf("Error creating test data directories: %s", err)
|
||||
}
|
||||
statePath := filepath.Join(testDir, "tfstate")
|
||||
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
|
||||
TerraformDir: exampleDir,
|
||||
Vars: map[string]interface{}{
|
||||
"directory": testDir,
|
||||
"name": "basic_test.txt",
|
||||
},
|
||||
BackendConfig: map[string]interface{}{
|
||||
"path": statePath,
|
||||
},
|
||||
EnvVars: map[string]string{
|
||||
"TF_DATA_DIR": testDir,
|
||||
"TF_CLI_CONFIG_FILE": filepath.Join(repoRoot, "test", ".terraformrc"),
|
||||
"TF_IN_AUTOMATION": "1",
|
||||
"TF_CLI_ARGS_init": "-no-color",
|
||||
"TF_CLI_ARGS_plan": "-no-color",
|
||||
"TF_CLI_ARGS_apply": "-no-color",
|
||||
"TF_CLI_ARGS_destroy": "-no-color",
|
||||
"TF_CLI_ARGS_output": "-no-color",
|
||||
},
|
||||
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
||||
NoColor: true,
|
||||
Upgrade: true,
|
||||
// ExtraArgs: terraform.ExtraArgs{ Output: []string{"-json"} },
|
||||
})
|
||||
|
||||
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
t.Fatalf("Error creating file: %s", err)
|
||||
}
|
||||
outputs, err := terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
pesky_id := outputs["pesky_id"]
|
||||
snapshots, ok := outputs["snapshots"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("snapshots is not a []interface{}")
|
||||
}
|
||||
a := assert.New(t)
|
||||
a.Equal(pesky_id, snapshots[0], "On the first run the snapshot will match the id.")
|
||||
|
||||
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
t.Fatalf("Error creating file: %s", err)
|
||||
}
|
||||
outputs, err = terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
pesky_id = outputs["pesky_id"]
|
||||
snapshots, ok = outputs["snapshots"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("snapshots is not a []interface{}")
|
||||
}
|
||||
a.NotEqual(pesky_id, snapshots[0], "On subsequent runs the id will change, but the snapshot won't.")
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("Test failed...")
|
||||
} else {
|
||||
t.Log("Test passed...")
|
||||
}
|
||||
t.Log("Test complete, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
}
|
||||
Loading…
Reference in New Issue