diff --git a/genesyscloud/architect_grammar/resource_genesyscloud_architect_grammar.go b/genesyscloud/architect_grammar/resource_genesyscloud_architect_grammar.go index cad2acdfb..b5c43a684 100644 --- a/genesyscloud/architect_grammar/resource_genesyscloud_architect_grammar.go +++ b/genesyscloud/architect_grammar/resource_genesyscloud_architect_grammar.go @@ -139,9 +139,9 @@ func GenerateGrammarResource( description string, ) string { return fmt.Sprintf(` - resource "genesyscloud_architect_grammar" "%s" { + resource "%s" "%s" { name = "%s" description = "%s" } - `, resourceLabel, name, description) + `, ResourceType, resourceLabel, name, description) } diff --git a/genesyscloud/provider/provider.go b/genesyscloud/provider/provider.go index e37c8d7b5..43ab0723f 100644 --- a/genesyscloud/provider/provider.go +++ b/genesyscloud/provider/provider.go @@ -121,11 +121,11 @@ func New(version string, providerResources map[string]*schema.Resource, provider Description: "If set to true the provider will log stack traces to a file instead of crashing, where possible. Can be set with the `GENESYSCLOUD_LOG_STACK_TRACES` environment variable.", }, "log_stack_traces_file_path": { - Type: schema.TypeString, - Optional: true, - Description: "Specifies the file path for the stack trace logs. Can be set with the `GENESYSCLOUD_LOG_STACK_TRACES_FILE_PATH` environment variable. Default value is genesyscloud_stack_traces.log", - DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_LOG_STACK_TRACES_FILE_PATH", "genesyscloud_stack_traces.log"), - ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`^(|\s+)$`), "Invalid File path "), + Type: schema.TypeString, + Optional: true, + Description: "Specifies the file path for the stack trace logs. Can be set with the `GENESYSCLOUD_LOG_STACK_TRACES_FILE_PATH` environment variable. Default value is genesyscloud_stack_traces.log", + DefaultFunc: schema.EnvDefaultFunc("GENESYSCLOUD_LOG_STACK_TRACES_FILE_PATH", "genesyscloud_stack_traces.log"), + ValidateDiagFunc: validateLogFilePath, }, "gateway": { Type: schema.TypeSet, diff --git a/genesyscloud/provider/provider_utils.go b/genesyscloud/provider/provider_utils.go index fce56b814..3865e4f0c 100644 --- a/genesyscloud/provider/provider_utils.go +++ b/genesyscloud/provider/provider_utils.go @@ -2,6 +2,9 @@ package provider import ( "fmt" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -44,6 +47,34 @@ func TestDefaultHomeDivision(resource string) resource.TestCheckFunc { } } +// validateLogFilePath validates that a log file path is not empty, does +// not contain any whitespaces, and that it ends with ".log" +// (Keeping this inside validators causes import cycle) +func validateLogFilePath(filepath any, _ cty.Path) (err diag.Diagnostics) { + defer func() { + if err != nil { + err = diag.Errorf("validateLogFilePath failed: %v", err) + } + }() + + val, ok := filepath.(string) + if !ok { + return diag.Errorf("expected type of %v to be string, got %T", filepath, filepath) + } + + // Check if the string is empty or contains any whitespace + if val == "" || strings.ContainsAny(val, " \t\n\r") { + return diag.Errorf("filepath must not be empty or contain whitespace, got: %s", val) + } + + // Check if the file ends with .log + if !strings.HasSuffix(val, ".log") { + return diag.Errorf("%s must end with .log extension", val) + } + + return err +} + func GetOrgDefaultCountryCode() string { return orgDefaultCountryCode } diff --git a/genesyscloud/provider/provider_utils_test.go b/genesyscloud/provider/provider_utils_test.go new file mode 100644 index 000000000..99d82dc88 --- /dev/null +++ b/genesyscloud/provider/provider_utils_test.go @@ -0,0 +1,66 @@ +package provider + +import ( + "testing" +) + +func TestUnitValidateLogFilePath(t *testing.T) { + testCases := []struct { + name string + path interface{} + expectError bool + }{ + { + name: "Valid log file path", + path: "logs/application.log", + expectError: false, + }, + { + name: "Empty path", + path: "", + expectError: true, + }, + { + name: "Non-string value", + path: 123, + expectError: true, + }, + { + name: "Relative path with directory", + path: "./logs/currentTestCase.log", + expectError: false, + }, + { + name: "Absolute path", + path: "/var/logs/currentTestCase.log", + expectError: false, + }, + { + name: "Path with spaces", + path: "logs/current TestCase.log", + expectError: true, + }, + { + name: "Incorrect file extension", + path: "terraform.tfstate", + expectError: true, + }, + { + name: "Incorrect file extension", + path: "main.go", + expectError: true, + }, + } + + for _, currentTestCase := range testCases { + t.Run(currentTestCase.name, func(t *testing.T) { + diagErr := validateLogFilePath(currentTestCase.path, nil) + if currentTestCase.expectError && diagErr == nil { + t.Fatalf("Expected an error, but got none") + } + if !currentTestCase.expectError && diagErr != nil { + t.Fatalf("Unexpected error: %v", diagErr) + } + }) + } +} diff --git a/genesyscloud/util/panic_recovery_logger/panic_recovery_logger.go b/genesyscloud/util/panic_recovery_logger/panic_recovery_logger.go index 3c124d50b..5b9b9389a 100644 --- a/genesyscloud/util/panic_recovery_logger/panic_recovery_logger.go +++ b/genesyscloud/util/panic_recovery_logger/panic_recovery_logger.go @@ -2,6 +2,7 @@ package panic_recovery_logger import ( "fmt" + "log" "os" "runtime/debug" ) @@ -14,6 +15,9 @@ type PanicRecoveryLogger struct { var panicRecoverLogger *PanicRecoveryLogger func InitPanicRecoveryLoggerInstance(enabled bool, filepath string) { + if err := deleteFileIfExists(filepath); err != nil { + log.Println(err) + } panicRecoverLogger = &PanicRecoveryLogger{ LoggerEnabled: enabled, filePath: filepath, @@ -61,3 +65,31 @@ func appendToFile(filename string, data []byte) (err error) { _ = file.Close() return err } + +// deleteFileIfExists deletes file at filepath if it exists and does nothing if it doesn't +func deleteFileIfExists(filepath string) (err error) { + defer func() { + if err != nil { + err = fmt.Errorf("deleteFileIfExists failed: %w (%s may contain stack traces from the previous deployment)", err, filepath) + } + }() + + // Check if file exists + _, err = os.Stat(filepath) + if err != nil { + if os.IsNotExist(err) { + // File doesn't exist, return nil as this is not an error condition + return nil + } + // Return other errors (permission issues, etc.) + return fmt.Errorf("failed to check file existence: %w", err) + } + + // File exists, try to remove it + err = os.Remove(filepath) + if err != nil { + return fmt.Errorf("failed to delete file %s: %w", filepath, err) + } + + return err +} diff --git a/genesyscloud/util/util_retries.go b/genesyscloud/util/util_retries.go index a91e3ab96..31334c554 100644 --- a/genesyscloud/util/util_retries.go +++ b/genesyscloud/util/util_retries.go @@ -20,6 +20,7 @@ import ( ) func WithRetries(ctx context.Context, timeout time.Duration, method func() *retry.RetryError) diag.Diagnostics { + method = wrapReadMethodWithRecover(method) err := diag.FromErr(retry.RetryContext(ctx, timeout, method)) if err != nil && strings.Contains(fmt.Sprintf("%v", err), "timeout while waiting for state to become") { ctx, cancel := context.WithTimeout(context.Background(), timeout) diff --git a/genesyscloud/validators/validators.go b/genesyscloud/validators/validators.go index 569b6ca7e..e5fedd4c4 100644 --- a/genesyscloud/validators/validators.go +++ b/genesyscloud/validators/validators.go @@ -11,8 +11,8 @@ import ( "terraform-provider-genesyscloud/genesyscloud/util" "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" - files "terraform-provider-genesyscloud/genesyscloud/util/files" - lists "terraform-provider-genesyscloud/genesyscloud/util/lists" + "terraform-provider-genesyscloud/genesyscloud/util/files" + "terraform-provider-genesyscloud/genesyscloud/util/lists" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag"