Skip to content

Commit de9c51e

Browse files
Gargi-thakur01Harness
authored andcommitted
feat: [CI-17888]: Command logging for Harness Docker Runner (#54)
* 466fdd Remove unnecessary imports from run.go * f8d922 Added Unit tests and updated command logging implementation * 0df289 Format runtime files for command logging implementation
1 parent 845c1b5 commit de9c51e

File tree

5 files changed

+193
-1
lines changed

5 files changed

+193
-1
lines changed

pipeline/runtime/common.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,40 @@ func IsFeatureFlagEnabled(featureFlagName string, engine *engine.Engine, step *s
213213
return ok && val == trueValue
214214
}
215215

216+
// printCommand logs the command being executed to the output
217+
func printCommand(step *spec.Step, output io.Writer) {
218+
219+
if step == nil {
220+
return
221+
}
222+
223+
var actualCommand string
224+
225+
if len(step.Command) > 0 {
226+
actualCommand = step.Command[0]
227+
} else if len(step.Entrypoint) > 2 && step.Entrypoint[0] == "sh" && step.Entrypoint[1] == "-c" {
228+
actualCommand = step.Entrypoint[2]
229+
} else if len(step.Entrypoint) > 0 {
230+
actualCommand = strings.Join(step.Entrypoint, " ")
231+
}
232+
233+
if actualCommand != "" {
234+
235+
boldYellow := "\033[1;33m" // ANSI bold yellow
236+
reset := "\033[0m" // ANSI reset
237+
238+
header := fmt.Sprintf("%sExecuting the following command(s):%s\n", boldYellow, reset)
239+
output.Write([]byte(header))
240+
241+
lines := strings.Split(actualCommand, "\n")
242+
for _, line := range lines {
243+
// Format each line with bold yellow and reset
244+
formattedLine := fmt.Sprintf("%s%s%s\n", boldYellow, line, reset)
245+
output.Write([]byte(formattedLine))
246+
}
247+
}
248+
}
249+
216250
// checkStepSuccess checks if the step was successful based on the return values
217251
func checkStepSuccess(state *runtime.State, err error) bool {
218252
if err == nil && state != nil && state.ExitCode == 0 && state.Exited {

pipeline/runtime/common_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,161 @@
11
package runtime
22

33
import (
4+
"bytes"
45
"os"
6+
"regexp"
57
"testing"
68

9+
"github.com/harness/harness-docker-runner/engine/spec"
10+
"github.com/harness/harness-docker-runner/logstream"
711
"github.com/stretchr/testify/assert"
812
)
913

14+
// Helper function to strip ANSI color codes
15+
func stripAnsiCodes(s string) string {
16+
ansi := regexp.MustCompile("\033\\[[0-9;]*m")
17+
return ansi.ReplaceAllString(s, "")
18+
}
19+
20+
// testWriter implements the logstream.Writer interface for testing
21+
type testWriter struct {
22+
buf *bytes.Buffer
23+
}
24+
25+
func (w *testWriter) Write(p []byte) (n int, err error) {
26+
return w.buf.Write(p)
27+
}
28+
29+
func (w *testWriter) Open() error {
30+
return nil
31+
}
32+
33+
func (w *testWriter) Close() error {
34+
return nil
35+
}
36+
37+
func (w *testWriter) Start() {
38+
39+
}
40+
41+
func (w *testWriter) Error() error {
42+
return nil
43+
}
44+
45+
func TestPrintCommand(t *testing.T) {
46+
tests := []struct {
47+
name string
48+
step *spec.Step
49+
expectedOutput []string
50+
}{
51+
{
52+
name: "command from Command field",
53+
step: &spec.Step{
54+
Name: "test-step",
55+
Command: []string{"echo 'hello world'"},
56+
},
57+
expectedOutput: []string{
58+
"Executing the following command(s):",
59+
"echo 'hello world'",
60+
},
61+
},
62+
{
63+
name: "command from sh -c entrypoint",
64+
step: &spec.Step{
65+
Name: "test-step",
66+
Entrypoint: []string{"sh", "-c", "echo 'hello'; echo 'world'"},
67+
},
68+
expectedOutput: []string{
69+
"Executing the following command(s):",
70+
"echo 'hello'; echo 'world'",
71+
},
72+
},
73+
{
74+
name: "command from regular entrypoint",
75+
step: &spec.Step{
76+
Name: "test-step",
77+
Entrypoint: []string{"node", "script.js", "--verbose"},
78+
},
79+
expectedOutput: []string{
80+
"Executing the following command(s):",
81+
"node script.js --verbose",
82+
},
83+
},
84+
{
85+
name: "multi-line command",
86+
step: &spec.Step{
87+
Name: "test-step",
88+
Command: []string{"echo 'Line 1'\necho 'Line 2'\necho 'Line 3'"},
89+
},
90+
expectedOutput: []string{
91+
"Executing the following command(s):",
92+
"echo 'Line 1'",
93+
"echo 'Line 2'",
94+
"echo 'Line 3'",
95+
},
96+
},
97+
{
98+
name: "nil step",
99+
step: nil,
100+
expectedOutput: []string{},
101+
},
102+
{
103+
name: "empty command and entrypoint",
104+
step: &spec.Step{
105+
Name: "test-step",
106+
Command: []string{},
107+
Entrypoint: []string{},
108+
},
109+
expectedOutput: []string{},
110+
},
111+
}
112+
113+
for _, tc := range tests {
114+
t.Run(tc.name, func(t *testing.T) {
115+
buf := new(bytes.Buffer)
116+
printCommand(tc.step, buf)
117+
118+
output := buf.String()
119+
120+
// Strip ANSI color codes for easier comparison
121+
output = stripAnsiCodes(output)
122+
123+
for _, expected := range tc.expectedOutput {
124+
assert.Contains(t, output, expected, "Expected output to contain %q", expected)
125+
}
126+
127+
if len(tc.expectedOutput) == 0 {
128+
assert.Empty(t, output, "Expected no output for %s", tc.name)
129+
}
130+
})
131+
}
132+
}
133+
134+
func TestPrintCommandWithSecrets(t *testing.T) {
135+
// Setup step with secret in command
136+
step := &spec.Step{
137+
Name: "secret-step",
138+
Command: []string{"curl -H 'Authorization: Bearer abcdeedcba' https://api.example.com"},
139+
}
140+
141+
rawBuf := new(bytes.Buffer)
142+
secrets := []string{"abcdeedcba"}
143+
replacerWriter := logstream.NewReplacer(
144+
&testWriter{buf: rawBuf},
145+
secrets,
146+
)
147+
148+
printCommand(step, replacerWriter)
149+
150+
output := stripAnsiCodes(rawBuf.String())
151+
152+
assert.Contains(t, output, "Executing the following command(s):")
153+
154+
assert.Contains(t, output, "curl -H 'Authorization: Bearer **************' https://api.example.com")
155+
156+
assert.NotContains(t, output, "abcdeedcba")
157+
}
158+
10159
func TestFetchExportedVars(t *testing.T) {
11160
tests := []struct {
12161
Name string

pipeline/runtime/run.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ func executeRunStep(ctx context.Context, engine *engine.Engine, r *api.StartStep
6363
log := logrus.New()
6464
log.Out = out
6565

66-
logrus.WithField("step_id", r.ID).WithField("stage_id", r.StageRuntimeID).Traceln("starting step run")
66+
logrus.WithField("step_id", r.ID).WithField("stage_id", r.StageRuntimeID).Infoln("starting step run")
67+
68+
// Log the command being executed
69+
printCommand(step, out)
6770

6871
artifactFile := fmt.Sprintf("%s/%s-artifact", pipeline.SharedVolPath, step.ID)
6972
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile

pipeline/runtime/runtest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ func executeRunTestStep(ctx context.Context, engine *engine.Engine, r *api.Start
6565
artifactFile := fmt.Sprintf("%s/%s-artifact", pipeline.SharedVolPath, step.ID)
6666
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile
6767

68+
// Log the command being executed
69+
printCommand(step, out)
70+
6871
exited, err := engine.Run(ctx, step, out)
6972
timeTakenMs := time.Since(start).Milliseconds()
7073
if _, rerr := report.ParseAndUploadTests(ctx, r.TestReport, r.WorkingDir, step.Name, log, time.Now(), tiConfig, &telemetry.TestIntelligenceMetaData, r.Envs); rerr != nil {

pipeline/runtime/runtestsV2.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ func executeRunTestsV2Step(ctx context.Context, engine *engine.Engine, r *api.St
8080
artifactFile := fmt.Sprintf("%s/%s-artifact", pipeline.SharedVolPath, step.ID)
8181
step.Envs["PLUGIN_ARTIFACT_FILE"] = artifactFile
8282

83+
// Log the command being executed
84+
printCommand(step, out)
85+
8386
exited, err := engine.Run(ctx, step, out)
8487
timeTakenMs := time.Since(start).Milliseconds()
8588
logrus.WithField("step_id", r.ID).WithField("stage_id", r.StageRuntimeID).Traceln("completed step runtestv2")

0 commit comments

Comments
 (0)