Skip to content

Commit 3341f83

Browse files
authored
Merge pull request #43 from DevanshMathur19/CI-14828
feat: [CI-14828] : Added Error and Output Secret Handling.
2 parents ad9dad0 + ba76946 commit 3341f83

File tree

4 files changed

+443
-4
lines changed

4 files changed

+443
-4
lines changed

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@ go 1.19
44

55
require (
66
github.com/sirupsen/logrus v1.9.0
7+
github.com/stretchr/testify v1.7.0
78
github.com/urfave/cli/v2 v2.23.6
89
)
910

11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
14+
gopkg.in/yaml.v3 v3.0.1 // indirect
15+
)
16+
1017
require (
1118
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
19+
github.com/harness/godotenv/v3 v3.0.1
1220
github.com/russross/blackfriday/v2 v2.1.0 // indirect
1321
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
1422
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
33
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/harness/godotenv/v3 v3.0.1 h1:7QPEOkpx6SLLrYRRzPBp50d6c0XIxq721iqoFxbz1Bs=
7+
github.com/harness/godotenv/v3 v3.0.1/go.mod h1:UIXXJtTM7NkSYMYknHYOO2d8BfDlAWMYZRuRsXcDDR0=
68
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
79
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
810
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -12,17 +14,15 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
1214
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1315
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
1416
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15-
github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE=
16-
github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
17-
github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
18-
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
1917
github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4=
2018
github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
2119
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
2220
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
2321
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2422
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
2523
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2625
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2726
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2827
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
28+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

harness/variables.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package harness
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
v3 "github.com/harness/godotenv/v3"
11+
)
12+
13+
const (
14+
// ErrorMessageKey is the key used to retrieve or store the error message content.
15+
ErrorMessageKey = "ERROR_MESSAGE"
16+
17+
// ErrorCodeKey is the key used to identify the specific error code associated with an error.
18+
ErrorCodeKey = "ERROR_CODE"
19+
20+
// ErrorCategoryKey is the key used to classify the category of the error, which can help in grouping similar types of errors.
21+
ErrorCategoryKey = "ERROR_CATEGORY"
22+
23+
// MetadataFile is the key for the file that stores metadata associated with an error, such as details about the error's source or context.
24+
MetadataFile = "ERROR_METADATA_FILE"
25+
26+
// DroneOutputFile is the key for the file where outputs can be exported and utilized in the subsequent steps in Harness CI pipeline.
27+
DroneOutputFile = "DRONE_OUTPUT"
28+
29+
// HarnessOutputSecretFile is the key for the file where secrets can be exported and utilized in the subsequent steps in Harness CI pipeline.
30+
HarnessOutputSecretFile = "HARNESS_OUTPUT_SECRET_FILE"
31+
)
32+
33+
// SetSecret sets a new secret by adding it to the HARNESS_OUTPUT_SECRET_FILE file
34+
func SetSecret(name, value string) error {
35+
return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false)
36+
}
37+
38+
// UpdateSecret overwrites the value of an existing secret.
39+
func UpdateSecret(name, value string) error {
40+
return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, value, false)
41+
}
42+
43+
// DeleteSecret removes a secret from the file entirely.
44+
func DeleteSecret(name string) error {
45+
return UpdateOrRemoveKeyValue(HarnessOutputSecretFile, name, "", true)
46+
}
47+
48+
// SetOutput sets a new secret by adding it to the DRONE_OUTPUT file
49+
func SetOutput(name, value string) error {
50+
return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false)
51+
}
52+
53+
// UpdateOutput overwrites the value of an existing output.
54+
func UpdateOutput(name, value string) error {
55+
return UpdateOrRemoveKeyValue(DroneOutputFile, name, value, false)
56+
}
57+
58+
// DeleteOutput removes an output from the file entirely.
59+
func DeleteOutput(name string) error {
60+
return UpdateOrRemoveKeyValue(DroneOutputFile, name, "", true)
61+
}
62+
63+
// SetErrorMetadata sets the error message, error code, and error category, writing them to the CI_ERROR_METADATA file
64+
func SetErrorMetadata(message, code, category string) error {
65+
// Write the error message
66+
if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorMessageKey, message, false); err != nil {
67+
return err
68+
}
69+
70+
// Write the error code
71+
if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCodeKey, code, false); err != nil {
72+
return err
73+
}
74+
75+
// Write the error category
76+
if err := UpdateOrRemoveKeyValue(MetadataFile, ErrorCategoryKey, category, false); err != nil {
77+
return err
78+
}
79+
80+
return nil
81+
}
82+
83+
// UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file.
84+
func UpdateOrRemoveKeyValue(envVar, key, newValue string, deleteKey bool) error {
85+
filePath := os.Getenv(envVar)
86+
if filePath == "" {
87+
return fmt.Errorf("environment variable %s is not set", envVar)
88+
}
89+
90+
// Ensure the file exists before reading
91+
if _, err := os.Stat(filePath); os.IsNotExist(err) {
92+
_, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
93+
if err != nil {
94+
return fmt.Errorf("failed to create file: %w", err)
95+
}
96+
}
97+
98+
// Trim trailing newline characters from newValue
99+
newValue = strings.TrimRight(newValue, "\n")
100+
101+
ext := strings.ToLower(filepath.Ext(filePath))
102+
103+
if ext == ".env" {
104+
// Use godotenv for .env files
105+
data, err := v3.Read(filePath)
106+
if err != nil {
107+
return fmt.Errorf("failed to parse .env file: %w", err)
108+
}
109+
110+
if deleteKey {
111+
delete(data, key)
112+
} else {
113+
data[key] = newValue
114+
}
115+
116+
err = v3.Write(data, filePath)
117+
if err != nil {
118+
return fmt.Errorf("failed to write .env file: %w", err)
119+
}
120+
} else {
121+
// For .out files, process manually
122+
// For .out files, check for multiline values
123+
if strings.Contains(newValue, "\n") {
124+
return fmt.Errorf("multiline values are not allowed for key %s in .out file", key)
125+
}
126+
127+
lines, err := ReadLines(filePath)
128+
if err != nil {
129+
return fmt.Errorf("failed to read file: %w", err)
130+
}
131+
132+
var updatedLines []string
133+
found := false
134+
for _, line := range lines {
135+
k, v := ParseKeyValue(line, ext)
136+
if k == key {
137+
found = true
138+
if deleteKey {
139+
continue
140+
}
141+
updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext))
142+
} else {
143+
updatedLines = append(updatedLines, FormatKeyValue(k, v, ext))
144+
}
145+
}
146+
147+
if !found && !deleteKey {
148+
updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext))
149+
}
150+
151+
err = WriteLines(filePath, updatedLines)
152+
if err != nil {
153+
return fmt.Errorf("failed to write file: %w", err)
154+
}
155+
}
156+
157+
return nil
158+
}
159+
160+
// ReadLines reads lines from a file and returns them as a slice of strings.
161+
func ReadLines(filename string) ([]string, error) {
162+
file, err := os.Open(filename)
163+
if err != nil {
164+
return nil, err
165+
}
166+
defer file.Close()
167+
168+
var lines []string
169+
scanner := bufio.NewScanner(file)
170+
for scanner.Scan() {
171+
lines = append(lines, scanner.Text())
172+
}
173+
return lines, scanner.Err()
174+
}
175+
176+
// WriteLines writes a slice of strings to a file, each string being written to a new line.
177+
func WriteLines(filename string, lines []string) error {
178+
file, err := os.Create(filename)
179+
if err != nil {
180+
return fmt.Errorf("failed to create file: %w", err)
181+
}
182+
defer file.Close()
183+
184+
for _, line := range lines {
185+
_, err := file.WriteString(line + "\n")
186+
if err != nil {
187+
return fmt.Errorf("failed to write to file: %w", err)
188+
}
189+
}
190+
return nil
191+
}
192+
193+
// ParseKeyValue parses a key-value pair from a string and returns the key and value.
194+
func ParseKeyValue(line, ext string) (string, string) {
195+
if ext == ".out" {
196+
parts := strings.Fields(line)
197+
if len(parts) > 1 {
198+
return strings.TrimSpace(parts[0]), strings.TrimSpace(strings.Join(parts[1:], " "))
199+
}
200+
return strings.TrimSpace(parts[0]), ""
201+
}
202+
// .env is handled by godotenv, so this is not used for .env files
203+
return "", ""
204+
}
205+
206+
// FormatKeyValue handles formatting for .env and .out files.
207+
func FormatKeyValue(key, value, ext string) string {
208+
if ext == ".out" {
209+
return fmt.Sprintf("%s %s", key, value)
210+
}
211+
// For .env files, use godotenv directly; this function won't apply
212+
return ""
213+
}

0 commit comments

Comments
 (0)