fleet/orbit/pkg/update/runner_test.go
Lucas Manuel Rodriguez 2e5bf75b6d
fleetd to start up when TUF signatures are expired (#23102)
#22740

Full QA is still a WIP but this is ready for review.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [x] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [x] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [x] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
2024-10-28 20:40:19 -03:00

234 lines
6.3 KiB
Go

package update
import (
"math/rand"
"os"
"runtime"
"strings"
"testing"
"time"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/pkg/nettest"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewRunner(t *testing.T) {
// TODO(lucas): Do not use our TUF remote repository
// but instead create local repository and serve with a httptest server.
// For that, we need to move and export some functionality currently in
// "ee/fleetctl/updates.go" (as it doesn't make sense to have such functionality
// there and import such eefleetctl package here).
nettest.Run(t)
rootDir := t.TempDir()
updateOpts := DefaultOptions
updateOpts.RootDirectory = rootDir
u, err := NewUpdater(updateOpts)
require.NoError(t, err)
err = u.UpdateMetadata()
require.NoError(t, err)
runnerOpts := RunnerOptions{
CheckInterval: 1 * time.Second,
Targets: []string{constant.OsqueryTUFTargetName},
}
// NewRunner should not fail if targets do not exist locally.
r, err := NewRunner(u, runnerOpts)
require.NoError(t, err)
// ExecutableLocalPath fails if the target does not exist in the expected path.
execPath, err := u.ExecutableLocalPath(constant.OsqueryTUFTargetName)
require.Error(t, err)
require.NoFileExists(t, execPath)
// r.UpdateAction should download osqueryd.
didUpdate, err := r.UpdateAction()
require.NoError(t, err)
require.True(t, didUpdate)
// ExecutableLocalPath should now succeed.
execPath, err = u.ExecutableLocalPath(constant.OsqueryTUFTargetName)
require.NoError(t, err)
require.FileExists(t, execPath)
// Create another Runner but with the target already existing.
r2, err := NewRunner(u, runnerOpts)
require.NoError(t, err)
didUpdate, err = r2.UpdateAction()
require.NoError(t, err)
require.False(t, didUpdate)
}
func TestRandomizeDuration(t *testing.T) {
rand, err := randomizeDuration(10 * time.Minute)
require.NoError(t, err)
assert.True(t, rand >= 0)
assert.True(t, rand < 10*time.Minute)
}
func TestGetVersion(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
t.Parallel()
testCases := map[string]struct {
cmd string
version string
}{
"4.5.6": {
cmd: "#!/bin/bash\n/bin/echo orbit 4.5.6",
version: "4.5.6",
},
"42.0.0": {
cmd: "#!/bin/bash\n/bin/echo fleet-desktop 42.0.0",
version: "42.0.0",
},
"5.10.2-26-gc396d07b4-dirty": {
cmd: "#!/bin/bash\n/bin/echo osquery version 5.10.2-26-gc396d07b4-dirty",
version: "5.10.2-26-gc396d07b4-dirty",
},
"bad output": {
cmd: "#!/bin/bash\n/bin/echo osquery version is weird",
version: "",
},
"bad cmd": {
cmd: "bozo+bozo+bozo",
version: "",
},
}
for name, tc := range testCases {
tc := tc // capture range variable, needed for parallel tests
t.Run(
name, func(t *testing.T) {
t.Parallel()
// create a temp executable file
dir := t.TempDir()
file, err := os.CreateTemp(dir, "binary")
require.NoError(t, err)
_, err = file.WriteString(tc.cmd)
require.NoError(t, err)
err = file.Chmod(0o755)
require.NoError(t, err)
_ = file.Close()
// "text file busy" is a Go issue when executing file just written: https://github.com/golang/go/issues/22315
var version string
retries := 0
for {
version, err = GetVersion(file.Name())
if err != nil {
t.Log(err)
if strings.Contains(err.Error(), "text file busy") {
if retries > 5 {
t.Fatal("too many retries due to 'text file busy' error: https://github.com/golang/go/issues/22315")
}
// adding some randomization so that parallel tests get out of sync if needed
time.Sleep((500 + time.Duration(rand.Intn(100))) * time.Millisecond) //nolint:gosec
retries++
} else {
break
}
} else {
break
}
}
assert.Equal(t, tc.version, version)
},
)
}
}
func TestCompareVersion(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
t.Parallel()
testCases := map[string]struct {
cmd string
oldVersion string
expected *int
}{
"downgrade": {
cmd: "#!/bin/bash\n/bin/echo orbit 4.9",
oldVersion: "4.10",
expected: ptr.Int(1),
},
"same": {
cmd: "#!/bin/bash\n/bin/echo osquery version 5.10.2-26-gc396d07b4-dirty",
oldVersion: "5.10.2-26-gc396d07b4-dirty",
expected: ptr.Int(0),
},
"same 2": {
cmd: "#!/bin/bash\n/bin/echo osquery version 5.10",
oldVersion: "5.10.0",
expected: ptr.Int(0),
},
"upgrade": {
cmd: "#!/bin/bash\n/bin/echo osquery version 5.10.10",
oldVersion: "5.10.9",
expected: ptr.Int(-1),
},
"invalid new version": {
cmd: "#!/bin/bash\n/bin/echo osquery version invalid",
oldVersion: "5.10.9",
expected: nil,
},
"invalid old version": {
cmd: "#!/bin/bash\n/bin/echo orbit 1",
oldVersion: "",
expected: nil,
},
"invalid old version 2": {
cmd: "#!/bin/bash\n/bin/echo orbit 1",
oldVersion: "1.01", // invalid, needs to be 1.1
expected: nil,
},
}
for name, tc := range testCases {
tc := tc // capture range variable, needed for parallel tests
t.Run(
name, func(t *testing.T) {
t.Parallel()
// create a temp executable file
dir := t.TempDir()
file, err := os.CreateTemp(dir, "binary")
require.NoError(t, err)
_, err = file.WriteString(tc.cmd)
require.NoError(t, err)
err = file.Chmod(0o755)
require.NoError(t, err)
_ = file.Close()
// "text file busy" is a Go issue when executing file just written: https://github.com/golang/go/issues/22315
var result *int
retries := 0
for {
result, err = compareVersion(file.Name(), tc.oldVersion, "target")
if err != nil {
t.Log(err)
if strings.Contains(err.Error(), "text file busy") {
if retries > 5 {
t.Fatal("too many retries due to 'text file busy' error: https://github.com/golang/go/issues/22315")
}
// adding some randomization so that parallel tests get out of sync if needed
time.Sleep((500 + time.Duration(rand.Intn(100))) * time.Millisecond) //nolint:gosec
retries++
} else {
break
}
} else {
break
}
}
assert.Equal(t, tc.expected, result)
},
)
}
}