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 (
|
require (
|
||||||
github.com/google/go-cmp v0.7.0
|
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 v1.16.0
|
||||||
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
|
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
|
||||||
github.com/hashicorp/terraform-plugin-go v0.29.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
|
Compress(directory string, name string, compressedName string) error
|
||||||
Encode(directory string, name string, encodedName string) error
|
Encode(directory string, name string, encodedName string) error
|
||||||
Hash(directory string, name string) (string, error) // Sha256Hash, error
|
Hash(directory string, name string) (string, error) // Sha256Hash, error
|
||||||
|
Copy(currentPath string, newPath string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemoryFileClient struct {
|
type MemoryFileClient struct {
|
||||||
|
|
@ -83,3 +84,9 @@ func (c *MemoryFileClient) Hash(directory string, name string) (string, error) {
|
||||||
|
|
||||||
return hashString, nil
|
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)
|
hexContents := hex.EncodeToString(contentsHash)
|
||||||
return hexContents, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
"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()
|
pDir := plan.Directory.ValueString()
|
||||||
pCompress := plan.Compress.ValueBool()
|
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
|
name := pName
|
||||||
if pCompress {
|
if pCompress {
|
||||||
err := r.client.Compress(pDir, pName, "compressed_"+pName)
|
err := r.client.Compress(tempDir, pName, "compressed_"+pName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error compressing file: ", err.Error())
|
resp.Diagnostics.AddError("Error compressing file: ", err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -142,19 +160,19 @@ func (r *LocalSnapshotResource) Create(ctx context.Context, req resource.CreateR
|
||||||
name = "compressed_" + pName
|
name = "compressed_" + pName
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.client.Encode(pDir, name, "encoded_"+pName)
|
err = r.client.Encode(tempDir, name, "encoded_"+pName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error encoding file: ", err.Error())
|
resp.Diagnostics.AddError("Error encoding file: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, encodedContents, err := r.client.Read(pDir, "encoded_"+pName)
|
_, encodedContents, err := r.client.Read(tempDir, "encoded_"+pName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
plan.LocalSnapshot = types.StringValue(encodedContents)
|
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||||
|
|
||||||
hash, err := r.client.Hash(pDir, pName)
|
hash, err := r.client.Hash(tempDir, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error hashing file: ", err.Error())
|
resp.Diagnostics.AddError("Error hashing file: ", err.Error())
|
||||||
return
|
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