Merge branch 'main' into feat-fleet-app-library
This commit is contained in:
+7
-6
@@ -33,9 +33,9 @@ func ExtractDebMetadata(r io.Reader) (*InstallerMetadata, error) {
|
||||
return nil, fmt.Errorf("failed to advance to next file in archive: %w", err)
|
||||
}
|
||||
|
||||
name := path.Clean(hdr.Name)
|
||||
if strings.HasPrefix(name, "control.tar") {
|
||||
ext := filepath.Ext(name)
|
||||
filename := path.Clean(hdr.Name)
|
||||
if strings.HasPrefix(filename, "control.tar") {
|
||||
ext := filepath.Ext(filename)
|
||||
if ext == ".tar" {
|
||||
ext = ""
|
||||
}
|
||||
@@ -49,9 +49,10 @@ func ExtractDebMetadata(r io.Reader) (*InstallerMetadata, error) {
|
||||
return nil, fmt.Errorf("failed to read all content: %w", err)
|
||||
}
|
||||
return &InstallerMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
SHASum: h.Sum(nil),
|
||||
Name: name,
|
||||
Version: version,
|
||||
PackageIDs: []string{name},
|
||||
SHASum: h.Sum(nil),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type InstallerMetadata struct {
|
||||
BundleIdentifier string
|
||||
SHASum []byte
|
||||
Extension string
|
||||
PackageIDs []string
|
||||
}
|
||||
|
||||
// ExtractInstallerMetadata extracts the software name and version from the
|
||||
|
||||
@@ -61,3 +61,32 @@ func GetRemoveScript(extension string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed scripts/uninstall_exe.ps1
|
||||
var uninstallExeScript string
|
||||
|
||||
//go:embed scripts/uninstall_pkg.sh
|
||||
var uninstallPkgScript string
|
||||
|
||||
//go:embed scripts/uninstall_msi.ps1
|
||||
var uninstallMsiScript string
|
||||
|
||||
//go:embed scripts/uninstall_deb.sh
|
||||
var uninstallDebScript string
|
||||
|
||||
// GetUninstallScript returns a script that can be used to uninstall a
|
||||
// software item with the given extension.
|
||||
func GetUninstallScript(extension string) string {
|
||||
switch extension {
|
||||
case "msi":
|
||||
return uninstallMsiScript
|
||||
case "deb":
|
||||
return uninstallDebScript
|
||||
case "pkg":
|
||||
return uninstallPkgScript
|
||||
case "exe":
|
||||
return uninstallExeScript
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
+22
-13
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -19,35 +20,43 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// Note: to update the goldens, run the tests with `-update`:
|
||||
// Note: to update the goldens, delete testdata/scripts/* and run the tests with `-update`:
|
||||
//
|
||||
// go test ./pkg/file/... -update
|
||||
func TestGetInstallAndRemoveScript(t *testing.T) {
|
||||
scriptsByType := map[string][2]string{
|
||||
t.Parallel()
|
||||
scriptsByType := map[string]map[string]string{
|
||||
"msi": {
|
||||
"./scripts/install_msi.ps1",
|
||||
"./scripts/remove_msi.ps1",
|
||||
"install": "./scripts/install_msi.ps1",
|
||||
"remove": "./scripts/remove_msi.ps1",
|
||||
"uninstall": "./scripts/uninstall_msi.ps1",
|
||||
},
|
||||
"pkg": {
|
||||
"./scripts/install_pkg.sh",
|
||||
"./scripts/remove_pkg.sh",
|
||||
"install": "./scripts/install_pkg.sh",
|
||||
"remove": "./scripts/remove_pkg.sh",
|
||||
"uninstall": "./scripts/uninstall_pkg.sh",
|
||||
},
|
||||
"deb": {
|
||||
"./scripts/install_deb.sh",
|
||||
"./scripts/remove_deb.sh",
|
||||
"install": "./scripts/install_deb.sh",
|
||||
"remove": "./scripts/remove_deb.sh",
|
||||
"uninstall": "./scripts/uninstall_deb.sh",
|
||||
},
|
||||
"exe": {
|
||||
"./scripts/install_exe.ps1",
|
||||
"./scripts/remove_exe.ps1",
|
||||
"install": "./scripts/install_exe.ps1",
|
||||
"remove": "./scripts/remove_exe.ps1",
|
||||
"uninstall": "./scripts/uninstall_exe.ps1",
|
||||
},
|
||||
}
|
||||
|
||||
for itype, scripts := range scriptsByType {
|
||||
gotScript := GetInstallScript(itype)
|
||||
assertGoldenMatches(t, scripts[0], gotScript, *update)
|
||||
assertGoldenMatches(t, scripts["install"], gotScript, *update)
|
||||
|
||||
gotScript = GetRemoveScript(itype)
|
||||
assertGoldenMatches(t, scripts[1], gotScript, *update)
|
||||
assertGoldenMatches(t, scripts["remove"], gotScript, *update)
|
||||
|
||||
gotScript = GetUninstallScript(itype)
|
||||
assertGoldenMatches(t, scripts["uninstall"], gotScript, *update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +76,5 @@ func assertGoldenMatches(t *testing.T, goldenFile string, actual string, update
|
||||
|
||||
content, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(content), actual)
|
||||
assert.Equal(t, string(content), actual)
|
||||
}
|
||||
|
||||
+6
-4
@@ -9,7 +9,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/sassoftware/relic/v7/lib/comdoc"
|
||||
"github.com/sassoftware/relic/v8/lib/comdoc"
|
||||
)
|
||||
|
||||
func ExtractMSIMetadata(r io.Reader) (*InstallerMetadata, error) {
|
||||
@@ -77,10 +77,12 @@ func ExtractMSIMetadata(r io.Reader) (*InstallerMetadata, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// MSI installer product information properties: https://learn.microsoft.com/en-us/windows/win32/msi/property-reference#product-information-properties
|
||||
return &InstallerMetadata{
|
||||
Name: strings.TrimSpace(props["ProductName"]),
|
||||
Version: strings.TrimSpace(props["ProductVersion"]),
|
||||
SHASum: h.Sum(nil),
|
||||
Name: strings.TrimSpace(props["ProductName"]),
|
||||
Version: strings.TrimSpace(props["ProductVersion"]),
|
||||
PackageIDs: []string{strings.TrimSpace(props["ProductCode"])},
|
||||
SHASum: h.Sum(nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -50,10 +50,12 @@ func ExtractPEMetadata(r io.Reader) (*InstallerMetadata, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing PE version resources: %w", err)
|
||||
}
|
||||
name := strings.TrimSpace(v["ProductName"])
|
||||
return applySpecialCases(&InstallerMetadata{
|
||||
Name: strings.TrimSpace(v["ProductName"]),
|
||||
Version: strings.TrimSpace(v["ProductVersion"]),
|
||||
SHASum: h.Sum(nil),
|
||||
Name: name,
|
||||
Version: strings.TrimSpace(v["ProductVersion"]),
|
||||
PackageIDs: []string{name},
|
||||
SHASum: h.Sum(nil),
|
||||
}, v), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExtractPEMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
file, err := os.Open("testdata/software-installers/hello-world-installer.exe")
|
||||
require.NoError(t, err)
|
||||
meta, err := ExtractPEMetadata(file)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, meta)
|
||||
assert.Equal(t, "Hello world", meta.Name)
|
||||
assert.Equal(t, "1.0.0", meta.Version)
|
||||
assert.Equal(t, []string{"Hello world"}, meta.PackageIDs)
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
# Learn more about .exe install scripts: http://fleetdm.com/learn-more-about/exe-install-scripts
|
||||
|
||||
$exeFilePath = "${env:INSTALLER_PATH}"
|
||||
|
||||
# extract the name of the executable to use as the sub-directory name
|
||||
$exeName = [System.IO.Path]::GetFileName($exeFilePath)
|
||||
$subDir = [System.IO.Path]::GetFileNameWithoutExtension($exeFilePath)
|
||||
|
||||
$destinationPath = Join-Path -Path $env:ProgramFiles -ChildPath $subDir
|
||||
|
||||
# check if the directory does not exist, and create it if necessary
|
||||
if (-not (Test-Path -Path $destinationPath)) {
|
||||
New-Item -ItemType Directory -Path $destinationPath
|
||||
# Add argument to install silently
|
||||
# Argument to make install silent depends on installer,
|
||||
# each installer might use different argument (usually it's "/S" or "/s")
|
||||
$processOptions = @{
|
||||
FilePath = "$exeFilePath"
|
||||
ArgumentList = "/S"
|
||||
PassThru = $true
|
||||
Wait = $true
|
||||
}
|
||||
|
||||
# Start process and track exit code
|
||||
$process = Start-Process @processOptions
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
# copy the .exe file to the new sub-directory
|
||||
$destinationExePath = Join-Path -Path $destinationPath -ChildPath $exeName
|
||||
Copy-Item -Path $exeFilePath -Destination $destinationExePath
|
||||
# Prints the exit code
|
||||
Write-Host "Install exit code: $exitCode"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package_name=$PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product name that's extracted on upload
|
||||
apt remove "$package_name" -y
|
||||
@@ -0,0 +1,17 @@
|
||||
# Fleet extracts name from installer (EXE) and saves it to package ID variable
|
||||
$softwareName = $PACKAGE_ID
|
||||
|
||||
# Get the list of subkeys under the Uninstall registry path
|
||||
$uninstallKeys = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath }
|
||||
|
||||
# Loop through each registry key to find the one containing "$softwareName" in DisplayName and run uninstall command from UninstallString
|
||||
foreach ($key in $uninstallKeys) {
|
||||
if ($key.DisplayName -like "*$softwareName*") {
|
||||
# Get the uninstall command
|
||||
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
|
||||
|
||||
# Run the uninstall command with arguments using the call operator &
|
||||
& $uninstallCommand
|
||||
break # Exit the loop once the software is found and uninstalled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
$product_code = $PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product code that's extracted on upload
|
||||
msiexec /quiet /x $product_code
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fleet extracts and saves package IDs.
|
||||
pkg_ids=$PACKAGE_ID
|
||||
|
||||
# Get all files associated with package and remove them
|
||||
for pkg_id in "${pkg_ids[@]}"
|
||||
do
|
||||
# Get volume and location of package
|
||||
volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}')
|
||||
location=$(pkgutil --pkg-info "$pkg_id" | grep -i "location" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}')
|
||||
# Check if this package id corresponds to a valid/installed package
|
||||
if [[ ! -z "$volume" && ! -z "$location" ]]; then
|
||||
# Remove individual files/directories belonging to package
|
||||
pkgutil --files "$pkg_id" | sed -e 's@^@'"$volume""$location"'/@' | tr '\n' '\0' | xargs -n 1 -0 rm -rf
|
||||
# Remove receipts
|
||||
pkgutil --forget "$pkg_id"
|
||||
else
|
||||
echo "WARNING: volume or location are empty for package ID $pkg_id"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<installer-gui-script minSpecVersion="2">
|
||||
<pkg-ref id="com.bozo.zeroinstallsize" installKBytes="0" packageIdentifier="com.bozo.zeroinstallsize.app">
|
||||
<bundle-version>
|
||||
<bundle CFBundleShortVersionString="1.2.3" CFBundleVersion="8.10.34.234040" id="com.bozo.zeroinstallsize" path="ZeroInstallSize.app"/>
|
||||
</bundle-version>
|
||||
</pkg-ref>
|
||||
<product id="com.bozo.zeroinstallsize" version="1.2.3"/>
|
||||
<title>ZeroInstallSize</title>
|
||||
</installer-gui-script>
|
||||
+16
-12
@@ -1,16 +1,20 @@
|
||||
# Learn more about .exe install scripts: http://fleetdm.com/learn-more-about/exe-install-scripts
|
||||
|
||||
$exeFilePath = "${env:INSTALLER_PATH}"
|
||||
|
||||
# extract the name of the executable to use as the sub-directory name
|
||||
$exeName = [System.IO.Path]::GetFileName($exeFilePath)
|
||||
$subDir = [System.IO.Path]::GetFileNameWithoutExtension($exeFilePath)
|
||||
|
||||
$destinationPath = Join-Path -Path $env:ProgramFiles -ChildPath $subDir
|
||||
|
||||
# check if the directory does not exist, and create it if necessary
|
||||
if (-not (Test-Path -Path $destinationPath)) {
|
||||
New-Item -ItemType Directory -Path $destinationPath
|
||||
# Add argument to install silently
|
||||
# Argument to make install silent depends on installer,
|
||||
# each installer might use different argument (usually it's "/S" or "/s")
|
||||
$processOptions = @{
|
||||
FilePath = "$exeFilePath"
|
||||
ArgumentList = "/S"
|
||||
PassThru = $true
|
||||
Wait = $true
|
||||
}
|
||||
|
||||
# Start process and track exit code
|
||||
$process = Start-Process @processOptions
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
# copy the .exe file to the new sub-directory
|
||||
$destinationExePath = Join-Path -Path $destinationPath -ChildPath $exeName
|
||||
Copy-Item -Path $exeFilePath -Destination $destinationExePath
|
||||
# Prints the exit code
|
||||
Write-Host "Install exit code: $exitCode"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package_name=$PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product name that's extracted on upload
|
||||
apt remove "$package_name" -y
|
||||
@@ -0,0 +1,17 @@
|
||||
# Fleet extracts name from installer (EXE) and saves it to package ID variable
|
||||
$softwareName = $PACKAGE_ID
|
||||
|
||||
# Get the list of subkeys under the Uninstall registry path
|
||||
$uninstallKeys = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath }
|
||||
|
||||
# Loop through each registry key to find the one containing "$softwareName" in DisplayName and run uninstall command from UninstallString
|
||||
foreach ($key in $uninstallKeys) {
|
||||
if ($key.DisplayName -like "*$softwareName*") {
|
||||
# Get the uninstall command
|
||||
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
|
||||
|
||||
# Run the uninstall command with arguments using the call operator &
|
||||
& $uninstallCommand
|
||||
break # Exit the loop once the software is found and uninstalled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
$product_code = $PACKAGE_ID
|
||||
|
||||
# Fleet uninstalls app using product code that's extracted on upload
|
||||
msiexec /quiet /x $product_code
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fleet extracts and saves package IDs.
|
||||
pkg_ids=$PACKAGE_ID
|
||||
|
||||
# Get all files associated with package and remove them
|
||||
for pkg_id in "${pkg_ids[@]}"
|
||||
do
|
||||
# Get volume and location of package
|
||||
volume=$(pkgutil --pkg-info "$pkg_id" | grep -i "volume" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}')
|
||||
location=$(pkgutil --pkg-info "$pkg_id" | grep -i "location" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}')
|
||||
# Check if this package id corresponds to a valid/installed package
|
||||
if [[ ! -z "$volume" && ! -z "$location" ]]; then
|
||||
# Remove individual files/directories belonging to package
|
||||
pkgutil --files "$pkg_id" | sed -e 's@^@'"$volume""$location"'/@' | tr '\n' '\0' | xargs -n 1 -0 rm -rf
|
||||
# Remove receipts
|
||||
pkgutil --forget "$pkg_id"
|
||||
else
|
||||
echo "WARNING: volume or location are empty for package ID $pkg_id"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1 @@
|
||||
- `hello-world-installer.exe` is an installer with a text file. It was created using [Inno Setup](https://jrsoftware.org/isinfo.php) on Windows.
|
||||
BIN
Binary file not shown.
+45
-5
@@ -123,6 +123,7 @@ type distributionPkgRef struct {
|
||||
BundleVersions []distributionBundleVersion `xml:"bundle-version"`
|
||||
MustClose distributionMustClose `xml:"must-close"`
|
||||
PackageIdentifier string `xml:"packageIdentifier,attr"`
|
||||
InstallKBytes string `xml:"installKBytes,attr"`
|
||||
}
|
||||
|
||||
// distributionBundleVersion represents the bundle-version element
|
||||
@@ -223,21 +224,55 @@ func parseDistributionFile(rawXML []byte) (*InstallerMetadata, error) {
|
||||
return nil, fmt.Errorf("unmarshal Distribution XML: %w", err)
|
||||
}
|
||||
|
||||
name, identifier, version := getDistributionInfo(&distXML)
|
||||
name, identifier, version, packageIDs := getDistributionInfo(&distXML)
|
||||
return &InstallerMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
BundleIdentifier: identifier,
|
||||
PackageIDs: packageIDs,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// getDistributionInfo gets the name, bundle identifier and version of a PKG distribution file
|
||||
func getDistributionInfo(d *distributionXML) (name string, identifier string, version string) {
|
||||
func getDistributionInfo(d *distributionXML) (name string, identifier string, version string, packageIDs []string) {
|
||||
var appVersion string
|
||||
|
||||
// find the package ids that have an installation size
|
||||
var packageIDSet = make(map[string]struct{}, 1)
|
||||
for _, pkg := range d.PkgRefs {
|
||||
if pkg.InstallKBytes != "" && pkg.InstallKBytes != "0" {
|
||||
var id string
|
||||
if pkg.PackageIdentifier != "" {
|
||||
id = pkg.PackageIdentifier
|
||||
} else if pkg.ID != "" {
|
||||
id = pkg.ID
|
||||
}
|
||||
if id != "" {
|
||||
packageIDSet[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(packageIDSet) == 0 {
|
||||
// if we didn't find any package IDs with installation size, then grab all of them
|
||||
for _, pkg := range d.PkgRefs {
|
||||
var id string
|
||||
if pkg.PackageIdentifier != "" {
|
||||
id = pkg.PackageIdentifier
|
||||
} else if pkg.ID != "" {
|
||||
id = pkg.ID
|
||||
}
|
||||
if id != "" {
|
||||
packageIDSet[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for id := range packageIDSet {
|
||||
packageIDs = append(packageIDs, id)
|
||||
}
|
||||
|
||||
out:
|
||||
// first, look in all the bundle versions for one that has a `path` attribute
|
||||
// look in all the bundle versions for one that has a `path` attribute
|
||||
// that is not nested, this is generally the case for packages that distribute
|
||||
// `.app` files, which are ultimately picked up as an installed app by osquery
|
||||
for _, pkg := range d.PkgRefs {
|
||||
@@ -284,6 +319,11 @@ out:
|
||||
identifier = d.Product.ID
|
||||
}
|
||||
|
||||
// if package IDs are still empty, use the identifier as the package ID
|
||||
if len(packageIDs) == 0 && identifier != "" {
|
||||
packageIDs = append(packageIDs, identifier)
|
||||
}
|
||||
|
||||
// for the name, try to use the title and fallback to the bundle
|
||||
// identifier
|
||||
if name == "" && d.Title != "" {
|
||||
@@ -296,7 +336,7 @@ out:
|
||||
// for the version, try to use the top-level product version, if not,
|
||||
// fallback to any version definition alongside the name or the first
|
||||
// version in a pkg-ref we find.
|
||||
if version == "" && d.Product.Version != "" {
|
||||
if d.Product.Version != "" {
|
||||
version = d.Product.Version
|
||||
}
|
||||
if version == "" && appVersion != "" {
|
||||
@@ -310,7 +350,7 @@ out:
|
||||
}
|
||||
}
|
||||
|
||||
return name, identifier, version
|
||||
return name, identifier, version, packageIDs
|
||||
}
|
||||
|
||||
// isValidAppFilePath checks if the given input is a file name ending with .app
|
||||
|
||||
+80
-44
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -52,113 +53,147 @@ func TestCheckPKGSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseRealDistributionFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
file string
|
||||
expectedName string
|
||||
expectedVersion string
|
||||
expectedBundleID string
|
||||
file string
|
||||
expectedName string
|
||||
expectedVersion string
|
||||
expectedBundleID string
|
||||
expectedPackageIDs []string
|
||||
}{
|
||||
{
|
||||
file: "distribution-1password.xml",
|
||||
expectedName: "1Password.app",
|
||||
expectedVersion: "8.10.34",
|
||||
expectedBundleID: "com.1password.1password",
|
||||
file: "distribution-1password.xml",
|
||||
expectedName: "1Password.app",
|
||||
expectedVersion: "8.10.34",
|
||||
expectedBundleID: "com.1password.1password",
|
||||
expectedPackageIDs: []string{"com.1password.1password"},
|
||||
},
|
||||
{
|
||||
file: "distribution-chrome.xml",
|
||||
expectedName: "Google Chrome.app",
|
||||
expectedVersion: "126.0.6478.62",
|
||||
expectedBundleID: "com.google.Chrome",
|
||||
file: "distribution-chrome.xml",
|
||||
expectedName: "Google Chrome.app",
|
||||
expectedVersion: "126.0.6478.62",
|
||||
expectedBundleID: "com.google.Chrome",
|
||||
expectedPackageIDs: []string{"com.google.Chrome"},
|
||||
},
|
||||
{
|
||||
file: "distribution-edge.xml",
|
||||
expectedName: "Microsoft Edge.app",
|
||||
expectedVersion: "126.0.2592.56",
|
||||
expectedBundleID: "com.microsoft.edgemac",
|
||||
file: "distribution-edge.xml",
|
||||
expectedName: "Microsoft Edge.app",
|
||||
expectedVersion: "126.0.2592.56",
|
||||
expectedBundleID: "com.microsoft.edgemac",
|
||||
expectedPackageIDs: []string{"com.microsoft.edgemac"},
|
||||
},
|
||||
{
|
||||
file: "distribution-firefox.xml",
|
||||
expectedName: "Firefox.app",
|
||||
expectedVersion: "99.0",
|
||||
expectedBundleID: "org.mozilla.firefox",
|
||||
file: "distribution-firefox.xml",
|
||||
expectedName: "Firefox.app",
|
||||
expectedVersion: "99.0",
|
||||
expectedBundleID: "org.mozilla.firefox",
|
||||
expectedPackageIDs: []string{"org.mozilla.firefox"},
|
||||
},
|
||||
{
|
||||
file: "distribution-fleet.xml",
|
||||
expectedName: "Fleet osquery",
|
||||
expectedVersion: "42.0.0",
|
||||
expectedBundleID: "com.fleetdm.orbit",
|
||||
file: "distribution-fleet.xml",
|
||||
expectedName: "Fleet osquery",
|
||||
expectedVersion: "42.0.0",
|
||||
expectedBundleID: "com.fleetdm.orbit",
|
||||
expectedPackageIDs: []string{"com.fleetdm.orbit.base.pkg"},
|
||||
},
|
||||
{
|
||||
file: "distribution-go.xml",
|
||||
expectedName: "Go",
|
||||
expectedVersion: "go1.22.4",
|
||||
expectedBundleID: "org.golang.go",
|
||||
file: "distribution-go.xml",
|
||||
expectedName: "Go",
|
||||
expectedVersion: "go1.22.4",
|
||||
expectedBundleID: "org.golang.go",
|
||||
expectedPackageIDs: []string{"org.golang.go"},
|
||||
},
|
||||
{
|
||||
file: "distribution-microsoft-teams.xml",
|
||||
expectedName: "Microsoft Teams.app",
|
||||
expectedVersion: "24124.1412.2911.3341",
|
||||
expectedBundleID: "com.microsoft.teams2",
|
||||
expectedPackageIDs: []string{"com.microsoft.teams2", "com.microsoft.package.Microsoft_AutoUpdate.app",
|
||||
"com.microsoft.MSTeamsAudioDevice"},
|
||||
},
|
||||
{
|
||||
file: "distribution-zoom.xml",
|
||||
expectedName: "zoom.us.app",
|
||||
expectedVersion: "6.0.11.35001",
|
||||
expectedBundleID: "us.zoom.xos",
|
||||
file: "distribution-zoom.xml",
|
||||
expectedName: "zoom.us.app",
|
||||
expectedVersion: "6.0.11.35001",
|
||||
expectedBundleID: "us.zoom.xos",
|
||||
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
|
||||
},
|
||||
{
|
||||
file: "distribution-acrobatreader.xml",
|
||||
expectedName: "Adobe Acrobat Reader.app",
|
||||
expectedVersion: "24.002.20857",
|
||||
expectedBundleID: "com.adobe.Reader",
|
||||
expectedPackageIDs: []string{"com.adobe.acrobat.DC.reader.app.pkg.MUI", "com.adobe.acrobat.DC.reader.appsupport.pkg.MUI",
|
||||
"com.adobe.acrobat.reader.DC.reader.app.pkg.MUI", "com.adobe.armdc.app.pkg"},
|
||||
},
|
||||
{
|
||||
file: "distribution-airtame.xml",
|
||||
expectedName: "Airtame.app",
|
||||
expectedVersion: "4.10.1",
|
||||
expectedBundleID: "com.airtame.airtame-application",
|
||||
file: "distribution-airtame.xml",
|
||||
expectedName: "Airtame.app",
|
||||
expectedVersion: "4.10.1",
|
||||
expectedBundleID: "com.airtame.airtame-application",
|
||||
expectedPackageIDs: []string{"com.airtame.airtame-application"},
|
||||
},
|
||||
{
|
||||
file: "distribution-boxdrive.xml",
|
||||
expectedName: "Box.app",
|
||||
expectedVersion: "2.38.173",
|
||||
expectedBundleID: "com.box.desktop",
|
||||
expectedPackageIDs: []string{"com.box.desktop.installer.desktop", "com.box.desktop.installer.local.appsupport",
|
||||
"com.box.desktop.installer.autoupdater", "com.box.desktop.installer.osxfuse"},
|
||||
},
|
||||
{
|
||||
file: "distribution-iriunwebcam.xml",
|
||||
expectedName: "IriunWebcam.app",
|
||||
expectedVersion: "2.8.8",
|
||||
expectedBundleID: "com.iriun.macwebcam",
|
||||
// Note: "com.iriun.pkg.multicam" is part of the installer package, but it is not actually installed by default.
|
||||
// We can't reliably determine which packages are installed by the installer, so we just list all of them.
|
||||
expectedPackageIDs: []string{"com.iriun.pkg.webcam.tmp", "com.iriun.pkg.multicam"},
|
||||
},
|
||||
{
|
||||
file: "distribution-microsoftexcel.xml",
|
||||
expectedName: "Microsoft Excel.app",
|
||||
expectedVersion: "16.86",
|
||||
expectedBundleID: "com.microsoft.Excel",
|
||||
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Excel.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
|
||||
"com.microsoft.pkg.licensing"},
|
||||
},
|
||||
{
|
||||
file: "distribution-microsoftword.xml",
|
||||
expectedName: "Microsoft Word.app",
|
||||
expectedVersion: "16.86",
|
||||
expectedBundleID: "com.microsoft.Word",
|
||||
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_Word.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
|
||||
"com.microsoft.pkg.licensing"},
|
||||
},
|
||||
{
|
||||
file: "distribution-miscrosoftpowerpoint.xml",
|
||||
expectedName: "Microsoft PowerPoint.app",
|
||||
expectedVersion: "16.86",
|
||||
expectedBundleID: "com.microsoft.Powerpoint",
|
||||
expectedPackageIDs: []string{"com.microsoft.package.Microsoft_PowerPoint.app", "com.microsoft.package.Microsoft_AutoUpdate.app",
|
||||
"com.microsoft.pkg.licensing"},
|
||||
},
|
||||
{
|
||||
file: "distribution-ringcentral.xml",
|
||||
expectedName: "RingCentral.app",
|
||||
expectedVersion: "24.1.32.9774",
|
||||
expectedBundleID: "com.ringcentral.glip",
|
||||
file: "distribution-ringcentral.xml",
|
||||
expectedName: "RingCentral.app",
|
||||
expectedVersion: "24.1.32.9774",
|
||||
expectedBundleID: "com.ringcentral.glip",
|
||||
expectedPackageIDs: []string{"com.ringcentral.glip"},
|
||||
},
|
||||
{
|
||||
file: "distribution-zoom-full.xml",
|
||||
expectedName: "Zoom Workplace",
|
||||
expectedVersion: "6.1.1.36333",
|
||||
expectedBundleID: "us.zoom.xos",
|
||||
file: "distribution-zoom-full.xml",
|
||||
expectedName: "Zoom Workplace",
|
||||
expectedVersion: "6.1.1.36333",
|
||||
expectedBundleID: "us.zoom.xos",
|
||||
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
|
||||
},
|
||||
{
|
||||
file: "test-zero-installkbytes.xml",
|
||||
expectedName: "ZeroInstallSize.app",
|
||||
expectedVersion: "1.2.3",
|
||||
expectedBundleID: "com.bozo.zeroinstallsize",
|
||||
expectedPackageIDs: []string{"com.bozo.zeroinstallsize.app"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -168,6 +203,7 @@ func TestParseRealDistributionFiles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
metadata, err := parseDistributionFile(rawXML)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, tt.expectedPackageIDs, metadata.PackageIDs)
|
||||
require.Equal(t, tt.expectedName, metadata.Name)
|
||||
require.Equal(t, tt.expectedVersion, metadata.Version)
|
||||
require.Equal(t, tt.expectedBundleID, metadata.BundleIdentifier)
|
||||
|
||||
+67
-11
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -36,6 +37,16 @@ type Controls struct {
|
||||
EnableDiskEncryption interface{} `json:"enable_disk_encryption"`
|
||||
|
||||
Scripts []BaseItem `json:"scripts"`
|
||||
|
||||
Defined bool
|
||||
}
|
||||
|
||||
func (c Controls) Set() bool {
|
||||
return c.MacOSUpdates != nil || c.IOSUpdates != nil ||
|
||||
c.IPadOSUpdates != nil || c.MacOSSettings != nil ||
|
||||
c.MacOSSetup != nil || c.MacOSMigration != nil ||
|
||||
c.WindowsUpdates != nil || c.WindowsSettings != nil || c.WindowsEnabledAndConfigured != nil ||
|
||||
c.EnableDiskEncryption != nil || len(c.Scripts) > 0
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
@@ -88,8 +99,10 @@ type GitOpsSoftware struct {
|
||||
AppStoreApps []*fleet.TeamSpecAppStoreApp
|
||||
}
|
||||
|
||||
type Logf func(format string, a ...interface{})
|
||||
|
||||
// GitOpsFromFile parses a GitOps yaml file.
|
||||
func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig) (*GitOps, error) {
|
||||
func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig, logFn Logf) (*GitOps, error) {
|
||||
b, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %s: %w", filePath, err)
|
||||
@@ -126,17 +139,30 @@ func GitOpsFromFile(filePath, baseDir string, appConfig *fleet.EnrichedAppConfig
|
||||
} else {
|
||||
multiError = parseOrgSettings(orgSettingsRaw, result, baseDir, multiError)
|
||||
}
|
||||
} else if teamOk && teamSettingsOk {
|
||||
} else if teamOk {
|
||||
multiError = parseName(teamRaw, result, multiError)
|
||||
multiError = parseTeamSettings(teamSettingsRaw, result, baseDir, multiError)
|
||||
if result.IsNoTeam() {
|
||||
if teamSettingsOk {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("cannot set 'team_settings' on 'No team' file: %q", filePath))
|
||||
}
|
||||
if filepath.Base(filePath) != "no-team.yml" {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("file %q for 'No team' must be named 'no-team.yml'", filePath))
|
||||
}
|
||||
} else {
|
||||
if !teamSettingsOk {
|
||||
multiError = multierror.Append(multiError, errors.New("'team_settings' is required when 'name' is provided"))
|
||||
} else {
|
||||
multiError = parseTeamSettings(teamSettingsRaw, result, baseDir, multiError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
multiError = multierror.Append(multiError, errors.New("either 'org_settings' or 'name' and 'team_settings' is required"))
|
||||
}
|
||||
|
||||
// Validate the required top level options
|
||||
multiError = parseControls(top, result, baseDir, multiError)
|
||||
multiError = parseAgentOptions(top, result, baseDir, multiError)
|
||||
multiError = parseQueries(top, result, baseDir, multiError)
|
||||
multiError = parseAgentOptions(top, result, baseDir, logFn, multiError)
|
||||
multiError = parseQueries(top, result, baseDir, logFn, multiError)
|
||||
|
||||
if appConfig != nil && appConfig.License.IsPremium() {
|
||||
multiError = parseSoftware(top, result, baseDir, multiError)
|
||||
@@ -161,6 +187,20 @@ func parseName(raw json.RawMessage, result *GitOps, multiError *multierror.Error
|
||||
return multiError
|
||||
}
|
||||
|
||||
func (g *GitOps) global() bool {
|
||||
return g.TeamName == nil || *g.TeamName == ""
|
||||
}
|
||||
|
||||
func (g *GitOps) IsNoTeam() bool {
|
||||
return g.TeamName != nil && isNoTeam(*g.TeamName)
|
||||
}
|
||||
|
||||
func isNoTeam(teamName string) bool {
|
||||
return strings.ToLower(teamName) == strings.ToLower(noTeam)
|
||||
}
|
||||
|
||||
const noTeam = "No team"
|
||||
|
||||
func parseOrgSettings(raw json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
var orgSettingsTop BaseItem
|
||||
if err := json.Unmarshal(raw, &orgSettingsTop); err != nil {
|
||||
@@ -314,9 +354,14 @@ func parseSecrets(result *GitOps, multiError *multierror.Error) *multierror.Erro
|
||||
return multiError
|
||||
}
|
||||
|
||||
func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, multiError *multierror.Error) *multierror.Error {
|
||||
agentOptionsRaw, ok := top["agent_options"]
|
||||
if !ok {
|
||||
if result.IsNoTeam() {
|
||||
if ok {
|
||||
logFn("[!] 'agent_options' is not supported for \"No team\". This key will be ignored.")
|
||||
}
|
||||
return multiError
|
||||
} else if !ok {
|
||||
return multierror.Append(multiError, errors.New("'agent_options' is required"))
|
||||
}
|
||||
var agentOptionsTop BaseItem
|
||||
@@ -366,12 +411,14 @@ func parseAgentOptions(top map[string]json.RawMessage, result *GitOps, baseDir s
|
||||
func parseControls(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
controlsRaw, ok := top["controls"]
|
||||
if !ok {
|
||||
return multierror.Append(multiError, errors.New("'controls' is required"))
|
||||
// Nothing to do, return.
|
||||
return multiError
|
||||
}
|
||||
var controlsTop Controls
|
||||
if err := json.Unmarshal(controlsRaw, &controlsTop); err != nil {
|
||||
return multierror.Append(multiError, fmt.Errorf("failed to unmarshal controls: %v", err))
|
||||
}
|
||||
controlsTop.Defined = true
|
||||
if controlsTop.Path == nil {
|
||||
result.Controls = controlsTop
|
||||
} else {
|
||||
@@ -516,9 +563,14 @@ func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string, logFn Logf, multiError *multierror.Error) *multierror.Error {
|
||||
queriesRaw, ok := top["queries"]
|
||||
if !ok {
|
||||
if result.IsNoTeam() {
|
||||
if ok {
|
||||
logFn("[!] 'queries' is not supported for \"No team\". This key will be ignored.")
|
||||
}
|
||||
return multiError
|
||||
} else if !ok {
|
||||
return multierror.Append(multiError, errors.New("'queries' key is required"))
|
||||
}
|
||||
var queries []Query
|
||||
@@ -593,7 +645,11 @@ func parseQueries(top map[string]json.RawMessage, result *GitOps, baseDir string
|
||||
|
||||
func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir string, multiError *multierror.Error) *multierror.Error {
|
||||
softwareRaw, ok := top["software"]
|
||||
if !ok {
|
||||
if result.global() {
|
||||
if ok && string(softwareRaw) != "null" {
|
||||
return multierror.Append(multiError, errors.New("'software' cannot be set on global file"))
|
||||
}
|
||||
} else if !ok {
|
||||
return multierror.Append(multiError, errors.New("'software' is required"))
|
||||
}
|
||||
var software Software
|
||||
|
||||
+60
-14
@@ -53,9 +53,22 @@ func createTempFile(t *testing.T, pattern, contents string) (filePath string, ba
|
||||
return tmpFile.Name(), filepath.Dir(tmpFile.Name())
|
||||
}
|
||||
|
||||
func createNamedFileOnTempDir(t *testing.T, name string, contents string) (filePath string, baseDir string) {
|
||||
tmpFilePath := filepath.Join(t.TempDir(), name)
|
||||
tmpFile, err := os.Create(tmpFilePath)
|
||||
require.NoError(t, err)
|
||||
_, err = tmpFile.WriteString(contents)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tmpFile.Close())
|
||||
return tmpFile.Name(), filepath.Dir(tmpFile.Name())
|
||||
}
|
||||
|
||||
func gitOpsFromString(t *testing.T, s string) (*GitOps, error) {
|
||||
path, basePath := createTempFile(t, "", s)
|
||||
return GitOpsFromFile(path, basePath, nil)
|
||||
return GitOpsFromFile(path, basePath, nil, nopLogf)
|
||||
}
|
||||
|
||||
func nopLogf(_ string, _ ...interface{}) {
|
||||
}
|
||||
|
||||
func TestValidGitOpsYaml(t *testing.T) {
|
||||
@@ -106,8 +119,6 @@ func TestValidGitOpsYaml(t *testing.T) {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
t.Parallel()
|
||||
}
|
||||
|
||||
var appConfig *fleet.EnrichedAppConfig
|
||||
@@ -118,7 +129,7 @@ func TestValidGitOpsYaml(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
gitops, err := GitOpsFromFile(test.filePath, "./testdata", appConfig)
|
||||
gitops, err := GitOpsFromFile(test.filePath, "./testdata", appConfig, nopLogf)
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.isTeam {
|
||||
@@ -142,6 +153,14 @@ func TestValidGitOpsYaml(t *testing.T) {
|
||||
require.Len(t, secrets.([]*fleet.EnrollSecret), 2)
|
||||
assert.Equal(t, "SampleSecret123", secrets.([]*fleet.EnrollSecret)[0].Secret)
|
||||
assert.Equal(t, "ABC", secrets.([]*fleet.EnrollSecret)[1].Secret)
|
||||
require.Len(t, gitops.Software.Packages, 2)
|
||||
for _, pkg := range gitops.Software.Packages {
|
||||
if strings.Contains(pkg.URL, "MicrosoftTeams") {
|
||||
assert.Equal(t, "uninstall.sh", pkg.UninstallScript.Path)
|
||||
} else {
|
||||
assert.Empty(t, pkg.UninstallScript.Path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check org settings
|
||||
serverSettings, ok := gitops.OrgSettings["server_settings"]
|
||||
@@ -443,14 +462,44 @@ func TestInvalidGitOpsYaml(t *testing.T) {
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "must have a 'secret' key")
|
||||
|
||||
// Missing team_settings.
|
||||
config = getConfig([]string{"team_settings"})
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "'team_settings' is required when 'name' is provided")
|
||||
|
||||
// team_settings set on a "no-team.yml".
|
||||
config = getConfig([]string{"name"})
|
||||
config += "name: No team\n"
|
||||
noTeamPath1, noTeamBasePath1 := createNamedFileOnTempDir(t, "no-team.yml", config)
|
||||
_, err = GitOpsFromFile(noTeamPath1, noTeamBasePath1, nil, nopLogf)
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("cannot set 'team_settings' on 'No team' file: %q", noTeamPath1))
|
||||
|
||||
// 'No team' file with invalid name.
|
||||
config = getConfig([]string{"name", "team_settings"})
|
||||
config += "name: No team\n"
|
||||
noTeamPath2, noTeamBasePath2 := createNamedFileOnTempDir(t, "foobar.yml", config)
|
||||
_, err = GitOpsFromFile(noTeamPath2, noTeamBasePath2, nil, nopLogf)
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("file %q for 'No team' must be named 'no-team.yml'", noTeamPath2))
|
||||
|
||||
// Missing secrets
|
||||
config = getConfig([]string{"team_settings"})
|
||||
config += "team_settings:\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "'team_settings.secrets' is required")
|
||||
} else {
|
||||
// 'software' is not allowed in global config
|
||||
config := getConfig(nil)
|
||||
config += "software:\n packages:\n - url: https://example.com\n"
|
||||
path1, basePath1 := createTempFile(t, "", config)
|
||||
appConfig := fleet.EnrichedAppConfig{}
|
||||
appConfig.License = &fleet.LicenseInfo{
|
||||
Tier: fleet.TierPremium,
|
||||
}
|
||||
_, err = GitOpsFromFile(path1, basePath1, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err, "'software' cannot be set on global file")
|
||||
|
||||
// Invalid org_settings
|
||||
config := getConfig([]string{"org_settings"})
|
||||
config = getConfig([]string{"org_settings"})
|
||||
config += "org_settings:\n path: [2]\n"
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal org_settings")
|
||||
@@ -595,9 +644,6 @@ func TestTopLevelGitOpsValidation(t *testing.T) {
|
||||
"missing_all": {
|
||||
optsToExclude: []string{"controls", "queries", "policies", "agent_options", "org_settings"},
|
||||
},
|
||||
"missing_controls": {
|
||||
optsToExclude: []string{"controls"},
|
||||
},
|
||||
"missing_queries": {
|
||||
optsToExclude: []string{"queries"},
|
||||
},
|
||||
@@ -724,7 +770,7 @@ func TestGitOpsPaths(t *testing.T) {
|
||||
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil, nopLogf)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test a bad path
|
||||
@@ -737,7 +783,7 @@ func TestGitOpsPaths(t *testing.T) {
|
||||
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil, nopLogf)
|
||||
assert.ErrorContains(t, err, "no such file or directory")
|
||||
|
||||
// Test a bad file -- cannot be unmarshalled
|
||||
@@ -772,7 +818,7 @@ func TestGitOpsPaths(t *testing.T) {
|
||||
}
|
||||
err = os.WriteFile(mainTmpFile.Name(), []byte(config), 0o644)
|
||||
require.NoError(t, err)
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil)
|
||||
_, err = GitOpsFromFile(mainTmpFile.Name(), dir, nil, nopLogf)
|
||||
assert.ErrorContains(t, err, "nested paths are not supported")
|
||||
},
|
||||
)
|
||||
@@ -830,7 +876,7 @@ software:
|
||||
Tier: fleet.TierPremium,
|
||||
}
|
||||
path, basePath := createTempFile(t, "", config)
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig)
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("software URL \"%s\" is too long, must be less than 256 characters", tooBigURL))
|
||||
|
||||
// Policy references a software installer not present in the team.
|
||||
@@ -857,7 +903,7 @@ software:
|
||||
0o755,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig)
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err,
|
||||
"install_software.package_path URL https://statics.teams.cdn.office.net/production-osx/enterprise/webview2/lkg/MicrosoftTeams.pkg not found on team",
|
||||
)
|
||||
@@ -889,7 +935,7 @@ software:
|
||||
appConfig.License = &fleet.LicenseInfo{
|
||||
Tier: fleet.TierPremium,
|
||||
}
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig)
|
||||
_, err = GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err, "failed to unmarshal install_software.package_path file")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
url: https://statics.teams.cdn.office.net/production-osx/enterprise/webview2/lkg/MicrosoftTeams.pkg
|
||||
self_service: false
|
||||
uninstall_script:
|
||||
path: uninstall.sh
|
||||
|
||||
Reference in New Issue
Block a user