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; \
|
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
||||||
popd;
|
popd;
|
||||||
|
|
||||||
debug: build
|
et: build
|
||||||
export REPO_ROOT="../../../."; \
|
export REPO_ROOT="../../../."; \
|
||||||
export TF_LOG=DEBUG; \
|
|
||||||
pushd ./test; \
|
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;
|
popd;
|
||||||
|
|
||||||
.PHONY: fmt lint build install generate test testacc debug
|
.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"
|
name = "snapshot_resource_basic_example.txt"
|
||||||
contents = "this is an example file that is used to show how snapshots work"
|
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 = [
|
depends_on = [
|
||||||
file_local.snapshot_file_basic_example,
|
file_local.snapshot_file_basic_example,
|
||||||
]
|
]
|
||||||
|
|
@ -12,21 +12,21 @@ resource "file_snapshot" "basic_example" {
|
||||||
update_trigger = "an arbitrary string"
|
update_trigger = "an arbitrary string"
|
||||||
}
|
}
|
||||||
output "snapshot_basic" {
|
output "snapshot_basic" {
|
||||||
value = file_snapshot.basic_example.snapshot
|
value = file_local_snapshot.basic_example.snapshot
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
# A more advanced use case:
|
# A more advanced use case:
|
||||||
# We use a file_local resource to write a local file in the current directory
|
# 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 update the file using a terraform_data resource
|
||||||
# then we get the contents of the file using a file_local datasource
|
# 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" {
|
resource "file_local" "snapshot_file_example" {
|
||||||
name = "snapshot_resource_test.txt"
|
name = "snapshot_resource_test.txt"
|
||||||
contents = "this is an example file that is used to show how snapshots work"
|
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 = [
|
depends_on = [
|
||||||
file_local.snapshot_file_example,
|
file_local.snapshot_file_example,
|
||||||
]
|
]
|
||||||
|
|
@ -36,7 +36,7 @@ resource "file_snapshot" "file_example" {
|
||||||
resource "terraform_data" "update_file" {
|
resource "terraform_data" "update_file" {
|
||||||
depends_on = [
|
depends_on = [
|
||||||
file_local.snapshot_file_example,
|
file_local.snapshot_file_example,
|
||||||
file_snapshot.file_example,
|
file_local_snapshot.file_example,
|
||||||
]
|
]
|
||||||
provisioner "local-exec" {
|
provisioner "local-exec" {
|
||||||
command = <<-EOT
|
command = <<-EOT
|
||||||
|
|
@ -47,7 +47,7 @@ resource "terraform_data" "update_file" {
|
||||||
data "file_local" "snapshot_file_example_after_update" {
|
data "file_local" "snapshot_file_example_after_update" {
|
||||||
depends_on = [
|
depends_on = [
|
||||||
file_local.snapshot_file_example,
|
file_local.snapshot_file_example,
|
||||||
file_snapshot.file_example,
|
file_local_snapshot.file_example,
|
||||||
terraform_data.update_file,
|
terraform_data.update_file,
|
||||||
]
|
]
|
||||||
name = "snapshot_resource_test.txt"
|
name = "snapshot_resource_test.txt"
|
||||||
|
|
@ -59,7 +59,7 @@ output "file" {
|
||||||
# this updates a file that is used to show how snapshots work
|
# this updates a file that is used to show how snapshots work
|
||||||
}
|
}
|
||||||
output "snapshot" {
|
output "snapshot" {
|
||||||
value = base64decode(file_snapshot.file_example.snapshot)
|
value = base64decode(file_local_snapshot.file_example.snapshot)
|
||||||
sensitive = true
|
sensitive = true
|
||||||
# this is an example file that is used to show how snapshots work
|
# 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
|
directory = local.directory
|
||||||
contents = local.pesky_id
|
contents = local.pesky_id
|
||||||
}
|
}
|
||||||
resource "file_snapshot" "use_case_basic" {
|
resource "file_local_snapshot" "use_case_basic" {
|
||||||
depends_on = [
|
depends_on = [
|
||||||
file_local.snapshot_use_case_basic,
|
file_local.snapshot_use_case_basic,
|
||||||
]
|
]
|
||||||
|
|
@ -4,6 +4,6 @@ output "pesky_id" {
|
||||||
}
|
}
|
||||||
|
|
||||||
output "snapshot" {
|
output "snapshot" {
|
||||||
value = base64decode(file_snapshot.use_case_basic.snapshot)
|
value = base64decode(file_local_snapshot.use_case_basic.snapshot)
|
||||||
sensitive = true
|
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
|
# 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,
|
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.
|
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
|
directory = local.directory
|
||||||
contents = local.pesky_id
|
contents = local.pesky_id
|
||||||
}
|
}
|
||||||
resource "file_snapshot" "use_case_compressed" {
|
resource "file_local_snapshot" "use_case_compressed" {
|
||||||
depends_on = [
|
depends_on = [
|
||||||
file_local.snapshot_use_case_compressed,
|
file_local.snapshot_use_case_compressed,
|
||||||
]
|
]
|
||||||
|
|
@ -25,11 +25,11 @@ resource "file_snapshot" "use_case_compressed" {
|
||||||
update_trigger = local.update
|
update_trigger = local.update
|
||||||
compress = true
|
compress = true
|
||||||
}
|
}
|
||||||
data "file_snapshot" "use_case_compressed" {
|
data "file_local_snapshot" "use_case_compressed" {
|
||||||
depends_on = [
|
depends_on = [
|
||||||
file_local.snapshot_use_case_compressed,
|
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
|
decompress = true
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,6 @@ output "pesky_id" {
|
||||||
}
|
}
|
||||||
|
|
||||||
output "snapshot" {
|
output "snapshot" {
|
||||||
value = data.file_snapshot.use_case_compressed.data
|
value = data.file_local_snapshot.use_case_compressed.data
|
||||||
sensitive = true
|
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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1758262103,
|
"lastModified": 1759070547,
|
||||||
"narHash": "sha256-aBGl3XEOsjWw6W3AHiKibN7FeoG73dutQQEqnd/etR8=",
|
"narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "12bd230118a1901a4a5d393f9f56b6ad7e571d01",
|
"rev": "647e5c14cbd5067f44ac86b74f014962df460840",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"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,6 +6,7 @@ type FileClient interface {
|
||||||
Read(directory string, name string) (string, string, error) // permissions, contents, error
|
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
|
Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error
|
||||||
Delete(directory string, name 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
|
Encode(directory string, name string, encodedName string) error
|
||||||
Hash(directory string, name string) (string, error) // Sha256Hash, error
|
Hash(directory string, name string) (string, error) // Sha256Hash, error
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -19,29 +19,29 @@ import (
|
||||||
// The `var _` is a special Go construct that results in an unusable variable.
|
// 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.
|
// 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.
|
// These will fail at compilation time if the implementation is not satisfied.
|
||||||
var _ datasource.DataSource = &SnapshotDataSource{}
|
var _ datasource.DataSource = &LocalSnapshotDataSource{}
|
||||||
|
|
||||||
func NewSnapshotDataSource() datasource.DataSource {
|
func NewLocalSnapshotDataSource() datasource.DataSource {
|
||||||
return &SnapshotDataSource{}
|
return &LocalSnapshotDataSource{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnapshotDataSource struct{}
|
type LocalSnapshotDataSource struct{}
|
||||||
|
|
||||||
type SnapshotDataSourceModel struct {
|
type LocalSnapshotDataSourceModel struct {
|
||||||
Id types.String `tfsdk:"id"`
|
Id types.String `tfsdk:"id"`
|
||||||
Contents types.String `tfsdk:"contents"`
|
Contents types.String `tfsdk:"contents"`
|
||||||
Data types.String `tfsdk:"data"`
|
Data types.String `tfsdk:"data"`
|
||||||
Decompress types.Bool `tfsdk:"decompress"`
|
Decompress types.Bool `tfsdk:"decompress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SnapshotDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
func (r *LocalSnapshotDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||||
resp.TypeName = req.ProviderTypeName + "_snapshot" // file_snapshot
|
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{
|
resp.Schema = schema.Schema{
|
||||||
MarkdownDescription: "File Snapshot data source. \n" +
|
MarkdownDescription: "File LocalSnapshot data source. \n" +
|
||||||
"This data source retrieves the contents of a file from the output of a file_snapshot datasource." +
|
"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.",
|
"Warning! Using this resource places the plain text contents of the snapshot in your state file.",
|
||||||
|
|
||||||
Attributes: map[string]schema.Attribute{
|
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.
|
// 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.
|
// 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.
|
// 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))
|
tflog.Debug(ctx, fmt.Sprintf("Read Request Object: %+v", req))
|
||||||
|
|
||||||
var config SnapshotDataSourceModel
|
var config LocalSnapshotDataSourceModel
|
||||||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package file_snapshot
|
package file_local_snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -30,14 +30,14 @@ const (
|
||||||
|
|
||||||
var snapshotDataSourceBooleanFields = []string{"decompress"}
|
var snapshotDataSourceBooleanFields = []string{"decompress"}
|
||||||
|
|
||||||
func TestSnapshotDataSourceMetadata(t *testing.T) {
|
func TestLocalSnapshotDataSourceMetadata(t *testing.T) {
|
||||||
t.Run("Metadata function", func(t *testing.T) {
|
t.Run("Metadata function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotDataSource
|
fit LocalSnapshotDataSource
|
||||||
want datasource.MetadataResponse
|
want datasource.MetadataResponse
|
||||||
}{
|
}{
|
||||||
{"Basic test", SnapshotDataSource{}, datasource.MetadataResponse{TypeName: "file_snapshot"}},
|
{"Basic test", LocalSnapshotDataSource{}, datasource.MetadataResponse{TypeName: "file_local_snapshot"}},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
t.Run("Schema function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotDataSource
|
fit LocalSnapshotDataSource
|
||||||
want datasource.SchemaResponse
|
want datasource.SchemaResponse
|
||||||
}{
|
}{
|
||||||
{"Basic test", SnapshotDataSource{}, *getSnapshotDataSourceSchema()},
|
{"Basic test", LocalSnapshotDataSource{}, *getLocalSnapshotDataSourceSchema()},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
t.Run("Read function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotDataSource
|
fit LocalSnapshotDataSource
|
||||||
have datasource.ReadRequest
|
have datasource.ReadRequest
|
||||||
want datasource.ReadResponse
|
want datasource.ReadResponse
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"Basic",
|
"Basic",
|
||||||
SnapshotDataSource{},
|
LocalSnapshotDataSource{},
|
||||||
// have
|
// have
|
||||||
getSnapshotDataSourceReadRequest(t, map[string]string{
|
getLocalSnapshotDataSourceReadRequest(t, map[string]string{
|
||||||
"id": "", // id is computed.
|
"id": "", // id is computed.
|
||||||
"data": "", // data is computed.
|
"data": "", // data is computed.
|
||||||
"contents": testDataEncoded,
|
"contents": testDataEncoded,
|
||||||
"decompress": defaultDecompress,
|
"decompress": defaultDecompress,
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotDataSourceReadResponse(t, map[string]string{
|
getLocalSnapshotDataSourceReadResponse(t, map[string]string{
|
||||||
"id": testDataEncodedId,
|
"id": testDataEncodedId,
|
||||||
"data": testDataContents,
|
"data": testDataContents,
|
||||||
"contents": testDataEncoded,
|
"contents": testDataEncoded,
|
||||||
|
|
@ -102,16 +102,16 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Compressed",
|
"Compressed",
|
||||||
SnapshotDataSource{},
|
LocalSnapshotDataSource{},
|
||||||
// have
|
// have
|
||||||
getSnapshotDataSourceReadRequest(t, map[string]string{
|
getLocalSnapshotDataSourceReadRequest(t, map[string]string{
|
||||||
"id": "", // id is computed.
|
"id": "", // id is computed.
|
||||||
"data": "", // data is computed.
|
"data": "", // data is computed.
|
||||||
"contents": testDataCompressed,
|
"contents": testDataCompressed,
|
||||||
"decompress": "true",
|
"decompress": "true",
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotDataSourceReadResponse(t, map[string]string{
|
getLocalSnapshotDataSourceReadResponse(t, map[string]string{
|
||||||
"id": testDataCompressedId,
|
"id": testDataCompressedId,
|
||||||
"data": testDataContents,
|
"data": testDataContents,
|
||||||
"contents": testDataCompressed,
|
"contents": testDataCompressed,
|
||||||
|
|
@ -121,7 +121,7 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := getSnapshotDataSourceReadResponseContainer()
|
r := getLocalSnapshotDataSourceReadResponseContainer()
|
||||||
tc.fit.Read(context.Background(), tc.have, &r)
|
tc.fit.Read(context.Background(), tc.have, &r)
|
||||||
got := r
|
got := r
|
||||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||||
|
|
@ -135,7 +135,7 @@ func TestSnapshotDataSourceRead(t *testing.T) {
|
||||||
// *** Test Helper Functions *** //
|
// *** Test Helper Functions *** //
|
||||||
|
|
||||||
// Read.
|
// 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotDataSourceBooleanFields, key) { // snapshotDataSourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotDataSourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotDataSourceAttributeTypes(), stateMap)
|
||||||
return datasource.ReadRequest{
|
return datasource.ReadRequest{
|
||||||
Config: tfsdk.Config{
|
Config: tfsdk.Config{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotDataSourceSchema().Schema,
|
Schema: getLocalSnapshotDataSourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotDataSourceReadResponseContainer() datasource.ReadResponse {
|
func getLocalSnapshotDataSourceReadResponseContainer() datasource.ReadResponse {
|
||||||
return 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotDataSourceBooleanFields, key) { // snapshotDataSourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotDataSourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotDataSourceAttributeTypes(), stateMap)
|
||||||
return datasource.ReadResponse{
|
return datasource.ReadResponse{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotDataSourceSchema().Schema,
|
Schema: getLocalSnapshotDataSourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The helpers helpers.
|
// The helpers helpers.
|
||||||
func getSnapshotDataSourceAttributeTypes() tftypes.Object {
|
func getLocalSnapshotDataSourceAttributeTypes() tftypes.Object {
|
||||||
return tftypes.Object{
|
return tftypes.Object{
|
||||||
AttributeTypes: map[string]tftypes.Type{
|
AttributeTypes: map[string]tftypes.Type{
|
||||||
"id": tftypes.String,
|
"id": tftypes.String,
|
||||||
|
|
@ -197,8 +197,8 @@ func getSnapshotDataSourceAttributeTypes() tftypes.Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotDataSourceSchema() *datasource.SchemaResponse {
|
func getLocalSnapshotDataSourceSchema() *datasource.SchemaResponse {
|
||||||
var testDataSource SnapshotDataSource
|
var testDataSource LocalSnapshotDataSource
|
||||||
r := &datasource.SchemaResponse{}
|
r := &datasource.SchemaResponse{}
|
||||||
testDataSource.Schema(context.Background(), datasource.SchemaRequest{}, r)
|
testDataSource.Schema(context.Background(), datasource.SchemaRequest{}, r)
|
||||||
return r
|
return r
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package file_snapshot
|
package file_local_snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -20,34 +20,34 @@ import (
|
||||||
// The `var _` is a special Go construct that results in an unusable variable.
|
// 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.
|
// 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.
|
// These will fail at compilation time if the implementation is not satisfied.
|
||||||
var _ resource.Resource = &SnapshotResource{}
|
var _ resource.Resource = &LocalSnapshotResource{}
|
||||||
var _ resource.ResourceWithImportState = &SnapshotResource{}
|
var _ resource.ResourceWithImportState = &LocalSnapshotResource{}
|
||||||
|
|
||||||
func NewSnapshotResource() resource.Resource {
|
func NewLocalSnapshotResource() resource.Resource {
|
||||||
return &SnapshotResource{}
|
return &LocalSnapshotResource{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnapshotResource struct {
|
type LocalSnapshotResource struct {
|
||||||
client c.FileClient
|
client c.FileClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnapshotResourceModel describes the resource data model.
|
// LocalSnapshotResourceModel describes the resource data model.
|
||||||
type SnapshotResourceModel struct {
|
type LocalSnapshotResourceModel struct {
|
||||||
Id types.String `tfsdk:"id"`
|
Id types.String `tfsdk:"id"`
|
||||||
Name types.String `tfsdk:"name"`
|
Name types.String `tfsdk:"name"`
|
||||||
Directory types.String `tfsdk:"directory"`
|
Directory types.String `tfsdk:"directory"`
|
||||||
Snapshot types.String `tfsdk:"snapshot"`
|
LocalSnapshot types.String `tfsdk:"snapshot"`
|
||||||
UpdateTrigger types.String `tfsdk:"update_trigger"`
|
UpdateTrigger types.String `tfsdk:"update_trigger"`
|
||||||
Compress types.Bool `tfsdk:"compress"`
|
Compress types.Bool `tfsdk:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SnapshotResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
func (r *LocalSnapshotResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
resp.TypeName = req.ProviderTypeName + "_snapshot" // file_snapshot
|
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{
|
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. " +
|
"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 refresh phase doesn't update state, instead " +
|
||||||
"the state can only change on create or update and only when the update_trigger argument changes.",
|
"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.
|
// 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.
|
// 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.
|
// 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)
|
// - 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)
|
// - 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))
|
tflog.Debug(ctx, fmt.Sprintf("Create Request Object: %+v", req))
|
||||||
|
|
||||||
if r.client == nil {
|
if r.client == nil {
|
||||||
|
|
@ -123,7 +123,7 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
|
||||||
r.client = &c.OsFileClient{}
|
r.client = &c.OsFileClient{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var plan SnapshotResourceModel
|
var plan LocalSnapshotResourceModel
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
|
|
@ -152,7 +152,7 @@ func (r *SnapshotResource) Create(ctx context.Context, req resource.CreateReques
|
||||||
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
plan.Snapshot = types.StringValue(encodedContents)
|
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||||
|
|
||||||
hash, err := r.client.Hash(pDir, pName)
|
hash, err := r.client.Hash(pDir, pName)
|
||||||
if err != nil {
|
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))
|
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))
|
tflog.Debug(ctx, fmt.Sprintf("Read Request Object: %+v", req))
|
||||||
|
|
||||||
var state SnapshotResourceModel
|
var state LocalSnapshotResourceModel
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
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.
|
// a difference between the plan and the state has been found.
|
||||||
// we want to update reality and state to match the plan.
|
// we want to update reality and state to match the plan.
|
||||||
// our snapshot will only update if the update trigger has changed.
|
// 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))
|
tflog.Debug(ctx, fmt.Sprintf("Update Request Object: %+v", req))
|
||||||
|
|
||||||
if r.client == nil {
|
if r.client == nil {
|
||||||
|
|
@ -206,7 +206,7 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
||||||
r.client = &c.OsFileClient{}
|
r.client = &c.OsFileClient{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var plan SnapshotResourceModel
|
var plan LocalSnapshotResourceModel
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
|
|
@ -216,14 +216,14 @@ func (r *SnapshotResource) Update(ctx context.Context, req resource.UpdateReques
|
||||||
pDir := plan.Directory.ValueString()
|
pDir := plan.Directory.ValueString()
|
||||||
pUpdateTrigger := plan.UpdateTrigger.ValueString()
|
pUpdateTrigger := plan.UpdateTrigger.ValueString()
|
||||||
|
|
||||||
var state SnapshotResourceModel
|
var state LocalSnapshotResourceModel
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sUpdateTrigger := state.UpdateTrigger.ValueString()
|
sUpdateTrigger := state.UpdateTrigger.ValueString()
|
||||||
sSnapshot := state.Snapshot.ValueString()
|
sLocalSnapshot := state.LocalSnapshot.ValueString()
|
||||||
sCompress := state.Compress.ValueBool()
|
sCompress := state.Compress.ValueBool()
|
||||||
sId := state.Id.ValueString()
|
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())
|
resp.Diagnostics.AddError("Error reading encoded file: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
plan.Snapshot = types.StringValue(encodedContents)
|
plan.LocalSnapshot = types.StringValue(encodedContents)
|
||||||
} else {
|
} else {
|
||||||
tflog.Debug(ctx, fmt.Sprintf("Update trigger hasn't changed, keeping previous snapshot (%s).", sSnapshot))
|
tflog.Debug(ctx, fmt.Sprintf("Update trigger hasn't changed, keeping previous snapshot (%s).", sLocalSnapshot))
|
||||||
plan.Snapshot = types.StringValue(sSnapshot)
|
plan.LocalSnapshot = types.StringValue(sLocalSnapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
tflog.Debug(ctx, fmt.Sprintf("Setting state to this plan: \n%+v", &plan))
|
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))
|
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))
|
tflog.Debug(ctx, fmt.Sprintf("Delete Request Object: %+v", req))
|
||||||
|
|
||||||
// delete is a no-op
|
// 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))
|
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)
|
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package file_snapshot
|
package file_local_snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -34,14 +34,14 @@ const (
|
||||||
|
|
||||||
var snapshotResourceBooleanFields = []string{"compress"}
|
var snapshotResourceBooleanFields = []string{"compress"}
|
||||||
|
|
||||||
func TestSnapshotResourceMetadata(t *testing.T) {
|
func TestLocalSnapshotResourceMetadata(t *testing.T) {
|
||||||
t.Run("Metadata function", func(t *testing.T) {
|
t.Run("Metadata function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
want resource.MetadataResponse
|
want resource.MetadataResponse
|
||||||
}{
|
}{
|
||||||
{"Basic test", SnapshotResource{}, resource.MetadataResponse{TypeName: "file_snapshot"}},
|
{"Basic test", LocalSnapshotResource{}, resource.MetadataResponse{TypeName: "file_local_snapshot"}},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
t.Run("Schema function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
want resource.SchemaResponse
|
want resource.SchemaResponse
|
||||||
}{
|
}{
|
||||||
{"Basic test", SnapshotResource{}, *getSnapshotResourceSchema()},
|
{"Basic test", LocalSnapshotResource{}, *getLocalSnapshotResourceSchema()},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
t.Run("Create function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
have resource.CreateRequest
|
have resource.CreateRequest
|
||||||
want resource.CreateResponse
|
want resource.CreateResponse
|
||||||
setup map[string]string
|
setup map[string]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"Basic",
|
"Basic",
|
||||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceCreateRequest(t, map[string]string{
|
getLocalSnapshotResourceCreateRequest(t, map[string]string{
|
||||||
"id": "",
|
"id": "",
|
||||||
"snapshot": "",
|
"snapshot": "",
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -100,7 +100,7 @@ func TestSnapshotResourceCreate(t *testing.T) {
|
||||||
"compress": defaultCompress,
|
"compress": defaultCompress,
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceCreateResponse(t, map[string]string{
|
getLocalSnapshotResourceCreateResponse(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -118,7 +118,7 @@ func TestSnapshotResourceCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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 {
|
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)
|
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) {
|
t.Run("Read function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
have resource.ReadRequest
|
have resource.ReadRequest
|
||||||
want resource.ReadResponse
|
want resource.ReadResponse
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"Basic",
|
"Basic",
|
||||||
SnapshotResource{},
|
LocalSnapshotResource{},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceReadRequest(t, map[string]string{
|
getLocalSnapshotResourceReadRequest(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -158,7 +158,7 @@ func TestSnapshotResourceRead(t *testing.T) {
|
||||||
"compress": defaultCompress,
|
"compress": defaultCompress,
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceReadResponse(t, map[string]string{
|
getLocalSnapshotResourceReadResponse(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -170,7 +170,7 @@ func TestSnapshotResourceRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := getSnapshotResourceReadResponseContainer()
|
r := getLocalSnapshotResourceReadResponseContainer()
|
||||||
tc.fit.Read(context.Background(), tc.have, &r)
|
tc.fit.Read(context.Background(), tc.have, &r)
|
||||||
got := r
|
got := r
|
||||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
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) {
|
t.Run("Update function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
have resource.UpdateRequest
|
have resource.UpdateRequest
|
||||||
want resource.UpdateResponse
|
want resource.UpdateResponse
|
||||||
setup map[string]string
|
setup map[string]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"Basic",
|
"Basic",
|
||||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||||
"priorState": {
|
"priorState": {
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
|
|
@ -213,7 +213,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -230,9 +230,9 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Updates when trigger changes",
|
"Updates when trigger changes",
|
||||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||||
"priorState": {
|
"priorState": {
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
|
|
@ -251,7 +251,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||||
"id": testId, // id shouldn't change
|
"id": testId, // id shouldn't change
|
||||||
"snapshot": "dGhlc2UgY29udGVudHMgYXJlIHVwZGF0ZWQgZm9yIHRlc3Rpbmc=", // echo -n "these contents are updated for testing" | base64 -w 0 #.
|
"snapshot": "dGhlc2UgY29udGVudHMgYXJlIHVwZGF0ZWQgZm9yIHRlc3Rpbmc=", // echo -n "these contents are updated for testing" | base64 -w 0 #.
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -268,9 +268,9 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Doesn't update when trigger stays the same",
|
"Doesn't update when trigger stays the same",
|
||||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
getLocalSnapshotResourceUpdateRequest(t, map[string]map[string]string{
|
||||||
"priorState": {
|
"priorState": {
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
|
|
@ -289,7 +289,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceUpdateResponse(t, map[string]string{
|
getLocalSnapshotResourceUpdateResponse(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"snapshot": testEncoded,
|
"snapshot": testEncoded,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
|
|
@ -307,7 +307,7 @@ func TestSnapshotResourceUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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 {
|
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)
|
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) {
|
t.Run("Delete function", func(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
fit SnapshotResource
|
fit LocalSnapshotResource
|
||||||
have resource.DeleteRequest
|
have resource.DeleteRequest
|
||||||
want resource.DeleteResponse
|
want resource.DeleteResponse
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"Basic test",
|
"Basic test",
|
||||||
SnapshotResource{client: &c.MemoryFileClient{}},
|
LocalSnapshotResource{client: &c.MemoryFileClient{}},
|
||||||
// have
|
// have
|
||||||
getSnapshotResourceDeleteRequest(t, map[string]string{
|
getLocalSnapshotResourceDeleteRequest(t, map[string]string{
|
||||||
"id": testId,
|
"id": testId,
|
||||||
"name": testName,
|
"name": testName,
|
||||||
"directory": defaultDirectory,
|
"directory": defaultDirectory,
|
||||||
|
|
@ -348,12 +348,12 @@ func TestSnapshotResourceDelete(t *testing.T) {
|
||||||
"compress": defaultCompress,
|
"compress": defaultCompress,
|
||||||
}),
|
}),
|
||||||
// want
|
// want
|
||||||
getSnapshotResourceDeleteResponse(),
|
getLocalSnapshotResourceDeleteResponse(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := getSnapshotResourceDeleteResponseContainer()
|
r := getLocalSnapshotResourceDeleteResponseContainer()
|
||||||
tc.fit.Delete(context.Background(), tc.have, &r)
|
tc.fit.Delete(context.Background(), tc.have, &r)
|
||||||
got := r
|
got := r
|
||||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||||
|
|
@ -366,7 +366,7 @@ func TestSnapshotResourceDelete(t *testing.T) {
|
||||||
|
|
||||||
// *** Test Helper Functions *** //
|
// *** Test Helper Functions *** //
|
||||||
// Create.
|
// 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)
|
planMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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{
|
return resource.CreateRequest{
|
||||||
Plan: tfsdk.Plan{
|
Plan: tfsdk.Plan{
|
||||||
Raw: planValue,
|
Raw: planValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotResourceCreateResponseContainer() resource.CreateResponse {
|
func getLocalSnapshotResourceCreateResponseContainer() resource.CreateResponse {
|
||||||
return 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
return resource.CreateResponse{
|
return resource.CreateResponse{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read.
|
// 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
return resource.ReadRequest{
|
return resource.ReadRequest{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotResourceReadResponseContainer() resource.ReadResponse {
|
func getLocalSnapshotResourceReadResponseContainer() resource.ReadResponse {
|
||||||
return 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
return resource.ReadResponse{
|
return resource.ReadResponse{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update.
|
// 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data["priorState"] {
|
for key, value := range data["priorState"] {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
priorStateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
priorStateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
|
|
||||||
planMap := make(map[string]tftypes.Value)
|
planMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data["plan"] {
|
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{
|
return resource.UpdateRequest{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: priorStateValue,
|
Raw: priorStateValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
Plan: tfsdk.Plan{
|
Plan: tfsdk.Plan{
|
||||||
Raw: planValue,
|
Raw: planValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotResourceUpdateResponseContainer() resource.UpdateResponse {
|
func getLocalSnapshotResourceUpdateResponseContainer() resource.UpdateResponse {
|
||||||
return 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
return resource.UpdateResponse{
|
return resource.UpdateResponse{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
Raw: stateValue,
|
||||||
Schema: getSnapshotResourceSchema().Schema,
|
Schema: getLocalSnapshotResourceSchema().Schema,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete.
|
// 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)
|
stateMap := make(map[string]tftypes.Value)
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if slices.Contains(snapshotResourceBooleanFields, key) { // snapshotResourceBooleanFields is a constant
|
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)
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateValue := tftypes.NewValue(getSnapshotResourceAttributeTypes(), stateMap)
|
stateValue := tftypes.NewValue(getLocalSnapshotResourceAttributeTypes(), stateMap)
|
||||||
return resource.DeleteRequest{
|
return resource.DeleteRequest{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: stateValue,
|
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.
|
// A delete response does not need a schema as it results in a null state.
|
||||||
return resource.DeleteResponse{}
|
return resource.DeleteResponse{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
func getLocalSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
||||||
return resource.DeleteResponse{
|
return resource.DeleteResponse{
|
||||||
State: tfsdk.State{
|
State: tfsdk.State{
|
||||||
Raw: tftypes.Value{},
|
Raw: tftypes.Value{},
|
||||||
|
|
@ -591,7 +591,7 @@ func getSnapshotResourceDeleteResponse() resource.DeleteResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The helpers helpers.
|
// The helpers helpers.
|
||||||
func getSnapshotResourceAttributeTypes() tftypes.Object {
|
func getLocalSnapshotResourceAttributeTypes() tftypes.Object {
|
||||||
return tftypes.Object{
|
return tftypes.Object{
|
||||||
AttributeTypes: map[string]tftypes.Type{
|
AttributeTypes: map[string]tftypes.Type{
|
||||||
"id": tftypes.String,
|
"id": tftypes.String,
|
||||||
|
|
@ -604,8 +604,8 @@ func getSnapshotResourceAttributeTypes() tftypes.Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshotResourceSchema() *resource.SchemaResponse {
|
func getLocalSnapshotResourceSchema() *resource.SchemaResponse {
|
||||||
var testResource SnapshotResource
|
var testResource LocalSnapshotResource
|
||||||
r := &resource.SchemaResponse{}
|
r := &resource.SchemaResponse{}
|
||||||
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
||||||
return r
|
return r
|
||||||
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"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_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.
|
// 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 {
|
func (p *FileProvider) Resources(ctx context.Context) []func() resource.Resource {
|
||||||
return []func() resource.Resource{
|
return []func() resource.Resource{
|
||||||
file_local.NewLocalResource,
|
file_local.NewLocalResource,
|
||||||
file_snapshot.NewSnapshotResource,
|
file_local_snapshot.NewLocalSnapshotResource,
|
||||||
|
file_local_directory.NewLocalDirectoryResource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FileProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
func (p *FileProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
||||||
return []func() datasource.DataSource{
|
return []func() datasource.DataSource{
|
||||||
file_local.NewLocalDataSource,
|
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()
|
t.Parallel()
|
||||||
|
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "snapshot_basic"
|
directory := "local_snapshot_basic"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
|
|
@ -13,7 +13,7 @@ func TestSnapshotExample(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "file_snapshot"
|
directory := "file_local_snapshot"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
|
|
@ -13,7 +13,7 @@ func TestSnapshotCompressed(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "snapshot_compressed"
|
directory := "local_snapshot_compressed"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
Loading…
Reference in New Issue