Files
Lucas Manuel Rodriguez ada55da5bd Drop Docker fleetdm/bomutils dependency in fleetctl when generating pkg fleetd installers (#48915)
Resolves #48448.

These should help with reviewing the XAR and BOM implementations:
- https://claude.ai/code/artifact/60a78c1d-2fc9-45da-9471-1517fe77adb4.
- https://claude.ai/code/artifact/1c759a32-02f7-4a41-8611-04d7358367d7.

The darwin only tests (bom_darwin_test.go) have been executed on my
workstation.
Goal is to make sure to run the tests on macOS Github runners in
https://github.com/fleetdm/fleet/issues/33371.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] QA'd all new/changed functionality manually

## fleetd/orbit/Fleet Desktop

- [x] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [x] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* macOS package builds now use an internal, built-in implementation
instead of external packaging tools.
* `.pkg` installer creation no longer depends on Docker for macOS
packaging.

* **Bug Fixes**
* Improved packaging reliability by reducing platform-specific build
steps.
* Packaging test coverage was streamlined to better match the supported
build environment.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 10:14:57 -03:00

131 lines
4.9 KiB
Go

package packaging
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/stretchr/testify/require"
)
// These tests exercise the pure-Go xar writer (writeXar) by feeding its output
// to Fleet's own xar decoder in pkg/file. A xar that round-trips through the
// decoder proves the encoder produces a well-formed header, a valid zlib TOC,
// and correct heap offsets/lengths (the decoder reads members back by those
// offsets). The writer is platform-independent, so these run everywhere.
const testDistribution = `<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="2">
<title>Fleet osquery</title>
<product id="com.fleetdm.orbit" version="1.2.3"/>
<pkg-ref id="com.fleetdm.orbit.base.pkg" version="1.2.3" packageIdentifier="com.fleetdm.orbit"/>
</installer-gui-script>
`
const testPackageInfo = `<pkg-info format-version="2" identifier="com.fleetdm.orbit" version="9.9.9" install-location="/" auth="root"/>
`
// writeXarTree writes the given name->contents map (names may contain "/" to
// create nested directories) into a fresh temp dir and returns its path.
func writeXarTree(t *testing.T, files map[string][]byte) string {
t.Helper()
root := t.TempDir()
for name, data := range files {
p := filepath.Join(root, name)
require.NoError(t, os.MkdirAll(filepath.Dir(p), 0o755))
require.NoError(t, os.WriteFile(p, data, 0o644))
}
return root
}
// buildXar runs writeXar over root and returns the archive bytes.
func buildXar(t *testing.T, root string) []byte {
t.Helper()
out := filepath.Join(t.TempDir(), "out.pkg")
require.NoError(t, writeXar(root, out))
b, err := os.ReadFile(out)
require.NoError(t, err)
return b
}
func extractXARMetadata(t *testing.T, xarBytes []byte) *file.InstallerMetadata {
t.Helper()
tfr, err := fleet.NewTempFileReader(bytes.NewReader(xarBytes), t.TempDir)
require.NoError(t, err)
t.Cleanup(func() { _ = tfr.Close() })
meta, err := file.ExtractXARMetadata(tfr)
require.NoError(t, err)
return meta
}
// TestWriteXarReadableByDecoder builds a distribution-style .pkg tree and
// verifies the pure-Go writer's output is a valid xar: parseable header + TOC,
// a discoverable Distribution member, and metadata read back correctly from the
// heap.
func TestWriteXarReadableByDecoder(t *testing.T) {
root := writeXarTree(t, map[string][]byte{
"Distribution": []byte(testDistribution),
"base.pkg/PackageInfo": []byte(testPackageInfo),
"base.pkg/Payload": bytes.Repeat([]byte("orbit-payload-bytes\n"), 1000),
})
xarBytes := buildXar(t, root)
// Valid, unsigned xar: exercises magic-byte check, SHA-1 hash-type mapping,
// zlib TOC decompression, and TOC XML parsing.
require.ErrorIs(t, file.CheckPKGSignature(bytes.NewReader(xarBytes)), file.ErrNotSigned)
// The TOC lists the top-level Distribution file.
hasDist, err := file.XARHasDistribution(bytes.NewReader(xarBytes))
require.NoError(t, err)
require.True(t, hasDist)
// Reading the Distribution member back (via its <offset>/<length> within the
// heap that begins with the 20-byte TOC checksum) yields exactly the bytes we
// wrote; if any offset/length were off the XML parse would fail.
meta := extractXARMetadata(t, xarBytes)
require.Equal(t, "Fleet osquery", meta.Name)
require.Equal(t, "1.2.3", meta.Version)
require.Equal(t, "com.fleetdm.orbit", meta.BundleIdentifier)
require.Contains(t, meta.PackageIDs, "com.fleetdm.orbit")
}
// TestWriteXarPackageInfoFallback verifies a component-style .pkg (top-level
// PackageInfo, no Distribution) also round-trips: the decoder falls back to
// PackageInfo, which again requires the writer's heap offsets to be correct.
func TestWriteXarPackageInfoFallback(t *testing.T) {
root := writeXarTree(t, map[string][]byte{
"PackageInfo": []byte(testPackageInfo),
})
xarBytes := buildXar(t, root)
hasDist, err := file.XARHasDistribution(bytes.NewReader(xarBytes))
require.NoError(t, err)
require.False(t, hasDist)
meta := extractXARMetadata(t, xarBytes)
require.Equal(t, "9.9.9", meta.Version)
require.Equal(t, "com.fleetdm.orbit", meta.BundleIdentifier)
}
// TestWriteXarEmptyAndNestedDirs ensures the writer handles empty files and
// nested directories without corrupting the archive (the decoder still parses
// the header/TOC and finds the Distribution).
func TestWriteXarEmptyAndNestedDirs(t *testing.T) {
root := writeXarTree(t, map[string][]byte{
"Distribution": []byte(testDistribution),
"base.pkg/empty": {},
"base.pkg/nested/deep/Info.plist": []byte("<plist/>"),
})
// An empty directory too (map above only creates dirs with files).
require.NoError(t, os.MkdirAll(filepath.Join(root, "Resources"), 0o755))
xarBytes := buildXar(t, root)
require.ErrorIs(t, file.CheckPKGSignature(bytes.NewReader(xarBytes)), file.ErrNotSigned)
meta := extractXARMetadata(t, xarBytes)
require.Equal(t, "Fleet osquery", meta.Name)
}