mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
implement a docker image to package orbit natively in Linux (#6504)
Related to #6364 and #6363, this: - Adds a new Docker image, `fleetdm/fleetctl` equipped with all necessary dependencies to build Fleet-osquery binaries for all platforms - Modifies the package generation logic to special case this scenario via an environment variable `FLEETCTL_NATIVE_TOOLING` - Adds a new GitHub workflow to test this There are more details in the README, but part of the special-casing logic is in place to output the binaries to a folder named `build` when they are run with `FLEETCTL_NATIVE_TOOLING`, this is so we can persist the binary generated by the docker container via a bind mount: ```bash docker run -v "$(pwd):/build" fleetdm/fleetctl package --type=msi ``` To test this changeset, I have generated packages for all platforms, both via the new Docker image and via the classic `fleetctl package`.
This commit is contained in:
parent
a336ed61e5
commit
f7dd8c86cd
69
.github/workflows/test-native-tooling-packaging.yml
vendored
Normal file
69
.github/workflows/test-native-tooling-packaging.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
name: Test native tooling packaging
|
||||
|
||||
# This workflow tests packaging of Fleet-osquery with the
|
||||
# `fleetdm/fleetctl` Docker image.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- patch-*
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.go'
|
||||
- 'tools/fleetctl-docker/**'
|
||||
- 'tools/wix-docker/**'
|
||||
- 'tools/bomutils-docker/**'
|
||||
- '.github/workflows/test-native-tooling-packaging.yml'
|
||||
workflow_dispatch: # Manual
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-packaging:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go-version: ['^1.17.8']
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 # v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||
|
||||
- name: Install Go Dependencies
|
||||
run: make deps-go
|
||||
|
||||
- name: Build fleetdm/fleetctl
|
||||
run: make fleetctl-docker
|
||||
|
||||
- name: Build DEB
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type deb --enroll-secret=foo --fleet-url=https://localhost:8080
|
||||
|
||||
- name: Build DEB with Fleet Desktop
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type deb --enroll-secret=foo --fleet-url=https://localhost:8080 --fleet-desktop
|
||||
|
||||
- name: Build RPM
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl --type rpm --enroll-secret=foo --fleet-url=https://localhost:8080
|
||||
|
||||
- name: Build RPM with Fleet Desktop
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type rpm --enroll-secret=foo --fleet-url=https://localhost:8080 --fleet-desktop
|
||||
|
||||
- name: Build MSI
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type msi --enroll-secret=foo --fleet-url=https://localhost:8080
|
||||
|
||||
- name: Build MSI with Fleet Desktop
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type msi --enroll-secret=foo --fleet-url=https://localhost:8080 --fleet-desktop
|
||||
|
||||
- name: Build PKG
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type pkg --enroll-secret=foo --fleet-url=https://localhost:8080
|
||||
|
||||
- name: Build PKG with Fleet Desktop
|
||||
run: docker run -v "$(pwd):/build" fleetdm/fleetctl package --type pkg --enroll-secret=foo --fleet-url=https://localhost:8080 --fleet-desktop
|
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: Test packaging
|
||||
|
||||
# This workflow tests packaging of Fleet-osquery with the
|
||||
# `fleetctl package' command. It fetches the targets: orbit,
|
||||
# `fleetctl package` command. It fetches the targets: orbit,
|
||||
# osquery and fleet-desktop from the default (Fleet's) TUF server,
|
||||
# https://tuf.fleetctl.com.
|
||||
|
||||
|
3
Makefile
3
Makefile
@ -205,6 +205,9 @@ docker-push-release: docker-build-release
|
||||
docker push fleetdm/fleet:${VERSION}
|
||||
docker push fleetdm/fleet:latest
|
||||
|
||||
fleetctl-docker: xp-fleetctl
|
||||
docker build -t fleetdm/fleetctl --platform=linux/amd64 -f tools/fleetctl-docker/Dockerfile .
|
||||
|
||||
.pre-binary-bundle:
|
||||
rm -rf build/binary-bundle
|
||||
mkdir -p build/binary-bundle/linux
|
||||
|
@ -144,6 +144,12 @@ func packageCommand() *cli.Command {
|
||||
Usage: "Disable opening the folder at the end",
|
||||
Destination: &disableOpenFolder,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "native-tooling",
|
||||
Usage: "Build the package using native tooling (only available in Linux)",
|
||||
EnvVars: []string{"FLEETCTL_NATIVE_TOOLING"},
|
||||
Destination: &opt.NativeTooling,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if opt.FleetURL != "" || opt.EnrollSecret != "" {
|
||||
@ -160,6 +166,10 @@ func packageCommand() *cli.Command {
|
||||
return errors.New("Windows can only build MSI packages.")
|
||||
}
|
||||
|
||||
if opt.NativeTooling && runtime.GOOS != "linux" {
|
||||
return errors.New("native tooling is only available in Linux")
|
||||
}
|
||||
|
||||
if opt.FleetCertificate != "" {
|
||||
err := checkPEMCertificate(opt.FleetCertificate)
|
||||
if err != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
|
||||
@ -38,6 +39,10 @@ func TestPackage(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
runAppCheckErr(t, []string{"package", "--type=deb", fmt.Sprintf("--fleet-certificate=%s", fleetCertificate)}, fmt.Sprintf("failed to read certificate %q: invalid PEM file", fleetCertificate))
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
runAppCheckErr(t, []string{"package", "--type=msi", "--native-tooling"}, "native on non-linux platforms fails")
|
||||
}
|
||||
|
||||
t.Run("deb", func(t *testing.T) {
|
||||
runAppForTest(t, []string{"package", "--type=deb", "--insecure", "--disable-open-folder"})
|
||||
info, err := os.Stat(fmt.Sprintf("fleet-osquery_%s_amd64.deb", updatesData.OrbitVersion))
|
||||
|
@ -184,6 +184,9 @@ func buildNFPM(opt Options, pkger nfpm.Packager) (string, error) {
|
||||
},
|
||||
}
|
||||
filename := pkger.ConventionalFileName(info)
|
||||
if opt.NativeTooling {
|
||||
filename = filepath.Join("build", filename)
|
||||
}
|
||||
|
||||
out, err := secure.OpenFile(filename, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode)
|
||||
if err != nil {
|
||||
|
@ -131,6 +131,9 @@ func BuildPkg(opt Options) (string, error) {
|
||||
}
|
||||
|
||||
filename := "fleet-osquery.pkg"
|
||||
if opt.NativeTooling {
|
||||
filename = filepath.Join("build", filename)
|
||||
}
|
||||
if err := file.Copy(generatedPath, filename, constant.DefaultFileMode); err != nil {
|
||||
return "", fmt.Errorf("rename pkg: %w", err)
|
||||
}
|
||||
@ -248,8 +251,11 @@ func xarBom(opt Options, rootPath string) error {
|
||||
|
||||
// Make bom
|
||||
var cmdMkbom *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
var isDarwin = runtime.GOOS == "darwin"
|
||||
var isLinuxNative = runtime.GOOS == "linux" && opt.NativeTooling
|
||||
|
||||
switch {
|
||||
case isDarwin, isLinuxNative:
|
||||
cmdMkbom = exec.Command("mkbom", filepath.Join(rootPath, "root"), filepath.Join("flat", "base.pkg", "Bom"))
|
||||
cmdMkbom.Dir = rootPath
|
||||
default:
|
||||
@ -261,8 +267,8 @@ func xarBom(opt Options, rootPath string) error {
|
||||
"/root/root", "/root/flat/base.pkg/Bom",
|
||||
)
|
||||
}
|
||||
cmdMkbom.Stdout = os.Stdout
|
||||
cmdMkbom.Stderr = os.Stderr
|
||||
|
||||
cmdMkbom.Stdout, cmdMkbom.Stderr = os.Stdout, os.Stderr
|
||||
if err := cmdMkbom.Run(); err != nil {
|
||||
return fmt.Errorf("mkbom: %w", err)
|
||||
}
|
||||
@ -286,8 +292,8 @@ func xarBom(opt Options, rootPath string) error {
|
||||
|
||||
// Make xar
|
||||
var cmdXar *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
switch {
|
||||
case isDarwin, isLinuxNative:
|
||||
cmdXar = exec.Command("xar", append([]string{"--compression", "none", "-cf", filepath.Join("..", "orbit.pkg")}, files...)...)
|
||||
cmdXar.Dir = filepath.Join(rootPath, "flat")
|
||||
default:
|
||||
@ -297,9 +303,8 @@ func xarBom(opt Options, rootPath string) error {
|
||||
)
|
||||
cmdXar.Args = append(cmdXar.Args, append([]string{"--compression", "none", "-cf", "/root/orbit.pkg"}, files...)...)
|
||||
}
|
||||
cmdXar.Stdout = os.Stdout
|
||||
cmdXar.Stderr = os.Stderr
|
||||
|
||||
cmdXar.Stdout, cmdXar.Stderr = os.Stdout, os.Stderr
|
||||
if err := cmdXar.Run(); err != nil {
|
||||
return fmt.Errorf("run xar: %w", err)
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ type Options struct {
|
||||
// LegacyVarLibSymlink indicates whether Orbit is legacy (< 0.0.11),
|
||||
// which assumes it is installed under /var/lib.
|
||||
LegacyVarLibSymlink bool
|
||||
// Native tooling is used to determine if the package should be built
|
||||
// natively instead of via Docker images.
|
||||
NativeTooling bool
|
||||
}
|
||||
|
||||
func initializeTempDir() (string, error) {
|
||||
|
@ -100,7 +100,7 @@ func BuildMSI(opt Options) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := wix.Heat(tmpDir); err != nil {
|
||||
if err := wix.Heat(tmpDir, opt.NativeTooling); err != nil {
|
||||
return "", fmt.Errorf("package root files: %w", err)
|
||||
}
|
||||
|
||||
@ -108,15 +108,18 @@ func BuildMSI(opt Options) (string, error) {
|
||||
return "", fmt.Errorf("transform heat: %w", err)
|
||||
}
|
||||
|
||||
if err := wix.Candle(tmpDir); err != nil {
|
||||
if err := wix.Candle(tmpDir, opt.NativeTooling); err != nil {
|
||||
return "", fmt.Errorf("build package: %w", err)
|
||||
}
|
||||
|
||||
if err := wix.Light(tmpDir); err != nil {
|
||||
if err := wix.Light(tmpDir, opt.NativeTooling); err != nil {
|
||||
return "", fmt.Errorf("build package: %w", err)
|
||||
}
|
||||
|
||||
filename := "fleet-osquery.msi"
|
||||
if opt.NativeTooling {
|
||||
filename = filepath.Join("build", filename)
|
||||
}
|
||||
if err := file.Copy(filepath.Join(tmpDir, "orbit.msi"), filename, constant.DefaultFileMode); err != nil {
|
||||
return "", fmt.Errorf("rename msi: %w", err)
|
||||
}
|
||||
|
@ -7,30 +7,54 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
directoryReference = "ORBITROOT"
|
||||
directoryReference = "ORBITROOT"
|
||||
linuxPathSeparator = "/"
|
||||
windowsPathSeparator = "\\"
|
||||
)
|
||||
|
||||
// windowsJoin returns the result of replacing each slash ('/') character in
|
||||
// each path with a Windows separator character ('\') and joining them using
|
||||
// the Windows separator character.
|
||||
//
|
||||
// We can't use filepath.FromSlash because this func is run in a *nix
|
||||
// machine.
|
||||
func windowsJoin(paths ...string) string {
|
||||
s := strings.Join(paths, windowsPathSeparator)
|
||||
return strings.ReplaceAll(s, linuxPathSeparator, windowsPathSeparator)
|
||||
}
|
||||
|
||||
// Heat runs the WiX Heat command on the provided directory.
|
||||
//
|
||||
// The Heat command creates XML fragments allowing WiX to include the entire
|
||||
// directory. See
|
||||
// https://wixtoolset.org/documentation/manual/v3/overview/heat.html.
|
||||
func Heat(path string) error {
|
||||
cmd := exec.Command(
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":/wix", // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
"heat", "dir", "root", // command in image
|
||||
"-out", "heat.wxs",
|
||||
func Heat(path string, native bool) error {
|
||||
var args []string
|
||||
|
||||
if !native {
|
||||
args = append(
|
||||
args,
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":"+path, // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
)
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
"heat", "dir", windowsJoin(path, "root"), // command
|
||||
"-out", windowsJoin(path, "heat.wxs"),
|
||||
"-gg", "-g1", // generate UUIDs (required by wix)
|
||||
"-cg", "OrbitFiles", // set ComponentGroup name
|
||||
"-scom", "-sfrag", "-srd", "-sreg", // suppress unneccesary generated items
|
||||
"-dr", directoryReference, // set reference name
|
||||
"-ke", // keep empty directories
|
||||
)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
@ -44,15 +68,26 @@ func Heat(path string) error {
|
||||
//
|
||||
// See
|
||||
// https://wixtoolset.org/documentation/manual/v3/overview/candle.html.
|
||||
func Candle(path string) error {
|
||||
cmd := exec.Command(
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":/wix", // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
"candle", "heat.wxs", "main.wxs", // command in image
|
||||
func Candle(path string, native bool) error {
|
||||
var args []string
|
||||
|
||||
if !native {
|
||||
args = append(
|
||||
args,
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":"+path, // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
)
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
"candle", windowsJoin(path, "heat.wxs"), windowsJoin(path, "main.wxs"), // command
|
||||
"-out", windowsJoin(path, ""),
|
||||
"-ext", "WixUtilExtension",
|
||||
"-arch", "x64",
|
||||
)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
@ -66,17 +101,27 @@ func Candle(path string) error {
|
||||
//
|
||||
// See
|
||||
// https://wixtoolset.org/documentation/manual/v3/overview/light.html.
|
||||
func Light(path string) error {
|
||||
cmd := exec.Command(
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":/wix", // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
"light", "heat.wixobj", "main.wixobj", // command in image
|
||||
func Light(path string, native bool) error {
|
||||
var args []string
|
||||
|
||||
if !native {
|
||||
args = append(
|
||||
args,
|
||||
"docker", "run", "--rm", "--platform", "linux/amd64",
|
||||
"--volume", path+":"+path, // mount volume
|
||||
"fleetdm/wix:latest", // image name
|
||||
)
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
"light", windowsJoin(path, "heat.wixobj"), windowsJoin(path, "main.wixobj"), // command
|
||||
"-ext", "WixUtilExtension",
|
||||
"-b", "root", // Set directory for finding heat files
|
||||
"-out", "orbit.msi",
|
||||
"-b", windowsJoin(path, "root"), // Set directory for finding heat files
|
||||
"-out", windowsJoin(path, "orbit.msi"),
|
||||
"-sval", // skip validation (otherwise Wine crashes)
|
||||
)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
23
orbit/pkg/packaging/wix/wix_test.go
Normal file
23
orbit/pkg/packaging/wix/wix_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package wix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWindowsJoin(t *testing.T) {
|
||||
cases := []struct {
|
||||
in []string
|
||||
out string
|
||||
}{
|
||||
{[]string{"one\\two", "three"}, "one\\two\\three"},
|
||||
{[]string{"one/two/three", "four.txt"}, "one\\two\\three\\four.txt"},
|
||||
{[]string{"one", "two", "three"}, "one\\two\\three"},
|
||||
{[]string{"one/two/three", "four/five.txt"}, "one\\two\\three\\four\\five.txt"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
require.Equal(t, c.out, windowsJoin(c.in...))
|
||||
}
|
||||
}
|
21
tools/fleetctl-docker/Dockerfile
Normal file
21
tools/fleetctl-docker/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM debian:stable-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& dpkg --add-architecture i386 \
|
||||
&& apt update \
|
||||
&& apt install -y --no-install-recommends ca-certificates cpio libxml2 wine wine32 libgtk-3-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# copy macOS dependencies
|
||||
COPY --from=fleetdm/bomutils:latest /usr/bin/mkbom /usr/local/bin/xar /usr/bin/
|
||||
COPY --from=fleetdm/bomutils:latest /usr/local/lib /usr/local/lib/
|
||||
|
||||
# copy Windows dependencies
|
||||
COPY --from=fleetdm/wix:latest /home/wine /home/wine
|
||||
|
||||
# copy fleetctl
|
||||
COPY build/binary-bundle/linux/fleetctl /usr/bin/fleetctl
|
||||
|
||||
ENV FLEETCTL_NATIVE_TOOLING=1 WINEPREFIX=/home/wine/.wine WINEARCH=win32 PATH="/home/wine/bin:$PATH" WINEDEBUG=-all
|
||||
|
||||
ENTRYPOINT ["fleetctl"]
|
25
tools/fleetctl-docker/README.md
Normal file
25
tools/fleetctl-docker/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
## fleetdm/fleetctl
|
||||
|
||||
This docker image allows to run `fleetctl` in a Linux environment that has all
|
||||
the necessary dependencies to package `msi`, `pkg`, `deb` and `rpm` packages.
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
docker run fleetdm/fleetctl command [flags]
|
||||
```
|
||||
|
||||
Build artifacts are generated at `/build`. To get a package using this image:
|
||||
|
||||
```
|
||||
docker run -v "$(pwd):/build" fleetdm/fleetctl package --type=msi
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
This image needs to be built from the root of the repo in order for the build
|
||||
context to have access to the `fleetctl` binary. To build the image, run:
|
||||
|
||||
```
|
||||
make fleetctl-docker
|
||||
```
|
Loading…
Reference in New Issue
Block a user