Fix/#1082 sanitize log filenames (#1086)
This commit is contained in:
commit
ea329c1cbe
|
|
@ -34,11 +34,11 @@ tar -xzf OliveTin-darwin-arm64.tar.gz
|
||||||
cd OliveTin-darwin-arm64
|
cd OliveTin-darwin-arm64
|
||||||
----
|
----
|
||||||
|
|
||||||
== Remove the Gatekeeper quarantine
|
== Gatekeeper and notarization
|
||||||
|
|
||||||
The binary is downloaded from the internet and is not notarized by Apple, so on first run Gatekeeper blocks it with a message like _"OliveTin can't be opened because Apple cannot check it for malicious software."_
|
Current release binaries are **Developer ID signed and notarized** by Apple. After extract, you should be able to run `./OliveTin` normally.
|
||||||
|
|
||||||
Clear the quarantine attribute so it will run:
|
If Gatekeeper still blocks an older (unsigned) build, or you see a prompt that Apple cannot check the binary for malicious software, clear the quarantine attribute:
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
This option installs OliveTin as a launchd service, so it runs in the background and starts automatically. This is the macOS equivalent of running OliveTin as a Linux systemd service or a xref:install/windows_service.adoc[Windows service]. If you just want to run OliveTin as a regular application, follow the xref:install/macos.adoc[macOS install] instructions instead.
|
This option installs OliveTin as a launchd service, so it runs in the background and starts automatically. This is the macOS equivalent of running OliveTin as a Linux systemd service or a xref:install/windows_service.adoc[Windows service]. If you just want to run OliveTin as a regular application, follow the xref:install/macos.adoc[macOS install] instructions instead.
|
||||||
|
|
||||||
Before continuing, complete the xref:install/macos.adoc[macOS install] steps (download, extract, and clear the Gatekeeper quarantine) and confirm OliveTin starts correctly by running `./OliveTin`.
|
Before continuing, complete the xref:install/macos.adoc[macOS install] steps (download, extract, and confirm Gatekeeper allows the binary) and confirm OliveTin starts correctly by running `./OliveTin`.
|
||||||
|
|
||||||
== Choose LaunchAgent or LaunchDaemon
|
== Choose LaunchAgent or LaunchDaemon
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ actions:
|
||||||
|
|
||||||
From the above example, you can see there There are two types of logs - **results (.yaml)** and **output (.log)**
|
From the above example, you can see there There are two types of logs - **results (.yaml)** and **output (.log)**
|
||||||
|
|
||||||
|
Filenames are built from the action title, a unix timestamp, and the execution tracking ID. Characters that are unsafe in filenames (for example `/` and `\`) are replaced with `_` so titles like `Create/update Monthly Report` still write a single file. The original title is unchanged inside the results YAML.
|
||||||
|
|
||||||
* **Results (.yaml)** - this captures almost everything that OliveTin knows about the action and looks like this.
|
* **Results (.yaml)** - this captures almost everything that OliveTin knows about the action and looks like this.
|
||||||
+
|
+
|
||||||
[source,yaml]
|
[source,yaml]
|
||||||
|
|
|
||||||
|
|
@ -1391,7 +1391,7 @@ func triggerLoop(req *ExecutionRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepSaveLog(req *ExecutionRequest) bool {
|
func stepSaveLog(req *ExecutionRequest) bool {
|
||||||
filename := fmt.Sprintf("%v.%v.%v", req.logEntry.ActionTitle, req.logEntry.DatetimeStarted.Unix(), req.logEntry.ExecutionTrackingID)
|
filename := fmt.Sprintf("%v.%v.%v", sanitizeLogFilename(req.logEntry.ActionTitle), req.logEntry.DatetimeStarted.Unix(), req.logEntry.ExecutionTrackingID)
|
||||||
|
|
||||||
saveLogResults(req, filename)
|
saveLogResults(req, filename)
|
||||||
saveLogOutput(req, filename)
|
saveLogOutput(req, filename)
|
||||||
|
|
@ -1399,6 +1399,24 @@ func stepSaveLog(req *ExecutionRequest) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanitizeLogFilename replaces characters that are unsafe in filenames so action
|
||||||
|
// titles like "Create/update Report" do not create nested paths or fail to write.
|
||||||
|
func sanitizeLogFilename(title string) string {
|
||||||
|
replacer := strings.NewReplacer(
|
||||||
|
"/", "_",
|
||||||
|
"\\", "_",
|
||||||
|
":", "_",
|
||||||
|
"*", "_",
|
||||||
|
"?", "_",
|
||||||
|
"\"", "_",
|
||||||
|
"<", "_",
|
||||||
|
">", "_",
|
||||||
|
"|", "_",
|
||||||
|
)
|
||||||
|
|
||||||
|
return replacer.Replace(title)
|
||||||
|
}
|
||||||
|
|
||||||
func firstNonEmpty(one, two string) string {
|
func firstNonEmpty(one, two string) string {
|
||||||
if one != "" {
|
if one != "" {
|
||||||
return one
|
return one
|
||||||
|
|
|
||||||
|
|
@ -780,3 +780,93 @@ func (c *executionFinishedCollector) OnExecutionFinished(entry *InternalLogEntry
|
||||||
func (c *executionFinishedCollector) OnOutputChunk(_ []byte, _ string) {}
|
func (c *executionFinishedCollector) OnOutputChunk(_ []byte, _ string) {}
|
||||||
|
|
||||||
func (c *executionFinishedCollector) OnActionMapRebuilt() {}
|
func (c *executionFinishedCollector) OnActionMapRebuilt() {}
|
||||||
|
|
||||||
|
func TestSanitizeLogFilename(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
title string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"Echo Test", "Echo Test"},
|
||||||
|
{"Create/update Monthly Report", "Create_update Monthly Report"},
|
||||||
|
{`path\with\backslashes`, "path_with_backslashes"},
|
||||||
|
{`a:b*c?d"e<f>g|h`, "a_b_c_d_e_f_g_h"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(t, tt.want, sanitizeLogFilename(tt.title), "title=%q", tt.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepSaveLogSanitizesSlashInTitle(t *testing.T) {
|
||||||
|
resultsDir := t.TempDir()
|
||||||
|
outputDir := t.TempDir()
|
||||||
|
started := time.Unix(1714333384, 0)
|
||||||
|
trackingID := "5e2dc9e5-b6b3-445b-bff9-c2082b0bbbb2"
|
||||||
|
title := "Create/update Monthly Report"
|
||||||
|
|
||||||
|
req := &ExecutionRequest{
|
||||||
|
Cfg: &config.Config{
|
||||||
|
SaveLogs: config.SaveLogsConfig{
|
||||||
|
ResultsDirectory: resultsDir,
|
||||||
|
OutputDirectory: outputDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Binding: &ActionBinding{
|
||||||
|
Action: &config.Action{},
|
||||||
|
},
|
||||||
|
logEntry: &InternalLogEntry{
|
||||||
|
ActionTitle: title,
|
||||||
|
DatetimeStarted: started,
|
||||||
|
ExecutionTrackingID: trackingID,
|
||||||
|
Output: "report ok",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, stepSaveLog(req))
|
||||||
|
|
||||||
|
expectedBase := "Create_update Monthly Report.1714333384." + trackingID
|
||||||
|
resultsPath := filepath.Join(resultsDir, expectedBase+".yaml")
|
||||||
|
outputPath := filepath.Join(outputDir, expectedBase+".log")
|
||||||
|
|
||||||
|
assert.FileExists(t, resultsPath)
|
||||||
|
assert.FileExists(t, outputPath)
|
||||||
|
|
||||||
|
resultsEntries, err := os.ReadDir(resultsDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, resultsEntries, 1, "results file must be flat under resultsDirectory, not a subdirectory")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(resultsPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, string(data), title, "YAML content keeps the original action title")
|
||||||
|
|
||||||
|
output, err := os.ReadFile(outputPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "report ok", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepSaveLogKeepsSafeTitleFilename(t *testing.T) {
|
||||||
|
resultsDir := t.TempDir()
|
||||||
|
started := time.Unix(1714333384, 0)
|
||||||
|
trackingID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||||
|
|
||||||
|
req := &ExecutionRequest{
|
||||||
|
Cfg: &config.Config{
|
||||||
|
SaveLogs: config.SaveLogsConfig{
|
||||||
|
ResultsDirectory: resultsDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Binding: &ActionBinding{
|
||||||
|
Action: &config.Action{},
|
||||||
|
},
|
||||||
|
logEntry: &InternalLogEntry{
|
||||||
|
ActionTitle: "Echo Test",
|
||||||
|
DatetimeStarted: started,
|
||||||
|
ExecutionTrackingID: trackingID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, stepSaveLog(req))
|
||||||
|
|
||||||
|
expectedPath := filepath.Join(resultsDir, "Echo Test.1714333384."+trackingID+".yaml")
|
||||||
|
assert.FileExists(t, expectedPath)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue