From 06247f4af716006e561237a70028871d251c1393 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Wed, 23 Oct 2024 02:09:02 +0530 Subject: [PATCH 01/26] Added Error and Output Secret Handling. --- harness/variables.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 harness/variables.go diff --git a/harness/variables.go b/harness/variables.go new file mode 100644 index 0000000..8563538 --- /dev/null +++ b/harness/variables.go @@ -0,0 +1,66 @@ +package harness + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +const ( + PluginErrorMessageKey = "PLUGIN_ERROR_MESSAGE" +) + +// SetSecret sets a new secret by adding it to the output +func SetSecret(name, value string) error { + return WriteEnvToFile(name, value) +} + +// UpdateSecret updates an existing secret with a new value +func UpdateSecret(name, value string) error { + return WriteEnvToFile(name, value) +} + +// DeleteSecret removes a secret from the output +func DeleteSecret(name string) error { + return WriteEnvToFile(name, "") +} + +// SetError sets the error message and writes it to the DRONE_OUTPUT file +func SetError(message string) error { + return WriteEnvToFile(PluginErrorMessageKey, message) +} + +// WriteEnvToFile writes a key-value pair to the DRONE_OUTPUT file +func WriteEnvToFile(key, value string) error { + outputFilePath := os.Getenv("DRONE_OUTPUT") + + // Check the extension of the output file (.env or .out) + ext := strings.ToLower(filepath.Ext(outputFilePath)) + + if ext == ".env" { + // Write in .env format (KEY=VALUE) + return writeToFile(outputFilePath, fmt.Sprintf("%s=%s\n", key, value)) + } else if ext == ".out" { + // Write in .out format (export KEY="VALUE") + return writeToFile(outputFilePath, fmt.Sprintf("%s %s\n", key, value)) + } else { + return fmt.Errorf("unsupported file extension: %s", ext) + } +} + +// Helper function to append content to the file +func writeToFile(filename, content string) error { + f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open output file: %w", err) + } + defer f.Close() + + _, err = f.WriteString(content) + if err != nil { + return fmt.Errorf("failed to write to file: %w", err) + } + + return nil +} \ No newline at end of file From 070cccc55d839a3c460d46b80a1ed7b2fae5f392 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:27:32 +0530 Subject: [PATCH 02/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 8563538..9fb1cbe 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -32,7 +32,7 @@ func SetError(message string) error { } // WriteEnvToFile writes a key-value pair to the DRONE_OUTPUT file -func WriteEnvToFile(key, value string) error { +func WriteEnvToOutputFile(key, value string) error { outputFilePath := os.Getenv("DRONE_OUTPUT") // Check the extension of the output file (.env or .out) From e92ac96ea15dbc5b827e539c4e6b42b302896e4a Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:27:40 +0530 Subject: [PATCH 03/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 9fb1cbe..7d2fdc8 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -13,7 +13,7 @@ const ( // SetSecret sets a new secret by adding it to the output func SetSecret(name, value string) error { - return WriteEnvToFile(name, value) + return WriteEnvToOutputFile(name, value) } // UpdateSecret updates an existing secret with a new value From 60cff6e73da9ab58f30db512cf9e651626fcea09 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:27:52 +0530 Subject: [PATCH 04/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 7d2fdc8..8788a8e 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -18,7 +18,7 @@ func SetSecret(name, value string) error { // UpdateSecret updates an existing secret with a new value func UpdateSecret(name, value string) error { - return WriteEnvToFile(name, value) + return WriteEnvToOutputFile(name, value) } // DeleteSecret removes a secret from the output From c73ad8c98e68843a51d89a3e1b98e5fa0fd1e1fe Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:27:59 +0530 Subject: [PATCH 05/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 8788a8e..b7bc110 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -23,7 +23,7 @@ func UpdateSecret(name, value string) error { // DeleteSecret removes a secret from the output func DeleteSecret(name string) error { - return WriteEnvToFile(name, "") + return WriteEnvToOutputFile(name, "") } // SetError sets the error message and writes it to the DRONE_OUTPUT file From 152cb7c1c26822b73663e78eb6ccae41c1d1b615 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Wed, 13 Nov 2024 18:36:59 +0530 Subject: [PATCH 06/26] Using CI_ERROR_MESSAGE and CI_ERROR_CODE --- go.mod | 2 +- harness/variables.go | 53 +++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 47c2603..7b8083e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/drone-plugins/drone-plugin-lib +module github.com/DevanshMathur19/drone-plugin-lib go 1.19 diff --git a/harness/variables.go b/harness/variables.go index 8563538..913a378 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -8,52 +8,65 @@ import ( ) const ( - PluginErrorMessageKey = "PLUGIN_ERROR_MESSAGE" + CIErrorMessageKey = "CI_ERROR_MESSAGE" + CIErrorCodeKey = "CI_ERROR_CODE" + CIMetadataFileEnv = "CI_ERROR_METADATA" + DroneOutputFileEnv = "DRONE_OUTPUT" ) -// SetSecret sets a new secret by adding it to the output +// SetSecret sets a new secret by adding it to the DRONE_OUTPUT file func SetSecret(name, value string) error { - return WriteEnvToFile(name, value) + return WriteEnvToFile(DroneOutputFileEnv, name, value) } -// UpdateSecret updates an existing secret with a new value +// UpdateSecret updates an existing secret with a new value in the DRONE_OUTPUT file func UpdateSecret(name, value string) error { - return WriteEnvToFile(name, value) + return WriteEnvToFile(DroneOutputFileEnv, name, value) } -// DeleteSecret removes a secret from the output +// DeleteSecret removes a secret by setting it to an empty value in the DRONE_OUTPUT file func DeleteSecret(name string) error { - return WriteEnvToFile(name, "") + return WriteEnvToFile(DroneOutputFileEnv, name, "") } -// SetError sets the error message and writes it to the DRONE_OUTPUT file -func SetError(message string) error { - return WriteEnvToFile(PluginErrorMessageKey, message) +// SetError sets the error message and error code, writing them to the CI_ERROR_METADATA file +func SetError(message, code string) error { + if err := WriteEnvToFile(CIMetadataFileEnv, CIErrorMessageKey, message); err != nil { + return err + } + return WriteEnvToFile(CIMetadataFileEnv, CIErrorCodeKey, code) } -// WriteEnvToFile writes a key-value pair to the DRONE_OUTPUT file -func WriteEnvToFile(key, value string) error { - outputFilePath := os.Getenv("DRONE_OUTPUT") - - // Check the extension of the output file (.env or .out) - ext := strings.ToLower(filepath.Ext(outputFilePath)) +// WriteEnvToFile writes a key-value pair to the specified file, determined by an environment variable +func WriteEnvToFile(envVar, key, value string) error { + // Get the file path from the specified environment variable + filePath := os.Getenv(envVar) + if filePath == "" { + return fmt.Errorf("environment variable %s is not set", envVar) + } + + // Check the extension of the file (.env or .out) + ext := strings.ToLower(filepath.Ext(filePath)) + var content string if ext == ".env" { // Write in .env format (KEY=VALUE) - return writeToFile(outputFilePath, fmt.Sprintf("%s=%s\n", key, value)) + content = fmt.Sprintf("%s=%s\n", key, value) } else if ext == ".out" { // Write in .out format (export KEY="VALUE") - return writeToFile(outputFilePath, fmt.Sprintf("%s %s\n", key, value)) + content = fmt.Sprintf("%s \"%s\"\n", key, value) } else { return fmt.Errorf("unsupported file extension: %s", ext) } + + return writeToFile(filePath, content) } // Helper function to append content to the file func writeToFile(filename, content string) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return fmt.Errorf("failed to open output file: %w", err) + return fmt.Errorf("failed to open file: %w", err) } defer f.Close() @@ -63,4 +76,4 @@ func writeToFile(filename, content string) error { } return nil -} \ No newline at end of file +} From c3cb2bafbb8780a5c23b7854e0e3a18d9656cd97 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Wed, 13 Nov 2024 18:40:13 +0530 Subject: [PATCH 07/26] Fixing go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7b8083e..47c2603 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/DevanshMathur19/drone-plugin-lib +module github.com/drone-plugins/drone-plugin-lib go 1.19 From 5f6c33da59bf2f7d5bcef619357f79f40e4feeae Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:14:48 +0530 Subject: [PATCH 08/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 913a378..fb6aa1d 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -38,7 +38,7 @@ func SetError(message, code string) error { } // WriteEnvToFile writes a key-value pair to the specified file, determined by an environment variable -func WriteEnvToFile(envVar, key, value string) error { +func WriteEnvToOutputFile(envVar, key, value string) error { // Get the file path from the specified environment variable filePath := os.Getenv(envVar) if filePath == "" { From daa1d98c1a919d4003136cbeda5c05fbd1d92bf1 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:14:55 +0530 Subject: [PATCH 09/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index fb6aa1d..a8ca8ae 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -16,7 +16,7 @@ const ( // SetSecret sets a new secret by adding it to the DRONE_OUTPUT file func SetSecret(name, value string) error { - return WriteEnvToFile(DroneOutputFileEnv, name, value) + return WriteEnvToOutputFile(DroneOutputFileEnv, name, value) } // UpdateSecret updates an existing secret with a new value in the DRONE_OUTPUT file From 49acda862df54ff113277d782200c77e679efb39 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:15:00 +0530 Subject: [PATCH 10/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index a8ca8ae..4dfce4a 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -21,7 +21,7 @@ func SetSecret(name, value string) error { // UpdateSecret updates an existing secret with a new value in the DRONE_OUTPUT file func UpdateSecret(name, value string) error { - return WriteEnvToFile(DroneOutputFileEnv, name, value) + return WriteEnvToOutputFile(DroneOutputFileEnv, name, value) } // DeleteSecret removes a secret by setting it to an empty value in the DRONE_OUTPUT file From 108862e5d7bab42d09f8a349449a6e4404060648 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:15:06 +0530 Subject: [PATCH 11/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 4dfce4a..1da66f2 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -26,7 +26,7 @@ func UpdateSecret(name, value string) error { // DeleteSecret removes a secret by setting it to an empty value in the DRONE_OUTPUT file func DeleteSecret(name string) error { - return WriteEnvToFile(DroneOutputFileEnv, name, "") + return WriteEnvToOutputFile(DroneOutputFileEnv, name, "") } // SetError sets the error message and error code, writing them to the CI_ERROR_METADATA file From cb2df35987b0845f2f537effe309a45026653072 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:15:11 +0530 Subject: [PATCH 12/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 1da66f2..dd7f0d4 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -31,7 +31,7 @@ func DeleteSecret(name string) error { // SetError sets the error message and error code, writing them to the CI_ERROR_METADATA file func SetError(message, code string) error { - if err := WriteEnvToFile(CIMetadataFileEnv, CIErrorMessageKey, message); err != nil { + if err := WriteEnvToOutputFile(CIMetadataFileEnv, CIErrorMessageKey, message); err != nil { return err } return WriteEnvToFile(CIMetadataFileEnv, CIErrorCodeKey, code) From 0594eed4183db64fbaf4a04933bb8976132edc77 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:15:19 +0530 Subject: [PATCH 13/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index dd7f0d4..fcf4794 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -34,7 +34,7 @@ func SetError(message, code string) error { if err := WriteEnvToOutputFile(CIMetadataFileEnv, CIErrorMessageKey, message); err != nil { return err } - return WriteEnvToFile(CIMetadataFileEnv, CIErrorCodeKey, code) + return WriteEnvToOutputFile(CIMetadataFileEnv, CIErrorCodeKey, code) } // WriteEnvToFile writes a key-value pair to the specified file, determined by an environment variable From ca32e59df17b32288d6e8c8470801797860ca9d3 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Tue, 19 Nov 2024 02:48:55 +0530 Subject: [PATCH 14/26] Updating variables and functions. --- harness/variables.go | 45 +++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/harness/variables.go b/harness/variables.go index fcf4794..18e8d22 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -8,36 +8,59 @@ import ( ) const ( - CIErrorMessageKey = "CI_ERROR_MESSAGE" - CIErrorCodeKey = "CI_ERROR_CODE" - CIMetadataFileEnv = "CI_ERROR_METADATA" - DroneOutputFileEnv = "DRONE_OUTPUT" + // ErrorMessageKey is the key used to retrieve or store the error message content. + ErrorMessageKey = "ERROR_MESSAGE" + + // ErrorCodeKey is the key used to identify the specific error code associated with an error. + ErrorCodeKey = "ERROR_CODE" + + // ErrorCategoryKey is the key used to classify the category of the error, which can help in grouping similar types of errors. + ErrorCategoryKey = "ERROR_CATEGORY" + + // MetadataFile is the key for the file that stores metadata associated with an error, such as details about the error's source or context. + MetadataFile = "ERROR_METADATA_FILE" + + // DroneOutputFile is the key for the file where output related to the Drone CI process is stored. + DroneOutputFile = "DRONE_OUTPUT" ) // SetSecret sets a new secret by adding it to the DRONE_OUTPUT file func SetSecret(name, value string) error { - return WriteEnvToOutputFile(DroneOutputFileEnv, name, value) + return WriteEnvToOutputFile(DroneOutputFile, name, value) } // UpdateSecret updates an existing secret with a new value in the DRONE_OUTPUT file func UpdateSecret(name, value string) error { - return WriteEnvToOutputFile(DroneOutputFileEnv, name, value) + return WriteEnvToOutputFile(DroneOutputFile, name, value) } // DeleteSecret removes a secret by setting it to an empty value in the DRONE_OUTPUT file func DeleteSecret(name string) error { - return WriteEnvToOutputFile(DroneOutputFileEnv, name, "") + return WriteEnvToOutputFile(DroneOutputFile, name, "") } // SetError sets the error message and error code, writing them to the CI_ERROR_METADATA file -func SetError(message, code string) error { - if err := WriteEnvToOutputFile(CIMetadataFileEnv, CIErrorMessageKey, message); err != nil { +// SetError sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file +func SetError(message, code, category string) error { + // Write the error message + if err := WriteEnvToOutputFile(MetadataFile, ErrorMessageKey, message); err != nil { + return err + } + + // Write the error code + if err := WriteEnvToOutputFile(MetadataFile, ErrorCodeKey, code); err != nil { + return err + } + + // Write the error category + if err := WriteEnvToOutputFile(MetadataFile, ErrorCategoryKey, category); err != nil { return err } - return WriteEnvToOutputFile(CIMetadataFileEnv, CIErrorCodeKey, code) + + return nil } -// WriteEnvToFile writes a key-value pair to the specified file, determined by an environment variable +// WriteEnvToOutputFile writes a key-value pair to the specified file, determined by an environment variable func WriteEnvToOutputFile(envVar, key, value string) error { // Get the file path from the specified environment variable filePath := os.Getenv(envVar) From 7fdd7d88360639f51bdb4acb0605c0c9ea989484 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:48:42 +0530 Subject: [PATCH 15/26] Update harness/variables.go --- harness/variables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness/variables.go b/harness/variables.go index 18e8d22..d85921f 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -20,7 +20,7 @@ const ( // MetadataFile is the key for the file that stores metadata associated with an error, such as details about the error's source or context. MetadataFile = "ERROR_METADATA_FILE" - // DroneOutputFile is the key for the file where output related to the Drone CI process is stored. + // DroneOutputFile is the key for the file where output can exported and utilized in the subsequent steps in Harness CI pipeline. DroneOutputFile = "DRONE_OUTPUT" ) From bbf67d88df78270ad79c3ee183b26bcbe94b78ed Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Wed, 20 Nov 2024 16:21:19 +0530 Subject: [PATCH 16/26] Adding new functions and updating Update and Delete methods. --- harness/variables.go | 150 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 14 deletions(-) diff --git a/harness/variables.go b/harness/variables.go index d85921f..7efb0e2 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -1,6 +1,7 @@ package harness import ( + "bufio" "fmt" "os" "path/filepath" @@ -20,28 +21,45 @@ const ( // MetadataFile is the key for the file that stores metadata associated with an error, such as details about the error's source or context. MetadataFile = "ERROR_METADATA_FILE" - // DroneOutputFile is the key for the file where output can exported and utilized in the subsequent steps in Harness CI pipeline. + // DroneOutputFile is the key for the file where outputs can be exported and utilized in the subsequent steps in Harness CI pipeline. DroneOutputFile = "DRONE_OUTPUT" + + // HarnessOutputSecretFile is the key for the file where secrets can be exported and utilized in the subsequent steps in Harness CI pipeline. + HarnessOutputSecretFile = "HARNESS_OUTPUT_SECRET_FILE" ) -// SetSecret sets a new secret by adding it to the DRONE_OUTPUT file +// SetSecret sets a new secret by adding it to the HARNESS_OUTPUT_SECRET_FILE file func SetSecret(name, value string) error { - return WriteEnvToOutputFile(DroneOutputFile, name, value) + return WriteEnvToOutputFile(HarnessOutputSecretFile, name, value) } -// UpdateSecret updates an existing secret with a new value in the DRONE_OUTPUT file +// UpdateSecret overwrites the value of an existing secret. func UpdateSecret(name, value string) error { - return WriteEnvToOutputFile(DroneOutputFile, name, value) + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false) } -// DeleteSecret removes a secret by setting it to an empty value in the DRONE_OUTPUT file +// DeleteSecret removes a secret from the file entirely. func DeleteSecret(name string) error { - return WriteEnvToOutputFile(DroneOutputFile, name, "") + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, "", true) } -// SetError sets the error message and error code, writing them to the CI_ERROR_METADATA file -// SetError sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file -func SetError(message, code, category string) error { +// SetOutput sets a new secret by adding it to the DRONE_OUTPUT file +func SetOutput(name, value string) error { + return WriteEnvToOutputFile(DroneOutputFile, name, value) +} + +// UpdateOutput overwrites the value of an existing output. +func UpdateOutput(name, value string) error { + return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false) +} + +// DeleteOutput removes an output from the file entirely. +func DeleteOutput(name string) error { + return UpdateOrRemoveKeyValue(DroneOutputFile, name, "", true) +} + +// SetErrorMetadata sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file +func SetErrorMetadata(message, code, category string) error { // Write the error message if err := WriteEnvToOutputFile(MetadataFile, ErrorMessageKey, message); err != nil { return err @@ -76,17 +94,17 @@ func WriteEnvToOutputFile(envVar, key, value string) error { // Write in .env format (KEY=VALUE) content = fmt.Sprintf("%s=%s\n", key, value) } else if ext == ".out" { - // Write in .out format (export KEY="VALUE") - content = fmt.Sprintf("%s \"%s\"\n", key, value) + // Write in .out format (KEY VALUE) + content = fmt.Sprintf("%s %s\n", key, value) } else { return fmt.Errorf("unsupported file extension: %s", ext) } - return writeToFile(filePath, content) + return WriteToFile(filePath, content) } // Helper function to append content to the file -func writeToFile(filename, content string) error { +func WriteToFile(filename, content string) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open file: %w", err) @@ -100,3 +118,107 @@ func writeToFile(filename, content string) error { return nil } + + +// UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file. +func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { + // Get the file path from the environment variable + filePath := os.Getenv(envVar) + if filePath == "" { + return fmt.Errorf("environment variable %s is not set", envVar) + } + + // Determine the file extension to handle formats + ext := strings.ToLower(filepath.Ext(filePath)) + + // Read the file contents into memory + lines, err := ReadLines(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + // Process lines + var updatedLines []string + found := false + for _, line := range lines { + k, v := ParseKeyValue(line, ext) + if k == key { + found = true + if delete { + continue // Skip the line to delete it + } + updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) + } else { + updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) + } + } + + // Append new key-value if not found and not deleting + if !found && !delete { + updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) + } + + // Write updated lines back to the file + return WriteLines(filePath, updatedLines) +} + +// Helper function to read lines from a file. +func ReadLines(filename string) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, scanner.Err() +} + +// Helper function to write lines to a file. +func WriteLines(filename string, lines []string) error { + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + for _, line := range lines { + _, err := file.WriteString(line + "\n") + if err != nil { + return fmt.Errorf("failed to write to file: %w", err) + } + } + return nil +} + +// Helper function to parse a line into key and value, considering file format. +func ParseKeyValue(line, ext string) (string, string) { + if ext == ".env" { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + } + return strings.TrimSpace(parts[0]), "" + } else if ext == ".out" { + parts := strings.Fields(line) + if len(parts) == 2 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + } + return strings.TrimSpace(parts[0]), "" + } + return "", "" +} + +// Helper function to format a key-value pair as a line, considering file format. +func FormatKeyValue(key, value, ext string) string { + if ext == ".env" { + return fmt.Sprintf("%s=%s", key, value) + } else if ext == ".out" { + return fmt.Sprintf("%s %s", key, value) + } + return "" +} \ No newline at end of file From 85e2820dd3b7f20712b8d9985a5a4de5f5381d7a Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Mon, 2 Dec 2024 21:19:39 +0530 Subject: [PATCH 17/26] Optimizing code . --- harness/variables.go | 63 +++++++++++--------------------------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/harness/variables.go b/harness/variables.go index 7efb0e2..6288fcf 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -30,7 +30,7 @@ const ( // SetSecret sets a new secret by adding it to the HARNESS_OUTPUT_SECRET_FILE file func SetSecret(name, value string) error { - return WriteEnvToOutputFile(HarnessOutputSecretFile, name, value) + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false) } // UpdateSecret overwrites the value of an existing secret. @@ -45,7 +45,7 @@ func DeleteSecret(name string) error { // SetOutput sets a new secret by adding it to the DRONE_OUTPUT file func SetOutput(name, value string) error { - return WriteEnvToOutputFile(DroneOutputFile, name, value) + return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false) } // UpdateOutput overwrites the value of an existing output. @@ -61,65 +61,23 @@ func DeleteOutput(name string) error { // SetErrorMetadata sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file func SetErrorMetadata(message, code, category string) error { // Write the error message - if err := WriteEnvToOutputFile(MetadataFile, ErrorMessageKey, message); err != nil { + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorMessageKey, message, false); err != nil { return err } // Write the error code - if err := WriteEnvToOutputFile(MetadataFile, ErrorCodeKey, code); err != nil { + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCodeKey, code, false); err != nil { return err } // Write the error category - if err := WriteEnvToOutputFile(MetadataFile, ErrorCategoryKey, category); err != nil { + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCategoryKey, category, false); err != nil { return err } return nil } -// WriteEnvToOutputFile writes a key-value pair to the specified file, determined by an environment variable -func WriteEnvToOutputFile(envVar, key, value string) error { - // Get the file path from the specified environment variable - filePath := os.Getenv(envVar) - if filePath == "" { - return fmt.Errorf("environment variable %s is not set", envVar) - } - - // Check the extension of the file (.env or .out) - ext := strings.ToLower(filepath.Ext(filePath)) - - var content string - if ext == ".env" { - // Write in .env format (KEY=VALUE) - content = fmt.Sprintf("%s=%s\n", key, value) - } else if ext == ".out" { - // Write in .out format (KEY VALUE) - content = fmt.Sprintf("%s %s\n", key, value) - } else { - return fmt.Errorf("unsupported file extension: %s", ext) - } - - return WriteToFile(filePath, content) -} - -// Helper function to append content to the file -func WriteToFile(filename, content string) error { - f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("failed to open file: %w", err) - } - defer f.Close() - - _, err = f.WriteString(content) - if err != nil { - return fmt.Errorf("failed to write to file: %w", err) - } - - return nil -} - - // UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file. func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { // Get the file path from the environment variable @@ -128,6 +86,15 @@ func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { return fmt.Errorf("environment variable %s is not set", envVar) } + // Ensure the file exists before reading + if _, err := os.Stat(filePath); os.IsNotExist(err) { + // Create the file if it does not exist + _, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + } + // Determine the file extension to handle formats ext := strings.ToLower(filepath.Ext(filePath)) @@ -221,4 +188,4 @@ func FormatKeyValue(key, value, ext string) string { return fmt.Sprintf("%s %s", key, value) } return "" -} \ No newline at end of file +} From deedf89000b09242554cc3c466cae51a5e21bf34 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:00:35 +0530 Subject: [PATCH 18/26] Fix staticcheck issue in .drone.yml --- .drone.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index dc8c6cf..127d043 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,9 +14,11 @@ steps: - name: staticcheck pull: always image: golang:1.19 + environment: + GO111MODULE: "on" # Explicitly enable Go modules commands: - - go get honnef.co/go/tools/cmd/staticcheck - - go run honnef.co/go/tools/cmd/staticcheck ./... + - go install honnef.co/go/tools/cmd/staticcheck@latest + - staticcheck ./... volumes: - name: gopath path: /go From 3de8df97602294757cc80b22e73f3aac574af4d8 Mon Sep 17 00:00:00 2001 From: "OP (oppenheimer)" <21008429+Ompragash@users.noreply.github.com> Date: Tue, 3 Dec 2024 01:05:15 +0530 Subject: [PATCH 19/26] Update .drone.yml --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 127d043..87bcda3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,7 +17,7 @@ steps: environment: GO111MODULE: "on" # Explicitly enable Go modules commands: - - go install honnef.co/go/tools/cmd/staticcheck@latest + - go install honnef.co/go/tools/cmd/staticcheck@v0.4.3 - staticcheck ./... volumes: - name: gopath From 05a2bbb21b039526b6b1f25776bd0fea30cdf3b4 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Tue, 3 Dec 2024 12:07:56 +0530 Subject: [PATCH 20/26] Adding UT's. --- go.sum | 4 - harness/variables_test.go | 230 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 harness/variables_test.go diff --git a/go.sum b/go.sum index 3c51b04..a1e18c3 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,6 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= -github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= -github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw= -github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4= github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= diff --git a/harness/variables_test.go b/harness/variables_test.go new file mode 100644 index 0000000..e861f55 --- /dev/null +++ b/harness/variables_test.go @@ -0,0 +1,230 @@ +package harness + +import ( + "os" + "path/filepath" + "testing" +) + +// Helper function to create a temporary file and set the environment variable +func setupTestFile(t *testing.T, envVar string) string { + t.Helper() + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "test.env") + err := os.WriteFile(tmpFile, []byte(""), 0644) + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + os.Setenv(envVar, tmpFile) + return tmpFile +} + +// Helper function to create a temporary .out file and set the environment variable +func setupOutTestFile(t *testing.T, envVar string) string { + t.Helper() + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "test.out") + err := os.WriteFile(tmpFile, []byte(""), 0644) + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + os.Setenv(envVar, tmpFile) + return tmpFile +} + +// Test for SetSecret +func TestSetSecret(t *testing.T) { + filePath := setupTestFile(t, HarnessOutputSecretFile) + + err := SetSecret("testKey", "testValue") + if err != nil { + t.Fatalf("SetSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "testKey=testValue\n" { + t.Errorf("Expected 'testKey=testValue', got: %s", string(content)) + } +} + +// Test for UpdateSecret +func TestUpdateSecret(t *testing.T) { + filePath := setupTestFile(t, HarnessOutputSecretFile) + + // Write initial content + err := os.WriteFile(filePath, []byte("testKey=oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial file content: %v", err) + } + + err = UpdateSecret("testKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "testKey=updatedValue\n" { + t.Errorf("Expected 'testKey=updatedValue', got: %s", string(content)) + } +} + +// Test for DeleteSecret +func TestDeleteSecret(t *testing.T) { + filePath := setupTestFile(t, HarnessOutputSecretFile) + + // Write initial content + err := os.WriteFile(filePath, []byte("testKey=testValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial file content: %v", err) + } + + err = DeleteSecret("testKey") + if err != nil { + t.Fatalf("DeleteSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) + } +} + +// Test for SetOutput +func TestSetOutput(t *testing.T) { + filePath := setupTestFile(t, DroneOutputFile) + + err := SetOutput("outputKey", "outputValue") + if err != nil { + t.Fatalf("SetOutput failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "outputKey=outputValue\n" { + t.Errorf("Expected 'outputKey=outputValue', got: %s", string(content)) + } +} + +// Test for SetErrorMetadata +func TestSetErrorMetadata(t *testing.T) { + filePath := setupTestFile(t, MetadataFile) + + err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") + if err != nil { + t.Fatalf("SetErrorMetadata failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + expected := "ERROR_MESSAGE=errorMessage\nERROR_CODE=errorCode\nERROR_CATEGORY=errorCategory\n" + if string(content) != expected { + t.Errorf("Expected '%s', got: %s", expected, string(content)) + } +} + +// Test for SetSecret with .out file +func TestSetSecretOutFile(t *testing.T) { + filePath := setupOutTestFile(t, HarnessOutputSecretFile) + + err := SetSecret("testKey", "testValue") + if err != nil { + t.Fatalf("SetSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "testKey testValue\n" { + t.Errorf("Expected 'testKey testValue', got: %s", string(content)) + } +} + +// Test for UpdateSecret with .out file +func TestUpdateSecretOutFile(t *testing.T) { + filePath := setupOutTestFile(t, HarnessOutputSecretFile) + + // Write initial content + err := os.WriteFile(filePath, []byte("testKey oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial file content: %v", err) + } + + err = UpdateSecret("testKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "testKey updatedValue\n" { + t.Errorf("Expected 'testKey updatedValue', got: %s", string(content)) + } +} + +// Test for DeleteSecret with .out file +func TestDeleteSecretOutFile(t *testing.T) { + filePath := setupOutTestFile(t, HarnessOutputSecretFile) + + // Write initial content + err := os.WriteFile(filePath, []byte("testKey testValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial file content: %v", err) + } + + err = DeleteSecret("testKey") + if err != nil { + t.Fatalf("DeleteSecret failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) + } +} + +// Test for SetErrorMetadata with .out file +func TestSetErrorMetadataOutFile(t *testing.T) { + filePath := setupOutTestFile(t, MetadataFile) + + err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") + if err != nil { + t.Fatalf("SetErrorMetadata failed: %v", err) + } + + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + expected := "ERROR_MESSAGE errorMessage\nERROR_CODE errorCode\nERROR_CATEGORY errorCategory\n" + if string(content) != expected { + t.Errorf("Expected '%s', got: %s", expected, string(content)) + } +} From 4891b9036eb38504c0c4411501807cdcd44e3a51 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Tue, 3 Dec 2024 12:47:31 +0530 Subject: [PATCH 21/26] Optimizing Tests. --- .drone.yml | 6 +- harness/variables_test.go | 265 +++++++++++++++++++++++++------------- 2 files changed, 176 insertions(+), 95 deletions(-) diff --git a/.drone.yml b/.drone.yml index dc8c6cf..87bcda3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,9 +14,11 @@ steps: - name: staticcheck pull: always image: golang:1.19 + environment: + GO111MODULE: "on" # Explicitly enable Go modules commands: - - go get honnef.co/go/tools/cmd/staticcheck - - go run honnef.co/go/tools/cmd/staticcheck ./... + - go install honnef.co/go/tools/cmd/staticcheck@v0.4.3 + - staticcheck ./... volumes: - name: gopath path: /go diff --git a/harness/variables_test.go b/harness/variables_test.go index e861f55..293b734 100644 --- a/harness/variables_test.go +++ b/harness/variables_test.go @@ -6,11 +6,11 @@ import ( "testing" ) -// Helper function to create a temporary file and set the environment variable -func setupTestFile(t *testing.T, envVar string) string { +// Helper function to create a temporary file with a given extension and set the environment variable +func setupTestFile(t *testing.T, envVar, extension string) string { t.Helper() tmpDir := t.TempDir() - tmpFile := filepath.Join(tmpDir, "test.env") + tmpFile := filepath.Join(tmpDir, "test."+extension) err := os.WriteFile(tmpFile, []byte(""), 0644) if err != nil { t.Fatalf("Failed to create temporary file: %v", err) @@ -19,46 +19,52 @@ func setupTestFile(t *testing.T, envVar string) string { return tmpFile } -// Helper function to create a temporary .out file and set the environment variable -func setupOutTestFile(t *testing.T, envVar string) string { - t.Helper() - tmpDir := t.TempDir() - tmpFile := filepath.Join(tmpDir, "test.out") - err := os.WriteFile(tmpFile, []byte(""), 0644) - if err != nil { - t.Fatalf("Failed to create temporary file: %v", err) - } - os.Setenv(envVar, tmpFile) - return tmpFile -} - -// Test for SetSecret +// Test for SetSecret with both .env and .out file extensions func TestSetSecret(t *testing.T) { - filePath := setupTestFile(t, HarnessOutputSecretFile) + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") err := SetSecret("testKey", "testValue") if err != nil { t.Fatalf("SetSecret failed: %v", err) } - content, err := os.ReadFile(filePath) + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to read .env file: %v", err) } if string(content) != "testKey=testValue\n" { t.Errorf("Expected 'testKey=testValue', got: %s", string(content)) } + + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + + err = SetSecret("testKey", "testValue") + if err != nil { + t.Fatalf("SetSecret failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "testKey testValue\n" { + t.Errorf("Expected 'testKey testValue', got: %s", string(content)) + } } -// Test for UpdateSecret +// Test for UpdateSecret with both .env and .out file extensions func TestUpdateSecret(t *testing.T) { - filePath := setupTestFile(t, HarnessOutputSecretFile) + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") // Write initial content - err := os.WriteFile(filePath, []byte("testKey=oldValue\n"), 0644) + err := os.WriteFile(envFilePath, []byte("testKey=oldValue\n"), 0644) if err != nil { - t.Fatalf("Failed to write initial file content: %v", err) + t.Fatalf("Failed to write initial .env file content: %v", err) } err = UpdateSecret("testKey", "updatedValue") @@ -66,24 +72,48 @@ func TestUpdateSecret(t *testing.T) { t.Fatalf("UpdateSecret failed: %v", err) } - content, err := os.ReadFile(filePath) + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to read .env file: %v", err) } if string(content) != "testKey=updatedValue\n" { t.Errorf("Expected 'testKey=updatedValue', got: %s", string(content)) } + + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + + // Write initial content + err = os.WriteFile(outFilePath, []byte("testKey oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + + err = UpdateSecret("testKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateSecret failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "testKey updatedValue\n" { + t.Errorf("Expected 'testKey updatedValue', got: %s", string(content)) + } } -// Test for DeleteSecret +// Test for DeleteSecret with both .env and .out file extensions func TestDeleteSecret(t *testing.T) { - filePath := setupTestFile(t, HarnessOutputSecretFile) + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") // Write initial content - err := os.WriteFile(filePath, []byte("testKey=testValue\n"), 0644) + err := os.WriteFile(envFilePath, []byte("testKey=testValue\n"), 0644) if err != nil { - t.Fatalf("Failed to write initial file content: %v", err) + t.Fatalf("Failed to write initial .env file content: %v", err) } err = DeleteSecret("testKey") @@ -91,140 +121,189 @@ func TestDeleteSecret(t *testing.T) { t.Fatalf("DeleteSecret failed: %v", err) } - content, err := os.ReadFile(filePath) + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to read .env file: %v", err) } if string(content) != "" { - t.Errorf("Expected file to be empty, got: %s", string(content)) + t.Errorf("Expected .env file to be empty, got: %s", string(content)) } -} -// Test for SetOutput -func TestSetOutput(t *testing.T) { - filePath := setupTestFile(t, DroneOutputFile) + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") - err := SetOutput("outputKey", "outputValue") + // Write initial content + err = os.WriteFile(outFilePath, []byte("testKey testValue\n"), 0644) if err != nil { - t.Fatalf("SetOutput failed: %v", err) + t.Fatalf("Failed to write initial .out file content: %v", err) } - content, err := os.ReadFile(filePath) + err = DeleteSecret("testKey") if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("DeleteSecret failed: %v", err) } - if string(content) != "outputKey=outputValue\n" { - t.Errorf("Expected 'outputKey=outputValue', got: %s", string(content)) + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "" { + t.Errorf("Expected .out file to be empty, got: %s", string(content)) } } -// Test for SetErrorMetadata +// Test for SetErrorMetadata with both .env and .out file extensions func TestSetErrorMetadata(t *testing.T) { - filePath := setupTestFile(t, MetadataFile) + // Test for .env file + envFilePath := setupTestFile(t, MetadataFile, "env") err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") if err != nil { t.Fatalf("SetErrorMetadata failed: %v", err) } - content, err := os.ReadFile(filePath) + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to read .env file: %v", err) } expected := "ERROR_MESSAGE=errorMessage\nERROR_CODE=errorCode\nERROR_CATEGORY=errorCategory\n" if string(content) != expected { t.Errorf("Expected '%s', got: %s", expected, string(content)) } -} -// Test for SetSecret with .out file -func TestSetSecretOutFile(t *testing.T) { - filePath := setupOutTestFile(t, HarnessOutputSecretFile) + // Test for .out file + outFilePath := setupTestFile(t, MetadataFile, "out") - err := SetSecret("testKey", "testValue") + err = SetErrorMetadata("errorMessage", "errorCode", "errorCategory") if err != nil { - t.Fatalf("SetSecret failed: %v", err) + t.Fatalf("SetErrorMetadata failed: %v", err) } - content, err := os.ReadFile(filePath) + content, err = os.ReadFile(outFilePath) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to read .out file: %v", err) } - if string(content) != "testKey testValue\n" { - t.Errorf("Expected 'testKey testValue', got: %s", string(content)) + expectedOut := "ERROR_MESSAGE errorMessage\nERROR_CODE errorCode\nERROR_CATEGORY errorCategory\n" + if string(content) != expectedOut { + t.Errorf("Expected '%s', got: %s", expectedOut, string(content)) } } -// Test for UpdateSecret with .out file -func TestUpdateSecretOutFile(t *testing.T) { - filePath := setupOutTestFile(t, HarnessOutputSecretFile) - - // Write initial content - err := os.WriteFile(filePath, []byte("testKey oldValue\n"), 0644) +// Test for SetOutput with both .env and .out file extensions +func TestSetOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := SetOutput("outputKey", "outputValue") if err != nil { - t.Fatalf("Failed to write initial file content: %v", err) + t.Fatalf("SetOutput failed: %v", err) } - err = UpdateSecret("testKey", "updatedValue") + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("UpdateSecret failed: %v", err) + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "outputKey=outputValue\n" { + t.Errorf("Expected 'outputKey=outputValue', got: %s", string(content)) } - content, err := os.ReadFile(filePath) + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = SetOutput("outputKey", "outputValue") if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("SetOutput failed: %v", err) } - if string(content) != "testKey updatedValue\n" { - t.Errorf("Expected 'testKey updatedValue', got: %s", string(content)) + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "outputKey outputValue\n" { + t.Errorf("Expected 'outputKey outputValue', got: %s", string(content)) } } -// Test for DeleteSecret with .out file -func TestDeleteSecretOutFile(t *testing.T) { - filePath := setupOutTestFile(t, HarnessOutputSecretFile) - - // Write initial content - err := os.WriteFile(filePath, []byte("testKey testValue\n"), 0644) +// Test for UpdateOutput with both .env and .out file extensions +func TestUpdateOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := os.WriteFile(envFilePath, []byte("outputKey=oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + err = UpdateOutput("outputKey", "updatedValue") if err != nil { - t.Fatalf("Failed to write initial file content: %v", err) + t.Fatalf("UpdateOutput failed: %v", err) } - err = DeleteSecret("testKey") + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("DeleteSecret failed: %v", err) + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "outputKey=updatedValue\n" { + t.Errorf("Expected 'outputKey=updatedValue', got: %s", string(content)) } - content, err := os.ReadFile(filePath) + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = os.WriteFile(outFilePath, []byte("outputKey oldValue\n"), 0644) if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("Failed to write initial .out file content: %v", err) + } + err = UpdateOutput("outputKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateOutput failed: %v", err) } - if string(content) != "" { - t.Errorf("Expected file to be empty, got: %s", string(content)) + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "outputKey updatedValue\n" { + t.Errorf("Expected 'outputKey updatedValue', got: %s", string(content)) } } -// Test for SetErrorMetadata with .out file -func TestSetErrorMetadataOutFile(t *testing.T) { - filePath := setupOutTestFile(t, MetadataFile) +// Test for DeleteOutput with both .env and .out file extensions +func TestDeleteOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := os.WriteFile(envFilePath, []byte("outputKey=outputValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + err = DeleteOutput("outputKey") + if err != nil { + t.Fatalf("DeleteOutput failed: %v", err) + } - err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") + content, err := os.ReadFile(envFilePath) if err != nil { - t.Fatalf("SetErrorMetadata failed: %v", err) + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) } - content, err := os.ReadFile(filePath) + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = os.WriteFile(outFilePath, []byte("outputKey outputValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + err = DeleteOutput("outputKey") if err != nil { - t.Fatalf("Failed to read file: %v", err) + t.Fatalf("DeleteOutput failed: %v", err) } - expected := "ERROR_MESSAGE errorMessage\nERROR_CODE errorCode\nERROR_CATEGORY errorCategory\n" - if string(content) != expected { - t.Errorf("Expected '%s', got: %s", expected, string(content)) + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) } } From cc345e6840b0d1da0c09e7e7d74ebcae465be810 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Tue, 3 Dec 2024 12:52:08 +0530 Subject: [PATCH 22/26] Fixing lint errors. --- harness/variables.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/harness/variables.go b/harness/variables.go index 6288fcf..d5e4442 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -129,7 +129,7 @@ func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { return WriteLines(filePath, updatedLines) } -// Helper function to read lines from a file. +// ReadLines reads lines from a file and returns them as a slice of strings. func ReadLines(filename string) ([]string, error) { file, err := os.Open(filename) if err != nil { @@ -145,7 +145,7 @@ func ReadLines(filename string) ([]string, error) { return lines, scanner.Err() } -// Helper function to write lines to a file. +// WriteLines writes a slice of strings to a file, each string being written to a new line. func WriteLines(filename string, lines []string) error { file, err := os.Create(filename) if err != nil { @@ -162,7 +162,7 @@ func WriteLines(filename string, lines []string) error { return nil } -// Helper function to parse a line into key and value, considering file format. +// ParseKeyValue parses a key-value pair from a string and returns the key and value. func ParseKeyValue(line, ext string) (string, string) { if ext == ".env" { parts := strings.SplitN(line, "=", 2) @@ -180,7 +180,7 @@ func ParseKeyValue(line, ext string) (string, string) { return "", "" } -// Helper function to format a key-value pair as a line, considering file format. +// FormatKeyValue formats a key-value pair into a string. func FormatKeyValue(key, value, ext string) string { if ext == ".env" { return fmt.Sprintf("%s=%s", key, value) From d92cff41257548dd826a6f97c4f06beaf370f1cd Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Tue, 3 Dec 2024 19:34:26 +0530 Subject: [PATCH 23/26] Rebasing and fixing. --- go.sum | 4 - harness/variables.go | 191 +++++++++++++++++++++++ harness/variables_test.go | 309 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 500 insertions(+), 4 deletions(-) create mode 100644 harness/variables.go create mode 100644 harness/variables_test.go diff --git a/go.sum b/go.sum index 3c51b04..a1e18c3 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,6 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= -github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= -github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw= -github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4= github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= diff --git a/harness/variables.go b/harness/variables.go new file mode 100644 index 0000000..d5e4442 --- /dev/null +++ b/harness/variables.go @@ -0,0 +1,191 @@ +package harness + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" +) + +const ( + // ErrorMessageKey is the key used to retrieve or store the error message content. + ErrorMessageKey = "ERROR_MESSAGE" + + // ErrorCodeKey is the key used to identify the specific error code associated with an error. + ErrorCodeKey = "ERROR_CODE" + + // ErrorCategoryKey is the key used to classify the category of the error, which can help in grouping similar types of errors. + ErrorCategoryKey = "ERROR_CATEGORY" + + // MetadataFile is the key for the file that stores metadata associated with an error, such as details about the error's source or context. + MetadataFile = "ERROR_METADATA_FILE" + + // DroneOutputFile is the key for the file where outputs can be exported and utilized in the subsequent steps in Harness CI pipeline. + DroneOutputFile = "DRONE_OUTPUT" + + // HarnessOutputSecretFile is the key for the file where secrets can be exported and utilized in the subsequent steps in Harness CI pipeline. + HarnessOutputSecretFile = "HARNESS_OUTPUT_SECRET_FILE" +) + +// SetSecret sets a new secret by adding it to the HARNESS_OUTPUT_SECRET_FILE file +func SetSecret(name, value string) error { + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false) +} + +// UpdateSecret overwrites the value of an existing secret. +func UpdateSecret(name, value string) error { + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false) +} + +// DeleteSecret removes a secret from the file entirely. +func DeleteSecret(name string) error { + return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, "", true) +} + +// SetOutput sets a new secret by adding it to the DRONE_OUTPUT file +func SetOutput(name, value string) error { + return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false) +} + +// UpdateOutput overwrites the value of an existing output. +func UpdateOutput(name, value string) error { + return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false) +} + +// DeleteOutput removes an output from the file entirely. +func DeleteOutput(name string) error { + return UpdateOrRemoveKeyValue(DroneOutputFile, name, "", true) +} + +// SetErrorMetadata sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file +func SetErrorMetadata(message, code, category string) error { + // Write the error message + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorMessageKey, message, false); err != nil { + return err + } + + // Write the error code + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCodeKey, code, false); err != nil { + return err + } + + // Write the error category + if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCategoryKey, category, false); err != nil { + return err + } + + return nil +} + +// UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file. +func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { + // Get the file path from the environment variable + filePath := os.Getenv(envVar) + if filePath == "" { + return fmt.Errorf("environment variable %s is not set", envVar) + } + + // Ensure the file exists before reading + if _, err := os.Stat(filePath); os.IsNotExist(err) { + // Create the file if it does not exist + _, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + } + + // Determine the file extension to handle formats + ext := strings.ToLower(filepath.Ext(filePath)) + + // Read the file contents into memory + lines, err := ReadLines(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + // Process lines + var updatedLines []string + found := false + for _, line := range lines { + k, v := ParseKeyValue(line, ext) + if k == key { + found = true + if delete { + continue // Skip the line to delete it + } + updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) + } else { + updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) + } + } + + // Append new key-value if not found and not deleting + if !found && !delete { + updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) + } + + // Write updated lines back to the file + return WriteLines(filePath, updatedLines) +} + +// ReadLines reads lines from a file and returns them as a slice of strings. +func ReadLines(filename string) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, scanner.Err() +} + +// WriteLines writes a slice of strings to a file, each string being written to a new line. +func WriteLines(filename string, lines []string) error { + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + for _, line := range lines { + _, err := file.WriteString(line + "\n") + if err != nil { + return fmt.Errorf("failed to write to file: %w", err) + } + } + return nil +} + +// ParseKeyValue parses a key-value pair from a string and returns the key and value. +func ParseKeyValue(line, ext string) (string, string) { + if ext == ".env" { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + } + return strings.TrimSpace(parts[0]), "" + } else if ext == ".out" { + parts := strings.Fields(line) + if len(parts) == 2 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + } + return strings.TrimSpace(parts[0]), "" + } + return "", "" +} + +// FormatKeyValue formats a key-value pair into a string. +func FormatKeyValue(key, value, ext string) string { + if ext == ".env" { + return fmt.Sprintf("%s=%s", key, value) + } else if ext == ".out" { + return fmt.Sprintf("%s %s", key, value) + } + return "" +} diff --git a/harness/variables_test.go b/harness/variables_test.go new file mode 100644 index 0000000..293b734 --- /dev/null +++ b/harness/variables_test.go @@ -0,0 +1,309 @@ +package harness + +import ( + "os" + "path/filepath" + "testing" +) + +// Helper function to create a temporary file with a given extension and set the environment variable +func setupTestFile(t *testing.T, envVar, extension string) string { + t.Helper() + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "test."+extension) + err := os.WriteFile(tmpFile, []byte(""), 0644) + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + os.Setenv(envVar, tmpFile) + return tmpFile +} + +// Test for SetSecret with both .env and .out file extensions +func TestSetSecret(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") + + err := SetSecret("testKey", "testValue") + if err != nil { + t.Fatalf("SetSecret failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read .env file: %v", err) + } + + if string(content) != "testKey=testValue\n" { + t.Errorf("Expected 'testKey=testValue', got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + + err = SetSecret("testKey", "testValue") + if err != nil { + t.Fatalf("SetSecret failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "testKey testValue\n" { + t.Errorf("Expected 'testKey testValue', got: %s", string(content)) + } +} + +// Test for UpdateSecret with both .env and .out file extensions +func TestUpdateSecret(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") + + // Write initial content + err := os.WriteFile(envFilePath, []byte("testKey=oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + + err = UpdateSecret("testKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateSecret failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read .env file: %v", err) + } + + if string(content) != "testKey=updatedValue\n" { + t.Errorf("Expected 'testKey=updatedValue', got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + + // Write initial content + err = os.WriteFile(outFilePath, []byte("testKey oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + + err = UpdateSecret("testKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateSecret failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "testKey updatedValue\n" { + t.Errorf("Expected 'testKey updatedValue', got: %s", string(content)) + } +} + +// Test for DeleteSecret with both .env and .out file extensions +func TestDeleteSecret(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") + + // Write initial content + err := os.WriteFile(envFilePath, []byte("testKey=testValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + + err = DeleteSecret("testKey") + if err != nil { + t.Fatalf("DeleteSecret failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read .env file: %v", err) + } + + if string(content) != "" { + t.Errorf("Expected .env file to be empty, got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + + // Write initial content + err = os.WriteFile(outFilePath, []byte("testKey testValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + + err = DeleteSecret("testKey") + if err != nil { + t.Fatalf("DeleteSecret failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + if string(content) != "" { + t.Errorf("Expected .out file to be empty, got: %s", string(content)) + } +} + +// Test for SetErrorMetadata with both .env and .out file extensions +func TestSetErrorMetadata(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, MetadataFile, "env") + + err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") + if err != nil { + t.Fatalf("SetErrorMetadata failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read .env file: %v", err) + } + + expected := "ERROR_MESSAGE=errorMessage\nERROR_CODE=errorCode\nERROR_CATEGORY=errorCategory\n" + if string(content) != expected { + t.Errorf("Expected '%s', got: %s", expected, string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, MetadataFile, "out") + + err = SetErrorMetadata("errorMessage", "errorCode", "errorCategory") + if err != nil { + t.Fatalf("SetErrorMetadata failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read .out file: %v", err) + } + + expectedOut := "ERROR_MESSAGE errorMessage\nERROR_CODE errorCode\nERROR_CATEGORY errorCategory\n" + if string(content) != expectedOut { + t.Errorf("Expected '%s', got: %s", expectedOut, string(content)) + } +} + +// Test for SetOutput with both .env and .out file extensions +func TestSetOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := SetOutput("outputKey", "outputValue") + if err != nil { + t.Fatalf("SetOutput failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "outputKey=outputValue\n" { + t.Errorf("Expected 'outputKey=outputValue', got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = SetOutput("outputKey", "outputValue") + if err != nil { + t.Fatalf("SetOutput failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "outputKey outputValue\n" { + t.Errorf("Expected 'outputKey outputValue', got: %s", string(content)) + } +} + +// Test for UpdateOutput with both .env and .out file extensions +func TestUpdateOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := os.WriteFile(envFilePath, []byte("outputKey=oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + err = UpdateOutput("outputKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateOutput failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "outputKey=updatedValue\n" { + t.Errorf("Expected 'outputKey=updatedValue', got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = os.WriteFile(outFilePath, []byte("outputKey oldValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + err = UpdateOutput("outputKey", "updatedValue") + if err != nil { + t.Fatalf("UpdateOutput failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "outputKey updatedValue\n" { + t.Errorf("Expected 'outputKey updatedValue', got: %s", string(content)) + } +} + +// Test for DeleteOutput with both .env and .out file extensions +func TestDeleteOutput(t *testing.T) { + // Test for .env file + envFilePath := setupTestFile(t, DroneOutputFile, "env") + err := os.WriteFile(envFilePath, []byte("outputKey=outputValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .env file content: %v", err) + } + err = DeleteOutput("outputKey") + if err != nil { + t.Fatalf("DeleteOutput failed: %v", err) + } + + content, err := os.ReadFile(envFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", envFilePath, err) + } + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) + } + + // Test for .out file + outFilePath := setupTestFile(t, DroneOutputFile, "out") + err = os.WriteFile(outFilePath, []byte("outputKey outputValue\n"), 0644) + if err != nil { + t.Fatalf("Failed to write initial .out file content: %v", err) + } + err = DeleteOutput("outputKey") + if err != nil { + t.Fatalf("DeleteOutput failed: %v", err) + } + + content, err = os.ReadFile(outFilePath) + if err != nil { + t.Fatalf("Failed to read file %s: %v", outFilePath, err) + } + if string(content) != "" { + t.Errorf("Expected file to be empty, got: %s", string(content)) + } +} From ff7bb9a8002e89278a5e4d4ad475cc1c2e289482 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Thu, 5 Dec 2024 11:56:09 +0530 Subject: [PATCH 24/26] Changing functionality of ParseKeyValue and adding tests. --- harness/variables.go | 4 ++-- harness/variables_test.go | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/harness/variables.go b/harness/variables.go index d5e4442..b0f36c2 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -172,8 +172,8 @@ func ParseKeyValue(line, ext string) (string, string) { return strings.TrimSpace(parts[0]), "" } else if ext == ".out" { parts := strings.Fields(line) - if len(parts) == 2 { - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + if len(parts) > 1 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(strings.Join(parts[1:], " ")) } return strings.TrimSpace(parts[0]), "" } diff --git a/harness/variables_test.go b/harness/variables_test.go index 293b734..69a1dff 100644 --- a/harness/variables_test.go +++ b/harness/variables_test.go @@ -307,3 +307,45 @@ func TestDeleteOutput(t *testing.T) { t.Errorf("Expected file to be empty, got: %s", string(content)) } } + +func TestParseKeyValue(t *testing.T) { + tests := []struct { + line string + ext string + expectedKey string + expectedValue string + }{ + // .env cases + {"key=value", ".env", "key", "value"}, + {"key= value", ".env", "key", "value"}, + {"key =value", ".env", "key", "value"}, + {"key = value", ".env", "key", "value"}, + {"key=", ".env", "key", ""}, + {"key", ".env", "key", ""}, + {"key=multi word value", ".env", "key", "multi word value"}, + {"key= spaced value ", ".env", "key", "spaced value"}, + + // .out cases + {"key value", ".out", "key", "value"}, + {"key value", ".out", "key", "value"}, + {" key value ", ".out", "key", "value"}, + {"key ", ".out", "key", ""}, + {"key", ".out", "key", ""}, + {"key multi word value", ".out", "key", "multi word value"}, + {"key spaced value ", ".out", "key", "spaced value"}, + + // Unsupported extension cases + {"key=value", ".unknown", "", ""}, + {"key value", ".unknown", "", ""}, + } + + for _, test := range tests { + t.Run(test.line+"_"+test.ext, func(t *testing.T) { + key, value := ParseKeyValue(test.line, test.ext) + if key != test.expectedKey || value != test.expectedValue { + t.Errorf("For line '%s' and ext '%s': expected ('%s', '%s'), got ('%s', '%s')", + test.line, test.ext, test.expectedKey, test.expectedValue, key, value) + } + }) + } +} From 4f13a99d9b5a13f1bfceba81203ab1dbb3cc7005 Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Thu, 5 Dec 2024 16:51:14 +0530 Subject: [PATCH 25/26] Adding multiline tests. --- harness/variables_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/harness/variables_test.go b/harness/variables_test.go index 69a1dff..a07cc1d 100644 --- a/harness/variables_test.go +++ b/harness/variables_test.go @@ -307,7 +307,6 @@ func TestDeleteOutput(t *testing.T) { t.Errorf("Expected file to be empty, got: %s", string(content)) } } - func TestParseKeyValue(t *testing.T) { tests := []struct { line string @@ -324,6 +323,8 @@ func TestParseKeyValue(t *testing.T) { {"key", ".env", "key", ""}, {"key=multi word value", ".env", "key", "multi word value"}, {"key= spaced value ", ".env", "key", "spaced value"}, + {"key=first line\nsecond line", ".env", "key", "first line\nsecond line"}, + {"key=first line\nsecond line\nthird line", ".env", "key", "first line\nsecond line\nthird line"}, // .out cases {"key value", ".out", "key", "value"}, From ba76946663c89e625ddfa9249bff8eba1e60bc5f Mon Sep 17 00:00:00 2001 From: Devansh Mathur Date: Thu, 5 Dec 2024 20:59:32 +0530 Subject: [PATCH 26/26] Using godotenv for .env files and new tests. --- go.mod | 8 + go.sum | 4 + harness/variables.go | 98 +++++---- harness/variables_test.go | 446 +++++++++++++------------------------- 4 files changed, 228 insertions(+), 328 deletions(-) diff --git a/go.mod b/go.mod index 47c2603..550108f 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,19 @@ go 1.19 require ( github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.7.0 github.com/urfave/cli/v2 v2.23.6 ) +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/harness/godotenv/v3 v3.0.1 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect diff --git a/go.sum b/go.sum index a1e18c3..e4865a2 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/harness/godotenv/v3 v3.0.1 h1:7QPEOkpx6SLLrYRRzPBp50d6c0XIxq721iqoFxbz1Bs= +github.com/harness/godotenv/v3 v3.0.1/go.mod h1:UIXXJtTM7NkSYMYknHYOO2d8BfDlAWMYZRuRsXcDDR0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -19,6 +21,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/harness/variables.go b/harness/variables.go index b0f36c2..dc5e640 100644 --- a/harness/variables.go +++ b/harness/variables.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "strings" + + v3 "github.com/harness/godotenv/v3" ) const ( @@ -79,8 +81,7 @@ func SetErrorMetadata(message, code, category string) error { } // UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file. -func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { - // Get the file path from the environment variable +func UpdateOrRemoveKeyValue(envVar, key, newValue string, deleteKey bool) error { filePath := os.Getenv(envVar) if filePath == "" { return fmt.Errorf("environment variable %s is not set", envVar) @@ -88,45 +89,72 @@ func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { // Ensure the file exists before reading if _, err := os.Stat(filePath); os.IsNotExist(err) { - // Create the file if it does not exist _, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to create file: %w", err) } } - // Determine the file extension to handle formats + // Trim trailing newline characters from newValue + newValue = strings.TrimRight(newValue, "\n") + ext := strings.ToLower(filepath.Ext(filePath)) - // Read the file contents into memory - lines, err := ReadLines(filePath) - if err != nil { - return fmt.Errorf("failed to read file: %w", err) - } + if ext == ".env" { + // Use godotenv for .env files + data, err := v3.Read(filePath) + if err != nil { + return fmt.Errorf("failed to parse .env file: %w", err) + } - // Process lines - var updatedLines []string - found := false - for _, line := range lines { - k, v := ParseKeyValue(line, ext) - if k == key { - found = true - if delete { - continue // Skip the line to delete it - } - updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) + if deleteKey { + delete(data, key) } else { - updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) + data[key] = newValue } - } - // Append new key-value if not found and not deleting - if !found && !delete { - updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) + err = v3.Write(data, filePath) + if err != nil { + return fmt.Errorf("failed to write .env file: %w", err) + } + } else { + // For .out files, process manually + // For .out files, check for multiline values + if strings.Contains(newValue, "\n") { + return fmt.Errorf("multiline values are not allowed for key %s in .out file", key) + } + + lines, err := ReadLines(filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + var updatedLines []string + found := false + for _, line := range lines { + k, v := ParseKeyValue(line, ext) + if k == key { + found = true + if deleteKey { + continue + } + updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) + } else { + updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) + } + } + + if !found && !deleteKey { + updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) + } + + err = WriteLines(filePath, updatedLines) + if err != nil { + return fmt.Errorf("failed to write file: %w", err) + } } - // Write updated lines back to the file - return WriteLines(filePath, updatedLines) + return nil } // ReadLines reads lines from a file and returns them as a slice of strings. @@ -164,28 +192,22 @@ func WriteLines(filename string, lines []string) error { // ParseKeyValue parses a key-value pair from a string and returns the key and value. func ParseKeyValue(line, ext string) (string, string) { - if ext == ".env" { - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) - } - return strings.TrimSpace(parts[0]), "" - } else if ext == ".out" { + if ext == ".out" { parts := strings.Fields(line) if len(parts) > 1 { return strings.TrimSpace(parts[0]), strings.TrimSpace(strings.Join(parts[1:], " ")) } return strings.TrimSpace(parts[0]), "" } + // .env is handled by godotenv, so this is not used for .env files return "", "" } -// FormatKeyValue formats a key-value pair into a string. +// FormatKeyValue handles formatting for .env and .out files. func FormatKeyValue(key, value, ext string) string { - if ext == ".env" { - return fmt.Sprintf("%s=%s", key, value) - } else if ext == ".out" { + if ext == ".out" { return fmt.Sprintf("%s %s", key, value) } + // For .env files, use godotenv directly; this function won't apply return "" } diff --git a/harness/variables_test.go b/harness/variables_test.go index a07cc1d..a6be218 100644 --- a/harness/variables_test.go +++ b/harness/variables_test.go @@ -2,351 +2,217 @@ package harness import ( "os" - "path/filepath" "testing" -) - -// Helper function to create a temporary file with a given extension and set the environment variable -func setupTestFile(t *testing.T, envVar, extension string) string { - t.Helper() - tmpDir := t.TempDir() - tmpFile := filepath.Join(tmpDir, "test."+extension) - err := os.WriteFile(tmpFile, []byte(""), 0644) - if err != nil { - t.Fatalf("Failed to create temporary file: %v", err) - } - os.Setenv(envVar, tmpFile) - return tmpFile -} - -// Test for SetSecret with both .env and .out file extensions -func TestSetSecret(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") - err := SetSecret("testKey", "testValue") - if err != nil { - t.Fatalf("SetSecret failed: %v", err) - } + v3 "github.com/harness/godotenv/v3" + "github.com/stretchr/testify/assert" +) - content, err := os.ReadFile(envFilePath) +// Helper function to create the .env file with the given content +func createEnvFile(t *testing.T, filePath string, content map[string]string) error { + file, err := os.Create(filePath) if err != nil { - t.Fatalf("Failed to read .env file: %v", err) + t.Fatalf("Failed to create file: %v", err) + return err } + defer file.Close() - if string(content) != "testKey=testValue\n" { - t.Errorf("Expected 'testKey=testValue', got: %s", string(content)) + for key, value := range content { + _, err := file.WriteString(key + "=" + value + "\n") + if err != nil { + t.Fatalf("Failed to write to file: %v", err) + return err + } } - // Test for .out file - outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") - - err = SetSecret("testKey", "testValue") - if err != nil { - t.Fatalf("SetSecret failed: %v", err) - } + return nil +} - content, err = os.ReadFile(outFilePath) +// Helper function to create an output file (.out) with the given content +func createOutFile(t *testing.T, filePath string, content []string) error { + file, err := os.Create(filePath) if err != nil { - t.Fatalf("Failed to read .out file: %v", err) + t.Fatalf("Failed to create file: %v", err) + return err } + defer file.Close() - if string(content) != "testKey testValue\n" { - t.Errorf("Expected 'testKey testValue', got: %s", string(content)) + // Write the content to the .out file manually + for _, line := range content { + _, err := file.WriteString(line + "\n") + if err != nil { + t.Fatalf("Failed to write to file: %v", err) + return err + } } + return nil } -// Test for UpdateSecret with both .env and .out file extensions -func TestUpdateSecret(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") - - // Write initial content - err := os.WriteFile(envFilePath, []byte("testKey=oldValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .env file content: %v", err) - } +// Test Set, Update, and Delete functions for .env files +func TestSetUpdateDeleteEnvFile(t *testing.T) { + // Set the environment variable to point to the test.env file + envFilePath := "test.env" + os.Setenv("HARNESS_OUTPUT_SECRET_FILE", envFilePath) - err = UpdateSecret("testKey", "updatedValue") - if err != nil { - t.Fatalf("UpdateSecret failed: %v", err) + // Setup: Create the .env file at the path specified by HARNESS_OUTPUT_SECRET_FILE + envContent := map[string]string{ + "KEY1": "value1", + "KEY2": "value2", } + err := createEnvFile(t, envFilePath, envContent) + assert.NoError(t, err) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read .env file: %v", err) - } + // Defer file removal after the test completes + defer os.Remove(envFilePath) - if string(content) != "testKey=updatedValue\n" { - t.Errorf("Expected 'testKey=updatedValue', got: %s", string(content)) - } + // Set a new key-value pair + err = SetSecret("KEY3", "value3") + assert.NoError(t, err) - // Test for .out file - outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + // Update an existing key-value pair + err = UpdateSecret("KEY1", "new_value1") + assert.NoError(t, err) - // Write initial content - err = os.WriteFile(outFilePath, []byte("testKey oldValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .out file content: %v", err) - } + // Delete an existing key + err = DeleteSecret("KEY2") + assert.NoError(t, err) - err = UpdateSecret("testKey", "updatedValue") - if err != nil { - t.Fatalf("UpdateSecret failed: %v", err) - } + // Read the file content and verify the changes + data, err := v3.Read(envFilePath) + assert.NoError(t, err) - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read .out file: %v", err) - } + // Assertions + assert.Equal(t, "new_value1", data["KEY1"]) + assert.Equal(t, "value3", data["KEY3"]) + assert.NotContains(t, data, "KEY2") - if string(content) != "testKey updatedValue\n" { - t.Errorf("Expected 'testKey updatedValue', got: %s", string(content)) - } + // Clean up + defer os.Unsetenv("HARNESS_OUTPUT_SECRET_FILE") } -// Test for DeleteSecret with both .env and .out file extensions -func TestDeleteSecret(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, HarnessOutputSecretFile, "env") - - // Write initial content - err := os.WriteFile(envFilePath, []byte("testKey=testValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .env file content: %v", err) - } +// Test Set, Update, and Delete functions for .out files (single-line values) +func TestSetUpdateDeleteOutFile(t *testing.T) { + // Set the environment variable to point to the test.out file + outFilePath := "test.out" + os.Setenv("DRONE_OUTPUT", outFilePath) - err = DeleteSecret("testKey") - if err != nil { - t.Fatalf("DeleteSecret failed: %v", err) + // Setup: Create the .out file at the path specified by DRONE_OUTPUT + outContent := []string{ + "KEY1 value1", + "KEY2 value2", } + err := createOutFile(t, outFilePath, outContent) + assert.NoError(t, err) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read .env file: %v", err) - } + // Defer file removal after the test completes + defer os.Remove(outFilePath) - if string(content) != "" { - t.Errorf("Expected .env file to be empty, got: %s", string(content)) - } + // Set a new key-value pair + err = SetOutput("KEY3", "value3") + assert.NoError(t, err) - // Test for .out file - outFilePath := setupTestFile(t, HarnessOutputSecretFile, "out") + // Update an existing key-value pair + err = UpdateOutput("KEY1", "new_value1") + assert.NoError(t, err) - // Write initial content - err = os.WriteFile(outFilePath, []byte("testKey testValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .out file content: %v", err) - } + // Delete an existing key + err = DeleteOutput("KEY2") + assert.NoError(t, err) - err = DeleteSecret("testKey") - if err != nil { - t.Fatalf("DeleteSecret failed: %v", err) - } + // Verify changes + lines, err := ReadLines(outFilePath) + assert.NoError(t, err) - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read .out file: %v", err) - } + // Assertions + assert.Contains(t, lines, "KEY1 new_value1") + assert.Contains(t, lines, "KEY3 value3") + assert.NotContains(t, lines, "KEY2") - if string(content) != "" { - t.Errorf("Expected .out file to be empty, got: %s", string(content)) - } + // Clean up + defer os.Unsetenv("DRONE_OUTPUT") } -// Test for SetErrorMetadata with both .env and .out file extensions -func TestSetErrorMetadata(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, MetadataFile, "env") +// Test Set, Update, and Delete functions for multiline values in .env file +func TestSetUpdateDeleteMultilineEnvFile(t *testing.T) { + // Set the environment variable to point to the test.env file + envFilePath := "test.env" + os.Setenv("HARNESS_OUTPUT_SECRET_FILE", envFilePath) - err := SetErrorMetadata("errorMessage", "errorCode", "errorCategory") - if err != nil { - t.Fatalf("SetErrorMetadata failed: %v", err) - } + // Setup: Create a temporary .env file with multiline value for a key + envContent := map[string]string{} + err := createEnvFile(t, envFilePath, envContent) + assert.NoError(t, err) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read .env file: %v", err) - } + // Defer file removal after the test completes + defer os.Remove(envFilePath) - expected := "ERROR_MESSAGE=errorMessage\nERROR_CODE=errorCode\nERROR_CATEGORY=errorCategory\n" - if string(content) != expected { - t.Errorf("Expected '%s', got: %s", expected, string(content)) - } + // Set a new multiline value + err = SetSecret("KEY1", "line1\nline2\nline3") + assert.NoError(t, err) - // Test for .out file - outFilePath := setupTestFile(t, MetadataFile, "out") + // Verify the multiline update + data, err := v3.Read(envFilePath) + assert.NoError(t, err) + assert.Equal(t, "line1\nline2\nline3", data["KEY1"]) - err = SetErrorMetadata("errorMessage", "errorCode", "errorCategory") - if err != nil { - t.Fatalf("SetErrorMetadata failed: %v", err) - } + err = UpdateSecret("KEY1", "line4\nline5\nline6") + assert.NoError(t, err) - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read .out file: %v", err) - } - - expectedOut := "ERROR_MESSAGE errorMessage\nERROR_CODE errorCode\nERROR_CATEGORY errorCategory\n" - if string(content) != expectedOut { - t.Errorf("Expected '%s', got: %s", expectedOut, string(content)) - } -} - -// Test for SetOutput with both .env and .out file extensions -func TestSetOutput(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, DroneOutputFile, "env") - err := SetOutput("outputKey", "outputValue") - if err != nil { - t.Fatalf("SetOutput failed: %v", err) - } + // Verify the multiline update + data, err = v3.Read(envFilePath) + assert.NoError(t, err) + assert.Equal(t, "line4\nline5\nline6", data["KEY1"]) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", envFilePath, err) - } - if string(content) != "outputKey=outputValue\n" { - t.Errorf("Expected 'outputKey=outputValue', got: %s", string(content)) - } + // Delete the key + err = DeleteSecret("KEY1") + assert.NoError(t, err) - // Test for .out file - outFilePath := setupTestFile(t, DroneOutputFile, "out") - err = SetOutput("outputKey", "outputValue") - if err != nil { - t.Fatalf("SetOutput failed: %v", err) - } + // Verify deletion + data, err = v3.Read(envFilePath) + assert.NoError(t, err) + assert.NotContains(t, data, "KEY1") - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", outFilePath, err) - } - if string(content) != "outputKey outputValue\n" { - t.Errorf("Expected 'outputKey outputValue', got: %s", string(content)) - } + // Clean up + defer os.Unsetenv("HARNESS_OUTPUT_SECRET_FILE") } -// Test for UpdateOutput with both .env and .out file extensions -func TestUpdateOutput(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, DroneOutputFile, "env") - err := os.WriteFile(envFilePath, []byte("outputKey=oldValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .env file content: %v", err) - } - err = UpdateOutput("outputKey", "updatedValue") - if err != nil { - t.Fatalf("UpdateOutput failed: %v", err) - } +// Test invalid cases for setting a secret in .out (should not allow multiline) +func TestSetInvalidMultilineOut(t *testing.T) { + // Set the environment variable to point to the test.out file + outFilePath := "test.out" + os.Setenv("DRONE_OUTPUT", outFilePath) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", envFilePath, err) - } - if string(content) != "outputKey=updatedValue\n" { - t.Errorf("Expected 'outputKey=updatedValue', got: %s", string(content)) + // Setup: Create a temporary .out file + outContent := []string{ + "KEY1 value1", + "KEY2 value2", } + err := createOutFile(t, outFilePath, outContent) + assert.NoError(t, err) - // Test for .out file - outFilePath := setupTestFile(t, DroneOutputFile, "out") - err = os.WriteFile(outFilePath, []byte("outputKey oldValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .out file content: %v", err) - } - err = UpdateOutput("outputKey", "updatedValue") - if err != nil { - t.Fatalf("UpdateOutput failed: %v", err) - } + // Defer file removal after the test completes + defer os.Remove(outFilePath) - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", outFilePath, err) - } - if string(content) != "outputKey updatedValue\n" { - t.Errorf("Expected 'outputKey updatedValue', got: %s", string(content)) - } -} + // Attempt to set a multiline value (should fail) + err = SetOutput("KEY3", "line1\nline2") + assert.Error(t, err) -// Test for DeleteOutput with both .env and .out file extensions -func TestDeleteOutput(t *testing.T) { - // Test for .env file - envFilePath := setupTestFile(t, DroneOutputFile, "env") - err := os.WriteFile(envFilePath, []byte("outputKey=outputValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .env file content: %v", err) - } - err = DeleteOutput("outputKey") - if err != nil { - t.Fatalf("DeleteOutput failed: %v", err) - } + // Verify the content did not change + lines, err := ReadLines(outFilePath) + assert.NoError(t, err) - content, err := os.ReadFile(envFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", envFilePath, err) - } - if string(content) != "" { - t.Errorf("Expected file to be empty, got: %s", string(content)) - } - - // Test for .out file - outFilePath := setupTestFile(t, DroneOutputFile, "out") - err = os.WriteFile(outFilePath, []byte("outputKey outputValue\n"), 0644) - if err != nil { - t.Fatalf("Failed to write initial .out file content: %v", err) - } - err = DeleteOutput("outputKey") - if err != nil { - t.Fatalf("DeleteOutput failed: %v", err) - } + // Assertions + assert.Contains(t, lines, "KEY1 value1") + assert.Contains(t, lines, "KEY2 value2") - content, err = os.ReadFile(outFilePath) - if err != nil { - t.Fatalf("Failed to read file %s: %v", outFilePath, err) - } - if string(content) != "" { - t.Errorf("Expected file to be empty, got: %s", string(content)) - } + // Clean up + defer os.Unsetenv("DRONE_OUTPUT") } -func TestParseKeyValue(t *testing.T) { - tests := []struct { - line string - ext string - expectedKey string - expectedValue string - }{ - // .env cases - {"key=value", ".env", "key", "value"}, - {"key= value", ".env", "key", "value"}, - {"key =value", ".env", "key", "value"}, - {"key = value", ".env", "key", "value"}, - {"key=", ".env", "key", ""}, - {"key", ".env", "key", ""}, - {"key=multi word value", ".env", "key", "multi word value"}, - {"key= spaced value ", ".env", "key", "spaced value"}, - {"key=first line\nsecond line", ".env", "key", "first line\nsecond line"}, - {"key=first line\nsecond line\nthird line", ".env", "key", "first line\nsecond line\nthird line"}, - - // .out cases - {"key value", ".out", "key", "value"}, - {"key value", ".out", "key", "value"}, - {" key value ", ".out", "key", "value"}, - {"key ", ".out", "key", ""}, - {"key", ".out", "key", ""}, - {"key multi word value", ".out", "key", "multi word value"}, - {"key spaced value ", ".out", "key", "spaced value"}, - - // Unsupported extension cases - {"key=value", ".unknown", "", ""}, - {"key value", ".unknown", "", ""}, - } - for _, test := range tests { - t.Run(test.line+"_"+test.ext, func(t *testing.T) { - key, value := ParseKeyValue(test.line, test.ext) - if key != test.expectedKey || value != test.expectedValue { - t.Errorf("For line '%s' and ext '%s': expected ('%s', '%s'), got ('%s', '%s')", - test.line, test.ext, test.expectedKey, test.expectedValue, key, value) - } - }) - } +// Test that the functions return the appropriate error when the file does not exist +func TestFileNotExistError(t *testing.T) { + // Attempt to set a value when the .env file is missing + err := SetSecret("KEY1", "value1") + assert.Error(t, err) + assert.Contains(t, err.Error(), "environment variable HARNESS_OUTPUT_SECRET_FILE is not set") }