feat: add local directory management
Signed-off-by: matttrach <matt.trachier@suse.com>
This commit is contained in:
parent
bd0cb43401
commit
d848dafda5
|
|
@ -27,11 +27,10 @@ testacc: build
|
|||
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
||||
popd;
|
||||
|
||||
debug: build
|
||||
et: build
|
||||
export REPO_ROOT="../../../."; \
|
||||
export TF_LOG=DEBUG; \
|
||||
pushd ./test; \
|
||||
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
||||
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s -run=$(t); \
|
||||
popd;
|
||||
|
||||
.PHONY: fmt lint build install generate test testacc debug
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
# tflint-ignore: terraform_unused_declarations
|
||||
data "file_local_directory" "basic_example" {
|
||||
path = "example_directory"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# IDENTIFIER="$(echo -n "path/to/file" sha256sum | awk '{print $1}')"
|
||||
terraform import file_local_directory.example "IDENTIFIER"
|
||||
|
||||
# after this is run you will need to refine the resource further by setting the path and created properties.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
resource "file_local_directory" "basic_example" {
|
||||
path = "path/to/new/directory"
|
||||
permissions = "0700"
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ resource "file_local" "snapshot_file_basic_example" {
|
|||
name = "snapshot_resource_basic_example.txt"
|
||||
contents = "this is an example file that is used to show how snapshots work"
|
||||
}
|
||||
resource "file_snapshot" "basic_example" {
|
||||
resource "file_local_snapshot" "basic_example" {
|
||||
depends_on = [
|
||||
file_local.snapshot_file_basic_example,
|
||||
]
|
||||
|
|
@ -12,21 +12,21 @@ resource "file_snapshot" "basic_example" {
|
|||
update_trigger = "an arbitrary string"
|
||||
}
|
||||
output "snapshot_basic" {
|
||||
value = file_snapshot.basic_example.snapshot
|
||||
value = file_local_snapshot.basic_example.snapshot
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# A more advanced use case:
|
||||
# We use a file_local resource to write a local file in the current directory
|
||||
# then we create a snapshot of the file using file_snapshot
|
||||
# then we create a snapshot of the file using file_local_snapshot
|
||||
# then we update the file using a terraform_data resource
|
||||
# then we get the contents of the file using a file_local datasource
|
||||
# then we output both the file_local datasource and file_snapshot resource, observing that they are different
|
||||
# then we output both the file_local datasource and file_local_snapshot resource, observing that they are different
|
||||
resource "file_local" "snapshot_file_example" {
|
||||
name = "snapshot_resource_test.txt"
|
||||
contents = "this is an example file that is used to show how snapshots work"
|
||||
}
|
||||
resource "file_snapshot" "file_example" {
|
||||
resource "file_local_snapshot" "file_example" {
|
||||
depends_on = [
|
||||
file_local.snapshot_file_example,
|
||||
]
|
||||
|
|
@ -36,7 +36,7 @@ resource "file_snapshot" "file_example" {
|
|||
resource "terraform_data" "update_file" {
|
||||
depends_on = [
|
||||
file_local.snapshot_file_example,
|
||||
file_snapshot.file_example,
|
||||
file_local_snapshot.file_example,
|
||||
]
|
||||
provisioner "local-exec" {
|
||||
command = <<-EOT
|
||||
|
|
@ -47,7 +47,7 @@ resource "terraform_data" "update_file" {
|
|||
data "file_local" "snapshot_file_example_after_update" {
|
||||
depends_on = [
|
||||
file_local.snapshot_file_example,
|
||||
file_snapshot.file_example,
|
||||
file_local_snapshot.file_example,
|
||||
terraform_data.update_file,
|
||||
]
|
||||
name = "snapshot_resource_test.txt"
|
||||
|
|
@ -59,7 +59,7 @@ output "file" {
|
|||
# this updates a file that is used to show how snapshots work
|
||||
}
|
||||
output "snapshot" {
|
||||
value = base64decode(file_snapshot.file_example.snapshot)
|
||||
value = base64decode(file_local_snapshot.file_example.snapshot)
|
||||
sensitive = true
|
||||
# this is an example file that is used to show how snapshots work
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Local Directory Use Case
|
||||
|
||||
This is a more advanced use case for adding a directory.
|
||||
|
||||
The goal of this use case is to retrieve the files in the directory at a specific point in time.
|
||||
We don't want the live data because we add files that we don't want included in the output.
|
||||
|
||||
These are the steps:
|
||||
1. we generate a directory
|
||||
2. add files to it
|
||||
3. get the directory data
|
||||
4. save the directory data to a file in the directory
|
||||
5. snapshot the directory data
|
||||
6. output the snapshot
|
||||
|
||||
The resulting output will always be the files we first placed in the directory excluding the directory data file.
|
||||
|
||||
On the initial run the directory data will match the snapshot,
|
||||
but on subsequent runs the refresh phase will update the directory data to include the directory data file.
|
||||
Our snapshot data will always exclude this file though, since it only updates when we alter the update trigger.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
|
||||
provider "file" {}
|
||||
|
||||
locals {
|
||||
path = var.path
|
||||
}
|
||||
|
||||
resource "file_local_directory" "basic" {
|
||||
path = local.path
|
||||
}
|
||||
|
||||
resource "file_local" "a" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
]
|
||||
name = "a"
|
||||
directory = local.path
|
||||
contents = "An example file to place in the directory."
|
||||
}
|
||||
resource "file_local" "b" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
]
|
||||
name = "b"
|
||||
directory = local.path
|
||||
contents = "An example file to place in the directory."
|
||||
}
|
||||
resource "file_local" "c" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
]
|
||||
name = "c"
|
||||
directory = local.path
|
||||
contents = "An example file to place in the directory."
|
||||
}
|
||||
|
||||
data "file_local_directory" "basic" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
file_local.a,
|
||||
file_local.b,
|
||||
file_local.c,
|
||||
]
|
||||
path = local.path
|
||||
}
|
||||
|
||||
resource "file_local" "directory_info" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
file_local.a,
|
||||
file_local.b,
|
||||
file_local.c,
|
||||
data.file_local_directory.basic,
|
||||
]
|
||||
name = "directory_info.txt"
|
||||
directory = local.path
|
||||
contents = jsonencode(data.file_local_directory.basic)
|
||||
}
|
||||
|
||||
resource "file_local_snapshot" "directory_snapshot" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
file_local.a,
|
||||
file_local.b,
|
||||
file_local.c,
|
||||
data.file_local_directory.basic,
|
||||
file_local.directory_info,
|
||||
]
|
||||
name = "directory_info.txt"
|
||||
directory = local.path
|
||||
update_trigger = "manual"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
output "directory_resource" {
|
||||
value = jsonencode(file_local_directory.basic)
|
||||
}
|
||||
|
||||
output "directory_data_source" {
|
||||
value = jsonencode(data.file_local_directory.basic)
|
||||
}
|
||||
|
||||
output "snapshot" {
|
||||
value = base64decode(file_local_snapshot.directory_snapshot.snapshot)
|
||||
sensitive = true
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
variable "path" {
|
||||
type = string
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
file = {
|
||||
source = "rancher/file"
|
||||
version = ">= 0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Local Directory Use Case
|
||||
|
||||
This is the most basic use case for adding a directory.
|
||||
It creates the directory at the path specified, generating all subdirectories necessary to do so.
|
||||
It records the created directories and deletes only the ones it created on destroy.
|
||||
|
||||
This example will only set the required fields, all options accept the default value.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
terraform {
|
||||
backend "local" {}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
provider "file" {}
|
||||
|
||||
locals {
|
||||
path = var.path
|
||||
}
|
||||
|
||||
resource "file_local_directory" "basic" {
|
||||
path = local.path
|
||||
}
|
||||
|
||||
data "file_local_directory" "basic" {
|
||||
depends_on = [
|
||||
file_local_directory.basic,
|
||||
]
|
||||
path = local.path
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
output "directory_resource" {
|
||||
value = jsonencode(file_local_directory.basic)
|
||||
}
|
||||
|
||||
output "directory_data_source" {
|
||||
value = jsonencode(data.file_local_directory.basic)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
variable "path" {
|
||||
type = string
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
file = {
|
||||
source = "rancher/file"
|
||||
version = ">= 0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
terraform {
|
||||
backend "local" {}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ resource "file_local" "snapshot_use_case_basic" {
|
|||
directory = local.directory
|
||||
contents = local.pesky_id
|
||||
}
|
||||
resource "file_snapshot" "use_case_basic" {
|
||||
resource "file_local_snapshot" "use_case_basic" {
|
||||
depends_on = [
|
||||
file_local.snapshot_use_case_basic,
|
||||
]
|
||||
|
|
@ -4,6 +4,6 @@ output "pesky_id" {
|
|||
}
|
||||
|
||||
output "snapshot" {
|
||||
value = base64decode(file_snapshot.use_case_basic.snapshot)
|
||||
value = base64decode(file_local_snapshot.use_case_basic.snapshot)
|
||||
sensitive = true
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
file = {
|
||||
source = "rancher/file"
|
||||
version = ">= 0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Compressed Snapshot Use Case
|
||||
|
||||
This is an example of how you could use the file_snapshot resource.
|
||||
This is an example of how you could use the file_local_snapshot resource.
|
||||
WARNING! Please remember that Terraform must load the entire state into memory,
|
||||
make sure you have the resources available on the machine running Terraform to handle any file you save like this.
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
terraform {
|
||||
backend "local" {}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ resource "file_local" "snapshot_use_case_compressed" {
|
|||
directory = local.directory
|
||||
contents = local.pesky_id
|
||||
}
|
||||
resource "file_snapshot" "use_case_compressed" {
|
||||
resource "file_local_snapshot" "use_case_compressed" {
|
||||
depends_on = [
|
||||
file_local.snapshot_use_case_compressed,
|
||||
]
|
||||
|
|
@ -25,11 +25,11 @@ resource "file_snapshot" "use_case_compressed" {
|
|||
update_trigger = local.update
|
||||
compress = true
|
||||
}
|
||||
data "file_snapshot" "use_case_compressed" {
|
||||
data "file_local_snapshot" "use_case_compressed" {
|
||||
depends_on = [
|
||||
file_local.snapshot_use_case_compressed,
|
||||
file_snapshot.use_case_compressed,
|
||||
file_local_snapshot.use_case_compressed,
|
||||
]
|
||||
contents = file_snapshot.use_case_compressed.snapshot
|
||||
contents = file_local_snapshot.use_case_compressed.snapshot
|
||||
decompress = true
|
||||
}
|
||||
|
|
@ -4,6 +4,6 @@ output "pesky_id" {
|
|||
}
|
||||
|
||||
output "snapshot" {
|
||||
value = data.file_snapshot.use_case_compressed.data
|
||||
value = data.file_local_snapshot.use_case_compressed.data
|
||||
sensitive = true
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
file = {
|
||||
source = "rancher/file"
|
||||
version = ">= 0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1758262103,
|
||||
"narHash": "sha256-aBGl3XEOsjWw6W3AHiKibN7FeoG73dutQQEqnd/etR8=",
|
||||
"lastModified": 1759070547,
|
||||
"narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "12bd230118a1901a4a5d393f9f56b6ad7e571d01",
|
||||
"rev": "647e5c14cbd5067f44ac86b74f014962df460840",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package directory_client
|
||||
|
||||
type DirectoryClient interface {
|
||||
Create(path string, permissions string) (string, error) // Base of the newly created path (used in destroy), error
|
||||
// If directory isn't found the error message must have err.Error() == "directory not found"
|
||||
Read(path string) (string, map[string]map[string]string, error) // permissions, files info map, error
|
||||
Update(path string, permissions string) error
|
||||
Delete(path string) error // "path" should be the return from Create
|
||||
CreateFile(path string, data string, permissions string, lastModified string) error // create a file in the given directory
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package directory_client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ DirectoryClient = &MemoryDirectoryClient{} // make sure the MemoryDirectoryClient implements the DirectoryClient
|
||||
|
||||
type MemoryDirectoryClient struct {
|
||||
directory map[string]interface{}
|
||||
}
|
||||
|
||||
func (c *MemoryDirectoryClient) Create(path string, permissions string) (string, error) {
|
||||
|
||||
path = filepath.Clean(path)
|
||||
var base string
|
||||
if filepath.IsAbs(path) {
|
||||
base = filepath.VolumeName(path) + string(filepath.Separator)
|
||||
} else {
|
||||
p := strings.Split(path, string(filepath.Separator))
|
||||
base = p[0]
|
||||
}
|
||||
|
||||
c.directory = make(map[string]interface{})
|
||||
c.directory["permissions"] = permissions
|
||||
c.directory["path"] = path
|
||||
c.directory["base"] = base
|
||||
c.directory["info"] = map[string]map[string]string{}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (c *MemoryDirectoryClient) Read(path string) (string, map[string]map[string]string, error) {
|
||||
if c.directory == nil {
|
||||
return "", nil, fmt.Errorf("directory not found")
|
||||
}
|
||||
permissions, _ := c.directory["permissions"].(string)
|
||||
info, _ := c.directory["info"].(map[string]map[string]string)
|
||||
return permissions, info, nil
|
||||
}
|
||||
|
||||
func (c *MemoryDirectoryClient) Update(path string, permissions string) error {
|
||||
c.directory["permissions"] = permissions
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MemoryDirectoryClient) Delete(path string) error {
|
||||
c.directory = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MemoryDirectoryClient) CreateFile(path string, data string, permissions string, lastModified string) error {
|
||||
if c.directory == nil {
|
||||
return fmt.Errorf("directory not found")
|
||||
}
|
||||
c.directory["info"].(map[string]map[string]string)[path] = map[string]string{
|
||||
"Size": fmt.Sprintf("%d", len(data)),
|
||||
"Mode": permissions,
|
||||
"ModTime": lastModified,
|
||||
"IsDir": "false",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package directory_client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var _ DirectoryClient = &OsDirectoryClient{} // make sure the OsDirectoryClient implements the DirectoryClient
|
||||
|
||||
type OsDirectoryClient struct{
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (c *OsDirectoryClient) Create(path string, permissions string) (string, error) {
|
||||
created, err := makePath(path, permissions)
|
||||
if len(created) > 0 {
|
||||
fmt.Printf("created: %#v", created)
|
||||
return created[0], err
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OsDirectoryClient) Read(path string) (string, map[string]map[string]string, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil, fmt.Errorf("directory not found")
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
mode := fmt.Sprintf("%#o", info.Mode().Perm())
|
||||
|
||||
data, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
files := make(map[string]map[string]string)
|
||||
for _, file := range data {
|
||||
var isDir string
|
||||
fileInfo, err := file.Info()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
isDir = "true"
|
||||
} else {
|
||||
isDir = "false"
|
||||
}
|
||||
files[file.Name()] = map[string]string{
|
||||
"Size": strconv.FormatInt(fileInfo.Size(), 10),
|
||||
"Mode": fmt.Sprintf("%#o", fileInfo.Mode().Perm()),
|
||||
"ModTime": fileInfo.ModTime().String(),
|
||||
"IsDir": isDir,
|
||||
}
|
||||
}
|
||||
return mode, files, nil
|
||||
}
|
||||
|
||||
// The only thing that can be updated is the permissions.
|
||||
func (c *OsDirectoryClient) Update(path string, permissions string) error {
|
||||
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(path, os.FileMode(modeInt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OsDirectoryClient) Delete(path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func makePath(path string, permissions string) ([]string, error) {
|
||||
var created []string
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if info.IsDir() {
|
||||
return created, nil // Path already exists and is a directory.
|
||||
}
|
||||
// Path exists but is a file, which is an error.
|
||||
return nil, fmt.Errorf("path '%s' exists and is not a directory", path)
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
// There was an error, but not that the directory doesn't exist, something is up with the file system.
|
||||
return nil, err
|
||||
}
|
||||
// From here we know the filesystem is ready and the path doesn't exist.
|
||||
|
||||
parent := filepath.Dir(path)
|
||||
|
||||
if path == parent {
|
||||
// If we have reached a path with no parent then return the empty list.
|
||||
// This breaks the recursion.
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// Start a recursion.
|
||||
// This will recurse until path = parent, where parentCreated will be the empty list.
|
||||
parentCreated, err := makePath(parent, permissions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add the parent's created directories to our list.
|
||||
created = append(created, parentCreated...)
|
||||
|
||||
// The first time this point is reached we know:
|
||||
// - "created" is an empty list.
|
||||
// - the parent directory exists and is a valid directory.
|
||||
// - the filesystem is ready and the current path doesn't exist.
|
||||
// This means we are good to create the current path and return it.
|
||||
// Any other time we know:
|
||||
// - the parent directory exists and is a valid directory.
|
||||
// - the filesystem is ready and the current path doesn't exist.
|
||||
// - there was no error in the previous recursion.
|
||||
// This means the previous recursion must have successfully generated a directory.
|
||||
// In any recursion cycle from this point the parent directory exists and is valid.
|
||||
|
||||
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Mkdir(path, os.FileMode(modeInt)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// We successfully created the directory, add it to our list.
|
||||
created = append(created, path)
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// added to help with testing, use the file client to create files in production
|
||||
func (c *OsDirectoryClient) CreateFile(path string, data string, permissions string, lastModified string) error {
|
||||
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, []byte(data), os.FileMode(modeInt))
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@ type FileClient interface {
|
|||
Read(directory string, name string) (string, string, error) // permissions, contents, error
|
||||
Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error
|
||||
Delete(directory string, name string) error
|
||||
Compress(directory string, name string, compressedName string) error
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package file_local_directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
// "strconv"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
c "github.com/rancher/terraform-provider-file/internal/provider/directory_client"
|
||||
)
|
||||
|
||||
// The `var _` is a special Go construct that results in an unusable variable.
|
||||
// The purpose of these lines is to make sure our LocalDirectoryFileResource correctly implements the `resource.Resource“ interface.
|
||||
// These will fail at compilation time if the implementation is not satisfied.
|
||||
var _ datasource.DataSource = &LocalDirectoryDataSource{}
|
||||
|
||||
func NewLocalDirectoryDataSource() datasource.DataSource {
|
||||
return &LocalDirectoryDataSource{}
|
||||
}
|
||||
|
||||
type LocalDirectoryDataSource struct {
|
||||
client c.DirectoryClient
|
||||
}
|
||||
|
||||
type LocalDirectoryDataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Path types.String `tfsdk:"path"`
|
||||
Permissions types.String `tfsdk:"permissions"`
|
||||
Files []LocalDirectoryFileInfoModel `tfsdk:"files"`
|
||||
}
|
||||
|
||||
type LocalDirectoryFileInfoModel struct {
|
||||
Name types.String `tfsdk:"name"`
|
||||
Size types.String `tfsdk:"size"`
|
||||
Permissions types.String `tfsdk:"permissions"`
|
||||
LastModified types.String `tfsdk:"last_modified"`
|
||||
IsDirectory types.String `tfsdk:"is_directory"`
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_local_directory" // file_local_directory datasource
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "LocalDirectory File DataSource",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"path": schema.StringAttribute{
|
||||
MarkdownDescription: "Path to directory.",
|
||||
Required: true,
|
||||
},
|
||||
"permissions": schema.StringAttribute{
|
||||
MarkdownDescription: "Permissions of the directory.",
|
||||
Computed: true,
|
||||
},
|
||||
"files": schema.ListNestedAttribute{
|
||||
MarkdownDescription: "List of information about files in the directory.",
|
||||
Computed: true,
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"name": schema.StringAttribute{
|
||||
MarkdownDescription: "The file's name. ",
|
||||
Computed: true,
|
||||
},
|
||||
"size": schema.StringAttribute{
|
||||
MarkdownDescription: "The file's size in bytes. ",
|
||||
Computed: true,
|
||||
},
|
||||
"permissions": schema.StringAttribute{
|
||||
MarkdownDescription: "The file's permissions mode expressed in string format, eg. '0600'. ",
|
||||
Computed: true,
|
||||
},
|
||||
"last_modified": schema.StringAttribute{
|
||||
MarkdownDescription: "The UTC date of the last time the file was updated. ",
|
||||
Computed: true,
|
||||
},
|
||||
"is_directory": schema.StringAttribute{
|
||||
MarkdownDescription: "A string representation of whether or not the item is a directory or a file. " +
|
||||
"This will be 'true' if the item is a directory, or 'false' if it isn't.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
MarkdownDescription: "Identifier derived from sha256 hash of path. ",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
// Prevent panic if the provider has not been configured.
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read runs before all other resources are run, datasources only get the Read function.
|
||||
func (r *LocalDirectoryDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||
|
||||
if r.client == nil {
|
||||
tflog.Debug(ctx, "Configuring client with default OsDirectoryClient.")
|
||||
r.client = &c.OsDirectoryClient{}
|
||||
}
|
||||
|
||||
var config LocalDirectoryDataSourceModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
id := config.Id.ValueString()
|
||||
path := config.Path.ValueString()
|
||||
|
||||
perm, files, err := r.client.Read(path)
|
||||
if err != nil && err.Error() == "directory not found" {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("failed to read directory", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(path))
|
||||
id = hex.EncodeToString(hasher.Sum(nil))
|
||||
config.Id = types.StringValue(id)
|
||||
}
|
||||
config.Permissions = types.StringValue(perm)
|
||||
|
||||
config.Files = []LocalDirectoryFileInfoModel{}
|
||||
for fileName, fileData := range files {
|
||||
fileInfo := LocalDirectoryFileInfoModel{
|
||||
Name: types.StringValue(fileName),
|
||||
Size: types.StringValue(fileData["Size"]),
|
||||
Permissions: types.StringValue(fileData["Mode"]),
|
||||
LastModified: types.StringValue(fileData["ModTime"]),
|
||||
IsDirectory: types.StringValue(fileData["IsDir"]),
|
||||
}
|
||||
config.Files = append(config.Files, fileInfo)
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
||||
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package file_local_directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
c "github.com/rancher/terraform-provider-file/internal/provider/directory_client"
|
||||
)
|
||||
|
||||
const (
|
||||
// echo -n '/tmp/foo' | sha256sum | awk '{print $1}' #.
|
||||
testDirectoryId = "e2e1dcd28fea64180e4cd859b299ce67c4c02a3cbd49eca0042f7b5b47d241b5"
|
||||
testDirectoryPath = "/tmp/foo"
|
||||
defaultDirectoryPerm = "0755"
|
||||
)
|
||||
|
||||
func TestLocalDirectoryDataSourceMetadata(t *testing.T) {
|
||||
t.Run("Metadata function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryDataSource
|
||||
want datasource.MetadataResponse
|
||||
}{
|
||||
{"Basic test", LocalDirectoryDataSource{}, datasource.MetadataResponse{TypeName: "file_local_directory"}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := datasource.MetadataResponse{}
|
||||
tc.fit.Metadata(context.Background(), datasource.MetadataRequest{ProviderTypeName: "file"}, &res)
|
||||
got := res
|
||||
if got != tc.want {
|
||||
t.Errorf("%#v.Metadata() is %v; want %v", tc.fit, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalDirectoryDataSourceRead(t *testing.T) {
|
||||
t.Run("Read function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryDataSource
|
||||
have datasource.ReadRequest
|
||||
want datasource.ReadResponse
|
||||
setup map[string]interface{}
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
LocalDirectoryDataSource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getReadDataSourceRequest(t, map[string]interface{}{
|
||||
"path": testDirectoryPath,
|
||||
}),
|
||||
// want
|
||||
getReadDataSourceResponse(t, map[string]interface{}{
|
||||
"id": testDirectoryId,
|
||||
"path": testDirectoryPath,
|
||||
"permissions": defaultDirectoryPerm,
|
||||
"files": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": filepath.Join(testDirectoryPath, "test_file_a"),
|
||||
"size": "10",
|
||||
"permissions": "0700",
|
||||
"last_modified": "2025-09-29 16:09:15.039952008 +0000 UTC",
|
||||
"is_directory": "false",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": filepath.Join(testDirectoryPath, "test_file_b"),
|
||||
"size": "100",
|
||||
"permissions": "0400",
|
||||
"last_modified": "2021-02-18 00:56:32 +0000 UTC",
|
||||
"is_directory": "false",
|
||||
},
|
||||
},
|
||||
}),
|
||||
// setup
|
||||
map[string]interface{}{
|
||||
"path": testDirectoryPath,
|
||||
"permissions": defaultDirectoryPerm,
|
||||
"files": []map[string]string{
|
||||
{
|
||||
"name": "test_file_a",
|
||||
"size": "10",
|
||||
"permissions": "0700",
|
||||
"lastModified": "2025-09-29 16:09:15.039952008 +0000 UTC",
|
||||
},
|
||||
{
|
||||
"name": "test_file_b",
|
||||
"size": "100",
|
||||
"permissions": "0400",
|
||||
"lastModified": "2021-02-18 00:56:32 +0000 UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
path := tc.setup["path"].(string)
|
||||
permissions := tc.setup["permissions"].(string)
|
||||
files := tc.setup["files"].([]map[string]string)
|
||||
created, err := tc.fit.client.Create(path, permissions)
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
return
|
||||
}
|
||||
characterSet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
rand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for _, file := range files {
|
||||
size, err := strconv.Atoi(file["size"])
|
||||
if err != nil {
|
||||
t.Errorf("could not convert size '%s' to an integer: %v", file["size"], err)
|
||||
return
|
||||
}
|
||||
b := make([]byte, size)
|
||||
for i := range b {
|
||||
b[i] = characterSet[rand.Intn(len(characterSet))]
|
||||
}
|
||||
contents := string(b)
|
||||
err = tc.fit.client.CreateFile(filepath.Join(path, file["name"]), contents, file["permissions"], file["lastModified"])
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err := tc.fit.client.Delete(created); err != nil {
|
||||
t.Errorf("Error tearing down: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
r := getReadDataSourceResponseContainer()
|
||||
tc.fit.Read(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
val := map[string]tftypes.Value{}
|
||||
err = tc.want.State.Raw.As(&val)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting tc.want.State.Raw to map: %v", err)
|
||||
return
|
||||
}
|
||||
rawWantFiles := val["files"]
|
||||
|
||||
err = got.State.Raw.As(&val)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting got.State.Raw to map: %v", err)
|
||||
return
|
||||
}
|
||||
rawGotFiles := val["files"]
|
||||
|
||||
if diff := cmp.Diff(rawWantFiles, rawGotFiles); diff != "" {
|
||||
t.Errorf("Read() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Read() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//* Helpers *//
|
||||
|
||||
func getReadDataSourceRequest(t *testing.T, data map[string]interface{}) datasource.ReadRequest {
|
||||
objType := getDataObjectAttributeTypes()
|
||||
val := buildValue(t, objType, data)
|
||||
|
||||
return datasource.ReadRequest{
|
||||
Config: tfsdk.Config{
|
||||
Raw: val,
|
||||
Schema: getLocalDirectoryDataSourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getReadDataSourceResponseContainer() datasource.ReadResponse {
|
||||
return datasource.ReadResponse{
|
||||
State: tfsdk.State{Schema: getLocalDirectoryDataSourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
|
||||
func getReadDataSourceResponse(t *testing.T, data map[string]interface{}) datasource.ReadResponse {
|
||||
objType := getDataObjectAttributeTypes()
|
||||
val := buildValue(t, objType, data)
|
||||
|
||||
return datasource.ReadResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: val,
|
||||
Schema: getLocalDirectoryDataSourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//* Helper's Helpers *//
|
||||
|
||||
func buildValue(t *testing.T, tfType tftypes.Type, data interface{}) tftypes.Value {
|
||||
if data == nil {
|
||||
return tftypes.NewValue(tfType, nil)
|
||||
}
|
||||
|
||||
switch typ := tfType.(type) {
|
||||
case tftypes.Object:
|
||||
dataMap, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map[string]interface{} for tftypes.Object, got %T", data)
|
||||
}
|
||||
attrValues := make(map[string]tftypes.Value)
|
||||
for name, attrType := range typ.AttributeTypes {
|
||||
attrValues[name] = buildValue(t, attrType, dataMap[name])
|
||||
}
|
||||
return tftypes.NewValue(typ, attrValues)
|
||||
|
||||
case tftypes.List:
|
||||
dataSlice, ok := data.([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected []interface{} for tftypes.List, got %T", data)
|
||||
}
|
||||
elemValues := make([]tftypes.Value, 0, len(dataSlice))
|
||||
for _, v := range dataSlice {
|
||||
elemValues = append(elemValues, buildValue(t, typ.ElementType, v))
|
||||
}
|
||||
return tftypes.NewValue(typ, elemValues)
|
||||
|
||||
default:
|
||||
// Handle primitive types
|
||||
if tfType.Is(tftypes.String) {
|
||||
val, ok := data.(string)
|
||||
if !ok {
|
||||
t.Fatalf("Expected string for tftypes.String, got %T", data)
|
||||
}
|
||||
return tftypes.NewValue(tfType, val)
|
||||
}
|
||||
if tfType.Is(tftypes.Number) {
|
||||
var numVal interface{}
|
||||
switch v := data.(type) {
|
||||
case int:
|
||||
numVal = v
|
||||
case float64:
|
||||
numVal = v
|
||||
default:
|
||||
t.Fatalf("Expected int or float64 for tftypes.Number, got %T", data)
|
||||
}
|
||||
return tftypes.NewValue(tfType, numVal)
|
||||
}
|
||||
if tfType.Is(tftypes.Bool) {
|
||||
val, ok := data.(bool)
|
||||
if !ok {
|
||||
t.Fatalf("Expected bool for tftypes.Bool, got %T", data)
|
||||
}
|
||||
return tftypes.NewValue(tfType, val)
|
||||
}
|
||||
|
||||
t.Fatalf("Unsupported tftype: %T", tfType)
|
||||
return tftypes.Value{}
|
||||
}
|
||||
}
|
||||
|
||||
func getDataObjectAttributeTypes() tftypes.Object {
|
||||
return tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"id": tftypes.String,
|
||||
"path": tftypes.String,
|
||||
"permissions": tftypes.String,
|
||||
"files": tftypes.List{
|
||||
ElementType: tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"name": tftypes.String,
|
||||
"size": tftypes.String,
|
||||
"permissions": tftypes.String,
|
||||
"last_modified": tftypes.String,
|
||||
"is_directory": tftypes.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalDirectoryDataSourceSchema() *datasource.SchemaResponse {
|
||||
var testResource LocalDirectoryDataSource
|
||||
r := &datasource.SchemaResponse{}
|
||||
testResource.Schema(context.Background(), datasource.SchemaRequest{}, r)
|
||||
return r
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package file_local_directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
c "github.com/rancher/terraform-provider-file/internal/provider/directory_client"
|
||||
)
|
||||
|
||||
// The `var _` is a special Go construct that results in an unusable variable.
|
||||
// The purpose of these lines is to make sure our LocalDirectoryFileResource correctly implements the `resource.Resource“ interface.
|
||||
// These will fail at compilation time if the implementation is not satisfied.
|
||||
var _ resource.Resource = &LocalDirectoryResource{}
|
||||
var _ resource.ResourceWithImportState = &LocalDirectoryResource{}
|
||||
|
||||
func NewLocalDirectoryResource() resource.Resource {
|
||||
return &LocalDirectoryResource{}
|
||||
}
|
||||
|
||||
type LocalDirectoryResource struct {
|
||||
client c.DirectoryClient
|
||||
}
|
||||
|
||||
// LocalDirectoryResourceModel describes the resource data model.
|
||||
type LocalDirectoryResourceModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Path types.String `tfsdk:"path"`
|
||||
Permissions types.String `tfsdk:"permissions"`
|
||||
Created types.String `tfsdk:"created"`
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_local_directory" // file_local_directory resource
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "Local Directory resource.",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"path": schema.StringAttribute{
|
||||
MarkdownDescription: "Directory path, required. All subdirectories will also be created. Changing this forces recreate.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"permissions": schema.StringAttribute{
|
||||
MarkdownDescription: "The directory permissions to assign to the directory, defaults to '0700'. " +
|
||||
"In order to automatically create subdirectories the owner must have execute access, " +
|
||||
"ie. '0600' or less prevents the provider from creating subdirectories.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: stringdefault.StaticString("0700"),
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
MarkdownDescription: "Identifier derived from sha256 hash of path. ",
|
||||
Computed: true,
|
||||
},
|
||||
"created": schema.StringAttribute{
|
||||
MarkdownDescription: "The top level directory created. " +
|
||||
"eg. if 'path' = '/path/to/new/directory' and '/path/to' already exists, "+
|
||||
"but the rest doesn't, then 'created' will be '/path/to/new'. " +
|
||||
"This path will be recursively removed during destroy and recreate actions.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the provider for the resource if necessary.
|
||||
func (r *LocalDirectoryResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
// Prevent panic if the provider has not been configured.
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We should:
|
||||
// - generate reality and state in the Create function
|
||||
// - update state to match reality in the Read function
|
||||
// - update state to config and update reality to config in the Update function by looking for differences in the state and the config (trust read to collect reality)
|
||||
// - destroy reality and state in the Destroy function
|
||||
|
||||
func (r *LocalDirectoryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||
var err error
|
||||
|
||||
if r.client == nil {
|
||||
tflog.Debug(ctx, "Configuring client with default OsDirectoryClient.")
|
||||
r.client = &c.OsDirectoryClient{}
|
||||
}
|
||||
|
||||
var plan LocalDirectoryResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
path := plan.Path.ValueString()
|
||||
permString := plan.Permissions.ValueString()
|
||||
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(path))
|
||||
id := hex.EncodeToString(hasher.Sum(nil))
|
||||
plan.Id = types.StringValue(id)
|
||||
|
||||
cutPath, err := r.client.Create(path, permString);
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error creating file: ", err.Error())
|
||||
return
|
||||
}
|
||||
plan.Created = types.StringValue(cutPath)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||
|
||||
if r.client == nil {
|
||||
tflog.Debug(ctx, "Configuring client with default OsDirectoryClient.")
|
||||
r.client = &c.OsDirectoryClient{}
|
||||
}
|
||||
|
||||
var state LocalDirectoryResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
sPath := state.Path.ValueString()
|
||||
sPerm := state.Permissions.ValueString()
|
||||
|
||||
perm, data, err := r.client.Read(sPath)
|
||||
if err != nil && err.Error() == "directory not found" {
|
||||
// force recreate if directory not found
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
tflog.Debug(ctx, fmt.Sprintf("Read data: %#v", data))
|
||||
|
||||
if perm != sPerm {
|
||||
// update the state with the actual mode
|
||||
state.Permissions = types.StringValue(perm)
|
||||
}
|
||||
|
||||
// Only update permissions because id, path, and created should never change.
|
||||
// The directory resource manages a new directory, it is not meant to pull file information.
|
||||
// To retrieve file information in a directory, use the directory data source.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||
|
||||
if r.client == nil {
|
||||
tflog.Debug(ctx, "Configuring client with default OsDirectoryClient.")
|
||||
r.client = &c.OsDirectoryClient{}
|
||||
}
|
||||
|
||||
// Plan represents what is in the config, so plan = config
|
||||
var config LocalDirectoryResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &config)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
cPath := config.Path.ValueString()
|
||||
cPerm := config.Permissions.ValueString()
|
||||
|
||||
// Read updates state with reality, so state = reality
|
||||
var reality LocalDirectoryResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &reality)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
rPerm := reality.Permissions.ValueString()
|
||||
|
||||
if cPerm != rPerm {
|
||||
// Only update permissions because id, path, and created should never change.
|
||||
err := r.client.Update(cPath, cPerm)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error updating directory permissions: ", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
||||
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||
|
||||
// Allow the ability to inject a file client, but use the OsDirectoryClient by default.
|
||||
if r.client == nil {
|
||||
tflog.Debug(ctx, "Configuring client with default OsDirectoryClient.")
|
||||
r.client = &c.OsDirectoryClient{}
|
||||
}
|
||||
|
||||
var state LocalDirectoryResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
sCutPath := state.Created.ValueString()
|
||||
|
||||
if err := r.client.Delete(sCutPath); err != nil {
|
||||
resp.Diagnostics.AddError("Failed to delete directory: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||
}
|
||||
|
||||
func (r *LocalDirectoryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
|
|
@ -0,0 +1,618 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package file_local_directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
c "github.com/rancher/terraform-provider-file/internal/provider/directory_client"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultId = ""
|
||||
defaultPerm = "0700"
|
||||
defaultCreated = ""
|
||||
testPath = "path/to/new/directory"
|
||||
// echo -n "path/to/new/directory" | sha256sum | awk '{print $1}' #.
|
||||
testId = "2d020a0327fe0a114bf587a2b24894d67654203b0bd4428546ad5bf4ed7ed6a7"
|
||||
testCreated = "path"
|
||||
)
|
||||
|
||||
var booleanFields = []string{"fake"}
|
||||
|
||||
func TestLocalDirectoryResourceMetadata(t *testing.T) {
|
||||
t.Run("Metadata function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
want resource.MetadataResponse
|
||||
}{
|
||||
{"Basic test", LocalDirectoryResource{}, resource.MetadataResponse{TypeName: "file_local_directory"}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := resource.MetadataResponse{}
|
||||
tc.fit.Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "file"}, &res)
|
||||
got := res
|
||||
if got != tc.want {
|
||||
t.Errorf("%#v.Metadata() is %v; want %v", tc.fit, got, tc.want)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalDirectorySchema(t *testing.T) {
|
||||
t.Run("Schema function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
want resource.SchemaResponse
|
||||
}{
|
||||
{"Basic test", LocalDirectoryResource{}, *getLocalDirectoryResourceSchema()},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := resource.SchemaResponse{}
|
||||
tc.fit.Schema(context.Background(), resource.SchemaRequest{}, &r)
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Schema() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalDirectoryResourceCreate(t *testing.T) {
|
||||
t.Run("Create function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
have resource.CreateRequest
|
||||
want resource.CreateResponse
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getCreateRequest(t, map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"id": defaultId,
|
||||
"created": defaultCreated,
|
||||
}),
|
||||
// want
|
||||
getCreateResponse(t, map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"id": testId,
|
||||
"created": testCreated,
|
||||
}),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var plannedState LocalDirectoryResourceModel
|
||||
if diags := tc.have.Plan.Get(context.Background(), &plannedState); diags.HasError() {
|
||||
t.Errorf("Failed to get planned state: %v", diags)
|
||||
return
|
||||
}
|
||||
plannedPath := plannedState.Path.ValueString()
|
||||
r := getCreateResponseContainer()
|
||||
tc.fit.Create(context.Background(), tc.have, &r)
|
||||
defer func() {
|
||||
if err := tc.fit.client.Delete(plannedPath); err != nil {
|
||||
t.Errorf("Error cleaning up: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Create() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalDirectoryResourceRead(t *testing.T) {
|
||||
t.Run("Read function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
have resource.ReadRequest
|
||||
want resource.ReadResponse
|
||||
setup map[string]string
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getReadRequest(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"created": testCreated,
|
||||
"permissions": defaultPerm,
|
||||
}),
|
||||
// want
|
||||
getReadResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"created": testCreated,
|
||||
"permissions": defaultPerm,
|
||||
}),
|
||||
// setup
|
||||
map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
},
|
||||
},
|
||||
{
|
||||
"Updates permission",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getReadRequest(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"created": testCreated,
|
||||
"permissions": defaultPerm,
|
||||
}),
|
||||
// want
|
||||
getReadResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"created": testCreated,
|
||||
"permissions": "0777",
|
||||
}),
|
||||
// setup
|
||||
map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": "0777",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
created, err := tc.fit.client.Create(tc.setup["path"], tc.setup["permissions"])
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tc.fit.client.Delete(created); err != nil {
|
||||
t.Errorf("Error tearing down: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
r := getReadResponseContainer()
|
||||
tc.fit.Read(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Read() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
func TestLocalDirectoryResourceUpdate(t *testing.T) {
|
||||
t.Run("Update function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
have resource.UpdateRequest
|
||||
want resource.UpdateResponse
|
||||
setup map[string]string
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getUpdateRequest(t, map[string]map[string]string{
|
||||
"priorState": {
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"created": testCreated,
|
||||
},
|
||||
"plan": {
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"created": testCreated,
|
||||
},
|
||||
}),
|
||||
// want
|
||||
getUpdateResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"created": testCreated,
|
||||
}),
|
||||
// setup
|
||||
map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
},
|
||||
},
|
||||
{
|
||||
"Updates permissions",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getUpdateRequest(t, map[string]map[string]string{
|
||||
"priorState": {
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"created": testCreated,
|
||||
},
|
||||
"plan": {
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": "0755",
|
||||
"created": testCreated,
|
||||
},
|
||||
}),
|
||||
// want
|
||||
getUpdateResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": "0755",
|
||||
"created": testCreated,
|
||||
}),
|
||||
// setup
|
||||
map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
created, err := tc.fit.client.Create(tc.setup["path"],tc.setup["permissions"])
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tc.fit.client.Delete(created); err != nil {
|
||||
t.Errorf("Error tearing down: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
r := getUpdateResponseContainer()
|
||||
tc.fit.Update(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
var plannedState LocalDirectoryResourceModel
|
||||
if diags := tc.have.Plan.Get(context.Background(), &plannedState); diags.HasError() {
|
||||
t.Errorf("Failed to get planned state: %v", diags)
|
||||
return
|
||||
}
|
||||
plannedPath := plannedState.Path.ValueString()
|
||||
plannedPermissions := plannedState.Permissions.ValueString()
|
||||
permissionsAfterUpdate, _, err := tc.fit.client.Read(plannedPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read directory for update verification: %s", err)
|
||||
return
|
||||
}
|
||||
if permissionsAfterUpdate != plannedPermissions {
|
||||
t.Errorf("Directory permissions were not updated correctly. Got %q, want %q", permissionsAfterUpdate, plannedPermissions)
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Update() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalDirectoryResourceDelete(t *testing.T) {
|
||||
t.Run("Delete function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit LocalDirectoryResource
|
||||
have resource.DeleteRequest
|
||||
want resource.DeleteResponse
|
||||
setup map[string]string
|
||||
}{
|
||||
{
|
||||
"Basic test",
|
||||
LocalDirectoryResource{client: &c.MemoryDirectoryClient{}},
|
||||
// have
|
||||
getDeleteRequest(t, map[string]string{
|
||||
"id": testId,
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
"created": testCreated,
|
||||
}),
|
||||
// want
|
||||
getDeleteResponse(),
|
||||
// setup
|
||||
map[string]string{
|
||||
"path": testPath,
|
||||
"permissions": defaultPerm,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.fit.client.Create(tc.setup["path"], tc.setup["permissions"])
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
return
|
||||
}
|
||||
r := getDeleteResponseContainer()
|
||||
tc.fit.Delete(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
// Verify the directory was actually deleted
|
||||
if _, _, err := tc.fit.client.Read(tc.setup["path"]); err == nil || err.Error() != "directory not found" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected directory to be deleted, but it still exists.")
|
||||
return
|
||||
}
|
||||
t.Errorf("Expected directory to be deleted, but it still exists. Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
// verify that the directory was removed from state
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Errorf("Update() mismatch (-want +got):\n%s", diff)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// *** Test Helper Functions *** //
|
||||
|
||||
func getCreateRequest(t *testing.T, data map[string]string) resource.CreateRequest {
|
||||
planMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
if value == "" {
|
||||
planMap[key] = tftypes.NewValue(tftypes.Bool, tftypes.UnknownValue)
|
||||
} else {
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.CreateRequest{}
|
||||
}
|
||||
planMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
}
|
||||
} else {
|
||||
if value == "" {
|
||||
planMap[key] = tftypes.NewValue(tftypes.String, tftypes.UnknownValue)
|
||||
} else {
|
||||
planMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
planValue := tftypes.NewValue(getObjectAttributeTypes(), planMap)
|
||||
return resource.CreateRequest{
|
||||
Plan: tfsdk.Plan{
|
||||
Raw: planValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
func getCreateResponseContainer() resource.CreateResponse {
|
||||
return resource.CreateResponse{
|
||||
State: tfsdk.State{Schema: getLocalDirectoryResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
func getCreateResponse(t *testing.T, data map[string]string) resource.CreateResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.CreateResponse{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
return resource.CreateResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getReadRequest(t *testing.T, data map[string]string) resource.ReadRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.ReadRequest{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
return resource.ReadRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
func getReadResponseContainer() resource.ReadResponse {
|
||||
return resource.ReadResponse{
|
||||
State: tfsdk.State{Schema: getLocalDirectoryResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
func getReadResponse(t *testing.T, data map[string]string) resource.ReadResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.ReadResponse{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
return resource.ReadResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateRequest(t *testing.T, data map[string]map[string]string) resource.UpdateRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data["priorState"] {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.UpdateRequest{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
priorStateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
|
||||
planMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data["plan"] {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
if value == "" {
|
||||
planMap[key] = tftypes.NewValue(tftypes.Bool, tftypes.UnknownValue)
|
||||
} else {
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.UpdateRequest{}
|
||||
}
|
||||
planMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
}
|
||||
} else {
|
||||
if value == "" {
|
||||
planMap[key] = tftypes.NewValue(tftypes.String, tftypes.UnknownValue)
|
||||
} else {
|
||||
planMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
planValue := tftypes.NewValue(getObjectAttributeTypes(), planMap)
|
||||
|
||||
return resource.UpdateRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: priorStateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
Plan: tfsdk.Plan{
|
||||
Raw: planValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
func getUpdateResponseContainer() resource.UpdateResponse {
|
||||
return resource.UpdateResponse{
|
||||
State: tfsdk.State{Schema: getLocalDirectoryResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
func getUpdateResponse(t *testing.T, data map[string]string) resource.UpdateResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.UpdateResponse{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
return resource.UpdateResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getDeleteRequest(t *testing.T, data map[string]string) resource.DeleteRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||
return resource.DeleteRequest{}
|
||||
}
|
||||
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||
} else {
|
||||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getObjectAttributeTypes(), stateMap)
|
||||
return resource.DeleteRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getLocalDirectoryResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
func getDeleteResponseContainer() resource.DeleteResponse {
|
||||
// A delete response does not need a schema as it results in a null state.
|
||||
return resource.DeleteResponse{}
|
||||
}
|
||||
func getDeleteResponse() resource.DeleteResponse {
|
||||
return resource.DeleteResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: tftypes.Value{},
|
||||
Schema: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getObjectAttributeTypes() tftypes.Object {
|
||||
return tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"path": tftypes.String,
|
||||
"permissions": tftypes.String,
|
||||
"created": tftypes.String,
|
||||
"id": tftypes.String,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalDirectoryResourceSchema() *resource.SchemaResponse {
|
||||
var testResource LocalDirectoryResource
|
||||
r := &resource.SchemaResponse{}
|
||||
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
||||
return r
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package file_snapshot
|
||||
package file_local_snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -19,29 +19,29 @@ import (
|
|||
// The `var _` is a special Go construct that results in an unusable variable.
|
||||
// The purpose of these lines is to make sure our LocalFileDataSource correctly implements the `datasource.DataSource“ interface.
|
||||
// These will fail at compilation time if the implementation is not satisfied.
|
||||
var _ datasource.DataSource = &SnapshotDataSource{}
|
||||
var _ datasource.DataSource = &LocalSnapshotDataSource{}
|
||||
|
||||
func NewSnapshotDataSource() datasource.DataSource {
|
||||
return &SnapshotDataSource{}
|
||||
func NewLocalSnapshotDataSource() datasource.DataSource {
|
||||
return &LocalSnapshotDataSource{}
|
||||
}
|
||||
|
||||
type SnapshotDataSource struct{}
|
||||
type LocalSnapshotDataSource struct{}
|
||||
|
||||
type SnapshotDataSourceModel struct {
|
||||
type LocalSnapshotDataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Contents types.String `tfsdk:"contents"`
|
||||
Data types.String `tfsdk:"data"`
|
||||
Decompress types.Bool `tfsdk:"decompress"`
|
||||
}
|
||||
|
||||
func (r *SnapshotDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_snapshot" // file_snapshot
|
||||
func (r *LocalSnapshotDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_local_snapshot" // _local_snapshot
|
||||
}
|
||||
|
||||
func (r *SnapshotDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
func (r *LocalSnapshotDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "File Snapshot data source. \n" +
|
||||
"This data source retrieves the contents of a file from the output of a file_snapshot datasource." +
|
||||
MarkdownDescription: "File LocalSnapshot data source. \n" +
|
||||
"This data source retrieves the contents of a file from the output of a file_local_snapshot datasource." +
|
||||
"Warning! Using this resource places the plain text contents of the snapshot in your state file.",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
|
|
@ -74,7 +74,7 @@ func (r *SnapshotDataSource) Schema(ctx context.Context, req datasource.SchemaRe
|
|||
}
|
||||
}
|
||||
|
||||
func (r *SnapshotDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
func (r *LocalSnapshotDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
// Prevent panic if the provider has not been configured.
|
||||
// This only configures the provider, so anything here must be available in the provider package to configure.
|
||||
// If you want to configure a client, do that in the Create/Read/Update/Delete functions.
|
||||
|
|
@ -83,10 +83,10 @@ func (r *SnapshotDataSource) Configure(ctx context.Context, req datasource.Confi
|
|||
}
|
||||
}
|
||||
|
||||
func (r *SnapshotDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
func (r *LocalSnapshotDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Read Request Object: %+v", req))
|
||||
|
||||
var config SnapshotDataSourceModel
|
||||
var config LocalSnapshotDataSourceModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package file_snapshot
|
||||
package file_local_snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -30,14 +30,14 @@ const (
|
|||
|
||||
var snapshotDataSourceBooleanFields = []string{"decompress"}
|
||||
|
||||
func TestSnapshotDataSourceMetadata(t *testing.T) {
|
||||
func TestLocalSnapshotDataSourceMetadata(t *testing.T) {
|
||||
t.Run("Metadata function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotDataSource
|
||||
fit LocalSnapshotDataSource
|
||||
want datasource.MetadataResponse
|
||||
}{
|
||||
{"Basic test", SnapshotDataSource{}, datasource.MetadataResponse{TypeName: "file_snapshot"}},
|
||||
{"Basic test", LocalSnapshotDataSource{}, datasource.MetadataResponse{TypeName: "file_local_snapshot"}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -52,14 +52,14 @@ func TestSnapshotDataSourceMetadata(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotDataSourceSchema(t *testing.T) {
|
||||
func TestLocalSnapshotDataSourceSchema(t *testing.T) {
|
||||
t.Run("Schema function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotDataSource
|
||||
fit LocalSnapshotDataSource
|
||||
want datasource.SchemaResponse
|
||||
}{
|
||||
{"Basic test", SnapshotDataSource{}, *getSnapshotDataSourceSchema()},
|
||||
{"Basic test", LocalSnapshotDataSource{}, *getLocalSnapshotDataSourceSchema()},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -74,26 +74,26 @@ func TestSnapshotDataSourceSchema(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotDataSourceRead(t *testing.T) {
|
||||
func TestLocalSnapshotDataSourceRead(t *testing.T) {
|
||||
t.Run("Read function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotDataSource
|
||||
fit LocalSnapshotDataSource
|
||||
have datasource.ReadRequest
|
||||
want datasource.ReadResponse
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
SnapshotDataSource{},
|
||||
LocalSnapshotDataSource{},
|
||||
// have
|
||||
getSnapshotDataSourceReadRequest(t, map[string]string{
|
||||
getLocalSnapshotDataSourceReadRequest(t, map[string]string{
|
||||
"id": "", // id is computed.
|
||||
"data": "", // data is computed.
|
||||
"contents": testDataEncoded,
|
||||
"decompress": defaultDecompress,
|
||||
}),
|
||||
// want
|
||||
getSnapshotDataSourceReadResponse(t, map[string]string{
|
||||
getLocalSnapshotDataSourceReadResponse(t, map[string]string{
|
||||
"id": testDataEncodedId,
|
||||
"data": testDataContents,
|
||||
"contents": testDataEncoded,
|
||||
|
|
@ -102,16 +102,16 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"Compressed",
|
||||
SnapshotDataSource{},
|
||||
LocalSnapshotDataSource{},
|
||||
// have
|
||||
getSnapshotDataSourceReadRequest(t, map[string]string{
|
||||
getLocalSnapshotDataSourceReadRequest(t, map[string]string{
|
||||
"id": "", // id is computed.
|
||||
"data": "", // data is computed.
|
||||
"contents": testDataCompressed,
|
||||
"decompress": "true",
|
||||
}),
|
||||
// want
|
||||
getSnapshotDataSourceReadResponse(t, map[string]string{
|
||||
getLocalSnapshotDataSourceReadResponse(t, map[string]string{
|
||||
"id": testDataCompressedId,
|
||||
"data": testDataContents,
|
||||
"contents": testDataCompressed,
|
||||
|
|
@ -121,7 +121,7 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getSnapshotDataSourceReadResponseContainer()
|
||||
r := getLocalSnapshotDataSourceReadResponseContainer()
|
||||
tc.fit.Read(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
|
|
@ -135,7 +135,7 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
|||
// *** Test Helper Functions *** //
|
||||
|
||||
// Read.
|
||||
func getSnapshotDataSourceReadRequest(t *testing.T, data map[string]string) datasource.ReadRequest {
|
||||
func getLocalSnapshotDataSourceReadRequest(t *testing.T, data map[string]string) datasource.ReadRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotDataSourceBooleanFields, key) { // snapshotDataSourceBooleanFields is a constant
|
||||
|
|
@ -148,22 +148,22 @@ func getSnapshotDataSourceReadRequest(t *testing.T, data map[string]string) data
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotDataSourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotDataSourceAttributeTypes(), stateMap)
|
||||
return datasource.ReadRequest{
|
||||
Config: tfsdk.Config{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotDataSourceSchema().Schema,
|
||||
Schema: getLocalSnapshotDataSourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotDataSourceReadResponseContainer() datasource.ReadResponse {
|
||||
func getLocalSnapshotDataSourceReadResponseContainer() datasource.ReadResponse {
|
||||
return datasource.ReadResponse{
|
||||
State: tfsdk.State{Schema: getSnapshotDataSourceSchema().Schema},
|
||||
State: tfsdk.State{Schema: getLocalSnapshotDataSourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotDataSourceReadResponse(t *testing.T, data map[string]string) datasource.ReadResponse {
|
||||
func getLocalSnapshotDataSourceReadResponse(t *testing.T, data map[string]string) datasource.ReadResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotDataSourceBooleanFields, key) { // snapshotDataSourceBooleanFields is a constant
|
||||
|
|
@ -176,17 +176,17 @@ func getSnapshotDataSourceReadResponse(t *testing.T, data map[string]string) dat
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotDataSourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotDataSourceAttributeTypes(), stateMap)
|
||||
return datasource.ReadResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotDataSourceSchema().Schema,
|
||||
Schema: getLocalSnapshotDataSourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// The helpers helpers.
|
||||
func getSnapshotDataSourceAttributeTypes() tftypes.Object {
|
||||
func getLocalSnapshotDataSourceAttributeTypes() tftypes.Object {
|
||||
return tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"id": tftypes.String,
|
||||
|
|
@ -197,8 +197,8 @@ func getSnapshotDataSourceAttributeTypes() tftypes.Object {
|
|||
}
|
||||
}
|
||||
|
||||
func getSnapshotDataSourceSchema() *datasource.SchemaResponse {
|
||||
var testDataSource SnapshotDataSource
|
||||
func getLocalSnapshotDataSourceSchema() *datasource.SchemaResponse {
|
||||
var testDataSource LocalSnapshotDataSource
|
||||
r := &datasource.SchemaResponse{}
|
||||
testDataSource.Schema(context.Background(), datasource.SchemaRequest{}, r)
|
||||
return r
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package file_snapshot
|
||||
package file_local_snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -20,34 +20,34 @@ import (
|
|||
// The `var _` is a special Go construct that results in an unusable variable.
|
||||
// The purpose of these lines is to make sure our LocalFileResource correctly implements the `resource.Resource“ interface.
|
||||
// These will fail at compilation time if the implementation is not satisfied.
|
||||
var _ resource.Resource = &SnapshotResource{}
|
||||
var _ resource.ResourceWithImportState = &SnapshotResource{}
|
||||
var _ resource.Resource = &LocalSnapshotResource{}
|
||||
var _ resource.ResourceWithImportState = &LocalSnapshotResource{}
|
||||
|
||||
func NewSnapshotResource() resource.Resource {
|
||||
return &SnapshotResource{}
|
||||
func NewLocalSnapshotResource() resource.Resource {
|
||||
return &LocalSnapshotResource{}
|
||||
}
|
||||
|
||||
type SnapshotResource struct {
|
||||
type LocalSnapshotResource struct {
|
||||
client c.FileClient
|
||||
}
|
||||
|
||||
// SnapshotResourceModel describes the resource data model.
|
||||
type SnapshotResourceModel struct {
|
||||
// LocalSnapshotResourceModel describes the resource data model.
|
||||
type LocalSnapshotResourceModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Directory types.String `tfsdk:"directory"`
|
||||
Snapshot types.String `tfsdk:"snapshot"`
|
||||
LocalSnapshot types.String `tfsdk:"snapshot"`
|
||||
UpdateTrigger types.String `tfsdk:"update_trigger"`
|
||||
Compress types.Bool `tfsdk:"compress"`
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_snapshot" // file_snapshot
|
||||
func (r *LocalSnapshotResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_local_snapshot" // file_local_snapshot
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
func (r *LocalSnapshotResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "File Snapshot resource. \n" +
|
||||
MarkdownDescription: "File LocalSnapshot resource. \n" +
|
||||
"This resource saves some content in state and doesn't update it until the trigger argument changes. " +
|
||||
"The refresh phase doesn't update state, instead " +
|
||||
"the state can only change on create or update and only when the update_trigger argument changes.",
|
||||
|
|
@ -100,7 +100,7 @@ func (r *SnapshotResource) Schema(ctx context.Context, req resource.SchemaReques
|
|||
}
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
func (r *LocalSnapshotResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
// Prevent panic if the provider has not been configured.
|
||||
// This only configures the provider, so anything here must be available in the provider package to configure.
|
||||
// If you want to configure a client, do that in the Create/Read/Update/Delete functions.
|
||||
|
|
@ -115,7 +115,7 @@ func (r *SnapshotResource) Configure(ctx context.Context, req resource.Configure
|
|||
// - update reality and state to match plan in the Update function (don't compare old state, just override)
|
||||
// - destroy reality in the Destroy function (state is handled automatically)
|
||||
|
||||
func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
func (r *LocalSnapshotResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Create Request Object: %+v", req))
|
||||
|
||||
if r.client == nil {
|
||||
|
|
@ -123,7 +123,7 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
|
|||
r.client = &c.OsFileClient{}
|
||||
}
|
||||
|
||||
var plan SnapshotResourceModel
|
||||
var plan LocalSnapshotResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -152,7 +152,7 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
|
|||
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||
return
|
||||
}
|
||||
plan.Snapshot = types.StringValue(encodedContents)
|
||||
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||
|
||||
hash, err := r.client.Hash(pDir, pName)
|
||||
if err != nil {
|
||||
|
|
@ -180,10 +180,10 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
|
|||
tflog.Debug(ctx, fmt.Sprintf("Create Response Object: %+v", *resp))
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
func (r *LocalSnapshotResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Read Request Object: %+v", req))
|
||||
|
||||
var state SnapshotResourceModel
|
||||
var state LocalSnapshotResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -198,7 +198,7 @@ func (r *SnapshotResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
// a difference between the plan and the state has been found.
|
||||
// we want to update reality and state to match the plan.
|
||||
// our snapshot will only update if the update trigger has changed.
|
||||
func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
func (r *LocalSnapshotResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Update Request Object: %+v", req))
|
||||
|
||||
if r.client == nil {
|
||||
|
|
@ -206,7 +206,7 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
r.client = &c.OsFileClient{}
|
||||
}
|
||||
|
||||
var plan SnapshotResourceModel
|
||||
var plan LocalSnapshotResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -216,14 +216,14 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
pDir := plan.Directory.ValueString()
|
||||
pUpdateTrigger := plan.UpdateTrigger.ValueString()
|
||||
|
||||
var state SnapshotResourceModel
|
||||
var state LocalSnapshotResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
sUpdateTrigger := state.UpdateTrigger.ValueString()
|
||||
sSnapshot := state.Snapshot.ValueString()
|
||||
sLocalSnapshot := state.LocalSnapshot.ValueString()
|
||||
sCompress := state.Compress.ValueBool()
|
||||
sId := state.Id.ValueString()
|
||||
|
||||
|
|
@ -252,10 +252,10 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||
return
|
||||
}
|
||||
plan.Snapshot = types.StringValue(encodedContents)
|
||||
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||
} else {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Update trigger hasn't changed, keeping previous snapshot (%s).", sSnapshot))
|
||||
plan.Snapshot = types.StringValue(sSnapshot)
|
||||
tflog.Debug(ctx, fmt.Sprintf("Update trigger hasn't changed, keeping previous snapshot (%s).", sLocalSnapshot))
|
||||
plan.LocalSnapshot = types.StringValue(sLocalSnapshot)
|
||||
}
|
||||
|
||||
tflog.Debug(ctx, fmt.Sprintf("Setting state to this plan: \n%+v", &plan))
|
||||
|
|
@ -276,7 +276,7 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
tflog.Debug(ctx, fmt.Sprintf("Update Response Object: %+v", *resp))
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
func (r *LocalSnapshotResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
tflog.Debug(ctx, fmt.Sprintf("Delete Request Object: %+v", req))
|
||||
|
||||
// delete is a no-op
|
||||
|
|
@ -284,6 +284,6 @@ func (r *SnapshotResource) Delete(ctx context.Context, req resource.DeleteReques
|
|||
tflog.Debug(ctx, fmt.Sprintf("Delete Response Object: %+v", *resp))
|
||||
}
|
||||
|
||||
func (r *SnapshotResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
func (r *LocalSnapshotResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package file_snapshot
|
||||
package file_local_snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -34,14 +34,14 @@ const (
|
|||
|
||||
var snapshotResourceBooleanFields = []string{"compress"}
|
||||
|
||||
func TestSnapshotResourceMetadata(t *testing.T) {
|
||||
func TestLocalSnapshotResourceMetadata(t *testing.T) {
|
||||
t.Run("Metadata function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
want resource.MetadataResponse
|
||||
}{
|
||||
{"Basic test", SnapshotResource{}, resource.MetadataResponse{TypeName: "file_snapshot"}},
|
||||
{"Basic test", LocalSnapshotResource{}, resource.MetadataResponse{TypeName: "file_local_snapshot"}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -56,14 +56,14 @@ func TestSnapshotResourceMetadata(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotResourceSchema(t *testing.T) {
|
||||
func TestLocalSnapshotResourceSchema(t *testing.T) {
|
||||
t.Run("Schema function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
want resource.SchemaResponse
|
||||
}{
|
||||
{"Basic test", SnapshotResource{}, *getSnapshotResourceSchema()},
|
||||
{"Basic test", LocalSnapshotResource{}, *getLocalSnapshotResourceSchema()},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -78,20 +78,20 @@ func TestSnapshotResourceSchema(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotResourceCreate(t *testing.T) {
|
||||
func TestLocalSnapshotResourceCreate(t *testing.T) {
|
||||
t.Run("Create function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
have resource.CreateRequest
|
||||
want resource.CreateResponse
|
||||
setup map[string]string
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
||||
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||
// have
|
||||
getSnapshotResourceCreateRequest(t, map[string]string{
|
||||
getLocalSnapshotResourceCreateRequest(t, map[string]string{
|
||||
"id": "",
|
||||
"snapshot": "",
|
||||
"name": testName,
|
||||
|
|
@ -100,7 +100,7 @@ func TestSnapshotResourceCreate(t *testing.T) {
|
|||
"compress": defaultCompress,
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceCreateResponse(t, map[string]string{
|
||||
getLocalSnapshotResourceCreateResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
"name": testName,
|
||||
|
|
@ -118,7 +118,7 @@ func TestSnapshotResourceCreate(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getSnapshotResourceCreateResponseContainer()
|
||||
r := getLocalSnapshotResourceCreateResponseContainer()
|
||||
if err := tc.fit.client.Create(tc.setup["directory"], tc.setup["name"], tc.setup["contents"], defaultPermissions); err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
}
|
||||
|
|
@ -137,19 +137,19 @@ func TestSnapshotResourceCreate(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
func TestSnapshotResourceRead(t *testing.T) {
|
||||
func TestLocalSnapshotResourceRead(t *testing.T) {
|
||||
t.Run("Read function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
have resource.ReadRequest
|
||||
want resource.ReadResponse
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
SnapshotResource{},
|
||||
LocalSnapshotResource{},
|
||||
// have
|
||||
getSnapshotResourceReadRequest(t, map[string]string{
|
||||
getLocalSnapshotResourceReadRequest(t, map[string]string{
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
"name": testName,
|
||||
|
|
@ -158,7 +158,7 @@ func TestSnapshotResourceRead(t *testing.T) {
|
|||
"compress": defaultCompress,
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceReadResponse(t, map[string]string{
|
||||
getLocalSnapshotResourceReadResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
"name": testName,
|
||||
|
|
@ -170,7 +170,7 @@ func TestSnapshotResourceRead(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getSnapshotResourceReadResponseContainer()
|
||||
r := getLocalSnapshotResourceReadResponseContainer()
|
||||
tc.fit.Read(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
|
|
@ -181,20 +181,20 @@ func TestSnapshotResourceRead(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotResourceUpdate(t *testing.T) {
|
||||
func TestLocalSnapshotResourceUpdate(t *testing.T) {
|
||||
t.Run("Update function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
have resource.UpdateRequest
|
||||
want resource.UpdateResponse
|
||||
setup map[string]string
|
||||
}{
|
||||
{
|
||||
"Basic",
|
||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
||||
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||
// have
|
||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
"priorState": {
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
|
|
@ -213,7 +213,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
"name": testName,
|
||||
|
|
@ -230,9 +230,9 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"Updates when trigger changes",
|
||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
||||
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||
// have
|
||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
"priorState": {
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
|
|
@ -251,7 +251,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
"id": testId, // id shouldn't change
|
||||
"snapshot": "dGhlc2UgY29udGVudHMgYXJlIHVwZGF0ZWQgZm9yIHRlc3Rpbmc=", // echo -n "these contents are updated for testing" | base64 -w 0 #.
|
||||
"name": testName,
|
||||
|
|
@ -268,9 +268,9 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"Doesn't update when trigger stays the same",
|
||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
||||
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||
// have
|
||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||
"priorState": {
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
|
|
@ -289,7 +289,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||
"id": testId,
|
||||
"snapshot": testEncoded,
|
||||
"name": testName,
|
||||
|
|
@ -307,7 +307,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getSnapshotResourceUpdateResponseContainer()
|
||||
r := getLocalSnapshotResourceUpdateResponseContainer()
|
||||
if err := tc.fit.client.Create(tc.setup["directory"], tc.setup["name"], tc.setup["contents"], defaultPermissions); err != nil {
|
||||
t.Errorf("Error setting up: %v", err)
|
||||
}
|
||||
|
|
@ -327,19 +327,19 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSnapshotResourceDelete(t *testing.T) {
|
||||
func TestLocalSnapshotResourceDelete(t *testing.T) {
|
||||
t.Run("Delete function", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fit SnapshotResource
|
||||
fit LocalSnapshotResource
|
||||
have resource.DeleteRequest
|
||||
want resource.DeleteResponse
|
||||
}{
|
||||
{
|
||||
"Basic test",
|
||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
||||
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||
// have
|
||||
getSnapshotResourceDeleteRequest(t, map[string]string{
|
||||
getLocalSnapshotResourceDeleteRequest(t, map[string]string{
|
||||
"id": testId,
|
||||
"name": testName,
|
||||
"directory": defaultDirectory,
|
||||
|
|
@ -348,12 +348,12 @@ func TestSnapshotResourceDelete(t *testing.T) {
|
|||
"compress": defaultCompress,
|
||||
}),
|
||||
// want
|
||||
getSnapshotResourceDeleteResponse(),
|
||||
getLocalSnapshotResourceDeleteResponse(),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := getSnapshotResourceDeleteResponseContainer()
|
||||
r := getLocalSnapshotResourceDeleteResponseContainer()
|
||||
tc.fit.Delete(context.Background(), tc.have, &r)
|
||||
got := r
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
|
|
@ -366,7 +366,7 @@ func TestSnapshotResourceDelete(t *testing.T) {
|
|||
|
||||
// *** Test Helper Functions *** //
|
||||
// Create.
|
||||
func getSnapshotResourceCreateRequest(t *testing.T, data map[string]string) resource.CreateRequest {
|
||||
func getLocalSnapshotResourceCreateRequest(t *testing.T, data map[string]string) resource.CreateRequest {
|
||||
planMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -387,22 +387,22 @@ func getSnapshotResourceCreateRequest(t *testing.T, data map[string]string) reso
|
|||
}
|
||||
}
|
||||
}
|
||||
planValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), planMap)
|
||||
planValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), planMap)
|
||||
return resource.CreateRequest{
|
||||
Plan: tfsdk.Plan{
|
||||
Raw: planValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceCreateResponseContainer() resource.CreateResponse {
|
||||
func getLocalSnapshotResourceCreateResponseContainer() resource.CreateResponse {
|
||||
return resource.CreateResponse{
|
||||
State: tfsdk.State{Schema: getSnapshotResourceSchema().Schema},
|
||||
State: tfsdk.State{Schema: getLocalSnapshotResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceCreateResponse(t *testing.T, data map[string]string) resource.CreateResponse {
|
||||
func getLocalSnapshotResourceCreateResponse(t *testing.T, data map[string]string) resource.CreateResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -415,17 +415,17 @@ func getSnapshotResourceCreateResponse(t *testing.T, data map[string]string) res
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
return resource.CreateResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Read.
|
||||
func getSnapshotResourceReadRequest(t *testing.T, data map[string]string) resource.ReadRequest {
|
||||
func getLocalSnapshotResourceReadRequest(t *testing.T, data map[string]string) resource.ReadRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -438,22 +438,22 @@ func getSnapshotResourceReadRequest(t *testing.T, data map[string]string) resour
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
return resource.ReadRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceReadResponseContainer() resource.ReadResponse {
|
||||
func getLocalSnapshotResourceReadResponseContainer() resource.ReadResponse {
|
||||
return resource.ReadResponse{
|
||||
State: tfsdk.State{Schema: getSnapshotResourceSchema().Schema},
|
||||
State: tfsdk.State{Schema: getLocalSnapshotResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceReadResponse(t *testing.T, data map[string]string) resource.ReadResponse {
|
||||
func getLocalSnapshotResourceReadResponse(t *testing.T, data map[string]string) resource.ReadResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -466,17 +466,17 @@ func getSnapshotResourceReadResponse(t *testing.T, data map[string]string) resou
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
return resource.ReadResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Update.
|
||||
func getSnapshotResourceUpdateRequest(t *testing.T, data map[string]map[string]string) resource.UpdateRequest {
|
||||
func getLocalSnapshotResourceUpdateRequest(t *testing.T, data map[string]map[string]string) resource.UpdateRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data["priorState"] {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -489,7 +489,7 @@ func getSnapshotResourceUpdateRequest(t *testing.T, data map[string]map[string]s
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
priorStateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
priorStateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
|
||||
planMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data["plan"] {
|
||||
|
|
@ -511,27 +511,27 @@ func getSnapshotResourceUpdateRequest(t *testing.T, data map[string]map[string]s
|
|||
}
|
||||
}
|
||||
}
|
||||
planValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), planMap)
|
||||
planValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), planMap)
|
||||
|
||||
return resource.UpdateRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: priorStateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
Plan: tfsdk.Plan{
|
||||
Raw: planValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceUpdateResponseContainer() resource.UpdateResponse {
|
||||
func getLocalSnapshotResourceUpdateResponseContainer() resource.UpdateResponse {
|
||||
return resource.UpdateResponse{
|
||||
State: tfsdk.State{Schema: getSnapshotResourceSchema().Schema},
|
||||
State: tfsdk.State{Schema: getLocalSnapshotResourceSchema().Schema},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceUpdateResponse(t *testing.T, data map[string]string) resource.UpdateResponse {
|
||||
func getLocalSnapshotResourceUpdateResponse(t *testing.T, data map[string]string) resource.UpdateResponse {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -544,17 +544,17 @@ func getSnapshotResourceUpdateResponse(t *testing.T, data map[string]string) res
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
return resource.UpdateResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Delete.
|
||||
func getSnapshotResourceDeleteRequest(t *testing.T, data map[string]string) resource.DeleteRequest {
|
||||
func getLocalSnapshotResourceDeleteRequest(t *testing.T, data map[string]string) resource.DeleteRequest {
|
||||
stateMap := make(map[string]tftypes.Value)
|
||||
for key, value := range data {
|
||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
||||
|
|
@ -567,21 +567,21 @@ func getSnapshotResourceDeleteRequest(t *testing.T, data map[string]string) reso
|
|||
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||
}
|
||||
}
|
||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
||||
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||
return resource.DeleteRequest{
|
||||
State: tfsdk.State{
|
||||
Raw: stateValue,
|
||||
Schema: getSnapshotResourceSchema().Schema,
|
||||
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceDeleteResponseContainer() resource.DeleteResponse {
|
||||
func getLocalSnapshotResourceDeleteResponseContainer() resource.DeleteResponse {
|
||||
// A delete response does not need a schema as it results in a null state.
|
||||
return resource.DeleteResponse{}
|
||||
}
|
||||
|
||||
func getSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
||||
func getLocalSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
||||
return resource.DeleteResponse{
|
||||
State: tfsdk.State{
|
||||
Raw: tftypes.Value{},
|
||||
|
|
@ -591,7 +591,7 @@ func getSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
|||
}
|
||||
|
||||
// The helpers helpers.
|
||||
func getSnapshotResourceAttributeTypes() tftypes.Object {
|
||||
func getLocalSnapshotResourceAttributeTypes() tftypes.Object {
|
||||
return tftypes.Object{
|
||||
AttributeTypes: map[string]tftypes.Type{
|
||||
"id": tftypes.String,
|
||||
|
|
@ -604,8 +604,8 @@ func getSnapshotResourceAttributeTypes() tftypes.Object {
|
|||
}
|
||||
}
|
||||
|
||||
func getSnapshotResourceSchema() *resource.SchemaResponse {
|
||||
var testResource SnapshotResource
|
||||
func getLocalSnapshotResourceSchema() *resource.SchemaResponse {
|
||||
var testResource LocalSnapshotResource
|
||||
r := &resource.SchemaResponse{}
|
||||
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
||||
return r
|
||||
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/rancher/terraform-provider-file/internal/provider/file_local"
|
||||
"github.com/rancher/terraform-provider-file/internal/provider/file_snapshot"
|
||||
"github.com/rancher/terraform-provider-file/internal/provider/file_local_snapshot"
|
||||
"github.com/rancher/terraform-provider-file/internal/provider/file_local_directory"
|
||||
)
|
||||
|
||||
// The `var _` is a special Go construct that results in an unusable variable.
|
||||
|
|
@ -51,14 +52,16 @@ func (p *FileProvider) Configure(ctx context.Context, req provider.ConfigureRequ
|
|||
func (p *FileProvider) Resources(ctx context.Context) []func() resource.Resource {
|
||||
return []func() resource.Resource{
|
||||
file_local.NewLocalResource,
|
||||
file_snapshot.NewSnapshotResource,
|
||||
file_local_snapshot.NewLocalSnapshotResource,
|
||||
file_local_directory.NewLocalDirectoryResource,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FileProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
file_local.NewLocalDataSource,
|
||||
file_snapshot.NewSnapshotDataSource,
|
||||
file_local_snapshot.NewLocalSnapshotDataSource,
|
||||
file_local_directory.NewLocalDirectoryDataSource,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
util "github.com/rancher/terraform-provider-file/test"
|
||||
)
|
||||
|
||||
func TestLocalDirectoryAdvanced(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "local_directory_advanced"
|
||||
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)
|
||||
newDir := filepath.Join(repoRoot, "test", "data", id, "newDirectory", "subDirectory")
|
||||
|
||||
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{}{
|
||||
"path": newDir,
|
||||
},
|
||||
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)
|
||||
}
|
||||
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
directoryExists, err := util.CheckFileExists(filepath.Join(newDir))
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
t.Fatalf("Error checking file: %s", err)
|
||||
}
|
||||
if !directoryExists {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("Test failed...")
|
||||
} else {
|
||||
t.Log("Test passed...")
|
||||
}
|
||||
t.Log("Test complete, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
util "github.com/rancher/terraform-provider-file/test"
|
||||
)
|
||||
|
||||
func TestLocalDirectoryBasic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "local_directory_basic"
|
||||
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)
|
||||
newDir := filepath.Join(repoRoot, "test", "data", id, "newDirectory")
|
||||
|
||||
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{}{
|
||||
"path": newDir,
|
||||
},
|
||||
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)
|
||||
}
|
||||
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
directoryExists, err := util.CheckFileExists(filepath.Join(newDir))
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
t.Fatalf("Error checking file: %s", err)
|
||||
}
|
||||
if !directoryExists {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("Test failed...")
|
||||
} else {
|
||||
t.Log("Test passed...")
|
||||
}
|
||||
t.Log("Test complete, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
util "github.com/rancher/terraform-provider-file/test"
|
||||
)
|
||||
|
||||
func TestLocalDirectoryExample(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "file_local_directory"
|
||||
repoRoot, err := util.GetRepoRoot(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting git root directory: %v", err)
|
||||
}
|
||||
exampleDir := filepath.Join(repoRoot, "examples", "resources", 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{}{
|
||||
},
|
||||
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)
|
||||
}
|
||||
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("Test failed...")
|
||||
} else {
|
||||
t.Log("Test passed...")
|
||||
}
|
||||
t.Log("Test complete, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
util "github.com/rancher/terraform-provider-file/test"
|
||||
)
|
||||
|
||||
func TestLocalDirectorySubdirectory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "local_directory_basic"
|
||||
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)
|
||||
newDir := filepath.Join(repoRoot, "test", "data", id, "newDirectory", "newSubdirectory")
|
||||
|
||||
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{}{
|
||||
"path": newDir,
|
||||
},
|
||||
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)
|
||||
}
|
||||
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||
if err != nil {
|
||||
t.Log("Output failed, moving along...")
|
||||
}
|
||||
|
||||
directoryExists, err := util.CheckFileExists(newDir)
|
||||
if err != nil {
|
||||
t.Log("Test failed, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
t.Fatalf("Error checking file: %s", err)
|
||||
}
|
||||
if !directoryExists {
|
||||
t.Log("Directory doesn't exist")
|
||||
t.Fail()
|
||||
} else {
|
||||
t.Log("Directory exists")
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("Test failed...")
|
||||
} else {
|
||||
t.Log("Test passed...")
|
||||
}
|
||||
t.Log("Test complete, tearing down...")
|
||||
util.TearDown(t, testDir, terraformOptions)
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ func TestSnapshotBasic(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "snapshot_basic"
|
||||
directory := "local_snapshot_basic"
|
||||
repoRoot, err := util.GetRepoRoot(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting git root directory: %v", err)
|
||||
|
|
@ -13,7 +13,7 @@ func TestSnapshotExample(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "file_snapshot"
|
||||
directory := "file_local_snapshot"
|
||||
repoRoot, err := util.GetRepoRoot(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting git root directory: %v", err)
|
||||
|
|
@ -13,7 +13,7 @@ func TestSnapshotCompressed(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
id := util.GetId()
|
||||
directory := "snapshot_compressed"
|
||||
directory := "local_snapshot_compressed"
|
||||
repoRoot, err := util.GetRepoRoot(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting git root directory: %v", err)
|
||||
Loading…
Reference in New Issue