mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
FDM updates: fdm serve, snapshot/restore improvements (#27890)
For #27889 This PR introduces several improvements to the Makefile/`fdm` tool for development: ### `fdm serve` (alias `fdm up`) Starts a local Fleet server (building the binary first). The first time this is called, it will start the server on `localhost:8080` with the `--dev` and `--dev_license` flags, but the command accepts all of the options that you can pass to `fleet serve`. If you pass options to `fdm serve`, then subsequent invocations _without_ options will replay your last command. Additionally, `fdm serve` supports the following: - `--use-ip`: start the local server on your system's local IP address rather than `localhost`. This makes it easier to point VMs on your system to the fleet server to act as hosts. - `--no-build`: don't rebuild the fleet binary before starting the server. - `--no-save`: don't save the current command for future invocations (useful for scripting) - `--show`: show options for the last-invoked `fdm serve` command - `--reset`: reset the options for `fdm serve`. The next time `fdm serve` is invoked, it will use the default options. - `--help`: show all of the Fleet server options ### `fdm snapshot` improvements * Added `fdm snap` alias * Tracks the name of the last snapshot saved, to use as the default for `fdm restore` * Suppresses the "don't use password in CLI" warning when saving the snapshot ### `fdm restore` improvements * Added `--prep` / `--prepare` option to run db migrations after restoring snapshot. * Improved UI (more options displayed, and clearer indicator for selected option) * Now defaults to last snapshot restored
This commit is contained in:
parent
df61e6b7f5
commit
d51f2815ad
4 changed files with 141 additions and 12 deletions
102
Makefile
102
Makefile
|
|
@ -59,6 +59,15 @@ LDFLAGS_VERSION = "\
|
|||
-X github.com/fleetdm/fleet/v4/server/version.buildUser=${USER} \
|
||||
-X github.com/fleetdm/fleet/v4/server/version.goVersion=${GOVERSION}"
|
||||
|
||||
# Macro to allow targets to filter out their own arguments from the arguments
|
||||
# passed to the final command.
|
||||
# Targets may also add their own CLI arguments to the command as EXTRA_CLI_ARGS.
|
||||
# See `serve` target for an example.
|
||||
define filter_args
|
||||
$(eval FORWARDED_ARGS := $(filter-out $(TARGET_ARGS), $(CLI_ARGS)))
|
||||
$(eval FORWARDED_ARGS := $(FORWARDED_ARGS) $(EXTRA_CLI_ARGS))
|
||||
endef
|
||||
|
||||
all: build
|
||||
|
||||
|
||||
|
|
@ -112,6 +121,71 @@ fdm:
|
|||
sudo ln -sf "$$(pwd)/build/fdm" /usr/local/bin/fdm; \
|
||||
fi
|
||||
|
||||
.help-short--serve:
|
||||
@echo "Start the fleet server"
|
||||
.help-short--up:
|
||||
@echo "Start the fleet server (alias for \`serve\`)"
|
||||
.help-long--serve: SERVE_CMD:=serve
|
||||
.help-long--up: SERVE_CMD:=up
|
||||
.help-long--serve .help-long--up:
|
||||
@echo "Starts an instance of the Fleet web and API server."
|
||||
@echo
|
||||
@echo " By default the server will listen on localhost:8080, in development mode with a premium license."
|
||||
@echo " If different options are used to start the server, the options will become 'sticky' and will be used the next time \`$(TOOL_CMD) $(SERVE_CMD)\` is called."
|
||||
@echo
|
||||
@echo " To see all available options, run \`$(TOOL_CMD) $(SERVE_CMD) --help\`"
|
||||
.help-options--serve .help-options--up:
|
||||
@echo "HELP"
|
||||
@echo "Show all options for the fleet serve command"
|
||||
@echo "USE_IP"
|
||||
@echo "Start the server on the IP address of the host machine"
|
||||
@echo "NO_BUILD"
|
||||
@echo "Don't build the fleet binary before starting the server"
|
||||
@echo "NO_SAVE"
|
||||
@echo "Don't save the current arguments for the next invocation"
|
||||
@echo "SHOW"
|
||||
@echo "Show the last arguments used to start the server"
|
||||
|
||||
up: SERVE_CMD:=up
|
||||
up: serve
|
||||
serve: SERVE_CMD:=serve
|
||||
serve: TARGET_ARGS := --use-ip --no-save --show --no-build
|
||||
ifdef USE_IP
|
||||
serve: EXTRA_CLI_ARGS := $(EXTRA_CLI_ARGS) --server_address=$(shell ipconfig getifaddr en0):8080
|
||||
endif
|
||||
ifdef SHOW
|
||||
serve:
|
||||
@SAVED_ARGS=$$(cat ~/.fleet/last-serve-invocation); \
|
||||
if [[ $$? -eq 0 ]]; then \
|
||||
echo "$$SAVED_ARGS"; \
|
||||
fi
|
||||
else ifdef HELP
|
||||
serve:
|
||||
@./build/fleet serve --help
|
||||
else ifdef RESET
|
||||
serve:
|
||||
@touch ~/.fleet/last-serve-invocation && rm ~/.fleet/last-serve-invocation
|
||||
else
|
||||
serve:
|
||||
@if [[ "$(NO_BUILD)" != "true" ]]; then make fleet; fi
|
||||
$(call filter_args)
|
||||
# If FORWARDED_ARGS is not empty, run the command with the forwarded arguments.
|
||||
# Unless NO_SAVE is set to true, save the command to the last invocation file.
|
||||
# IF FORWARDED_ARGS is empty, attempt to repeat the last invocation.
|
||||
@if [[ "$(FORWARDED_ARGS)" != "" ]]; then \
|
||||
if [[ "$(NO_SAVE)" != "true" ]]; then \
|
||||
echo "./build/fleet serve $(FORWARDED_ARGS)" > ~/.fleet/last-serve-invocation; \
|
||||
fi; \
|
||||
./build/fleet serve $(FORWARDED_ARGS); \
|
||||
else \
|
||||
if ! [[ -f ~/.fleet/last-serve-invocation ]]; then \
|
||||
echo "./build/fleet serve --server_address=localhost:8080 --dev --dev_license" > ~/.fleet/last-serve-invocation; \
|
||||
fi; \
|
||||
cat ~/.fleet/last-serve-invocation; \
|
||||
$$(cat ~/.fleet/last-serve-invocation); \
|
||||
fi
|
||||
endif
|
||||
|
||||
fleet: .prefix .pre-build .pre-fleet
|
||||
CGO_ENABLED=1 go build -race=${GO_BUILD_RACE_ENABLED_VAR} -tags full,fts5,netgo -o build/${OUTPUT} -ldflags ${LDFLAGS_VERSION} ./cmd/fleet
|
||||
|
||||
|
|
@ -500,13 +574,32 @@ db-restore:
|
|||
|
||||
|
||||
# Interactive snapshot / restore
|
||||
.help-short--snap .help-short--snapshot:
|
||||
@echo "Snapshot the database"
|
||||
.help-long--snap .help-long--snapshot:
|
||||
@echo "Interactively take a snapshot of the present database state. Restore snapshots with \`$(TOOL_CMD) restore\`."
|
||||
|
||||
SNAPSHOT_BINARY = ./build/snapshot
|
||||
snapshot: $(SNAPSHOT_BINARY)
|
||||
snap snapshot: $(SNAPSHOT_BINARY)
|
||||
@ $(SNAPSHOT_BINARY) snapshot
|
||||
$(SNAPSHOT_BINARY): tools/snapshot/*.go
|
||||
cd tools/snapshot && go build -o ../../build/snapshot
|
||||
|
||||
.help-short--restore:
|
||||
@echo "Restore a database snapshot"
|
||||
.help-long--restore:
|
||||
@echo "Interactively restore database state using a snapshot taken with \`$(TOOL_CMD) snapshot\`."
|
||||
.help-options--restore:
|
||||
@echo "PREPARE (alias: PREP)"
|
||||
@echo "Run migrations after restoring the snapshot"
|
||||
|
||||
restore: $(SNAPSHOT_BINARY)
|
||||
@ $(SNAPSHOT_BINARY) restore
|
||||
@$(SNAPSHOT_BINARY) restore
|
||||
@if [[ "$(PREP)" == "true" || "$(PREPARE)" == "true" ]]; then \
|
||||
echo "Running migrations..."; \
|
||||
./build/fleet prepare db --dev; \
|
||||
fi
|
||||
@echo Done!
|
||||
|
||||
# Generate osqueryd.app.tar.gz bundle from osquery.io.
|
||||
#
|
||||
|
|
@ -679,7 +772,4 @@ db-replica-reset: fleet
|
|||
db-replica-run: fleet
|
||||
FLEET_MYSQL_ADDRESS=127.0.0.1:3308 FLEET_MYSQL_READ_REPLICA_ADDRESS=127.0.0.1:3309 FLEET_MYSQL_READ_REPLICA_USERNAME=fleet FLEET_MYSQL_READ_REPLICA_DATABASE=fleet FLEET_MYSQL_READ_REPLICA_PASSWORD=insecure ./build/fleet serve --dev --dev_license
|
||||
|
||||
include ./tools/makefile-support/helpsystem-targets
|
||||
|
||||
foo:
|
||||
@echo $(MAKECMDGOALS)
|
||||
include ./tools/makefile-support/helpsystem-targets
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
BACKUP_NAME="${1:-backup.sql.gz}"
|
||||
docker run --rm -i --network fleet_default ${FLEET_MYSQL_IMAGE:-mysql:8.0.36} bash -c 'gzip -dc - | mysql -hmysql -uroot -ptoor fleet' < ${BACKUP_NAME}
|
||||
docker run --rm -i --network fleet_default ${FLEET_MYSQL_IMAGE:-mysql:8.0.36} bash -c 'gzip -dc - | MYSQL_PWD=toor mysql -hmysql -uroot fleet' < ${BACKUP_NAME}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ func main() {
|
|||
func splitArgs(args []string) (map[string]string, []string) {
|
||||
options := make(map[string]string)
|
||||
var makeArgs []string
|
||||
cliArgs := " "
|
||||
positionalArgsIndex := 1
|
||||
isMakeArgs := false
|
||||
skipNext := false
|
||||
|
|
@ -96,13 +97,16 @@ func splitArgs(args []string) (map[string]string, []string) {
|
|||
// Handle options like --name=foo
|
||||
case len(parts) == 2:
|
||||
options[parts[0]] = parts[1]
|
||||
cliArgs += arg + " "
|
||||
// Handle options like --name foo
|
||||
case idx+1 < len(args) && !strings.HasPrefix(args[idx+1], "--"):
|
||||
options[arg[2:]] = args[idx+1]
|
||||
cliArgs += arg + " " + args[idx+1] + " "
|
||||
skipNext = true
|
||||
// Handle options like --useturbocharge by assuming they're booleans.
|
||||
default:
|
||||
options[parts[0]] = "true"
|
||||
cliArgs += arg + " "
|
||||
}
|
||||
// Otherwise assume we're dealing with a positional argument.
|
||||
default:
|
||||
|
|
@ -110,7 +114,7 @@ func splitArgs(args []string) (map[string]string, []string) {
|
|||
positionalArgsIndex++
|
||||
}
|
||||
}
|
||||
|
||||
options["CLI_ARGS"] = cliArgs
|
||||
return options, makeArgs
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ func restore(homedir string) error {
|
|||
// Walk the ~/.fleet/snapshots directory if it exists.
|
||||
dirEntries, err := os.ReadDir(snapshotsDir)
|
||||
var snapshots []Snapshot
|
||||
var lastSnapshotName []byte
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
// Ensure there's a db backup file.
|
||||
|
|
@ -108,20 +109,42 @@ func restore(homedir string) error {
|
|||
Path: dbBackupFile,
|
||||
}
|
||||
snapshots = append(snapshots, snapshot)
|
||||
} else if entry.Name() == "last_snapshot" {
|
||||
// If the entry is the "last_snapshot" file, read its contents
|
||||
lastSnapshotPath := filepath.Join(snapshotsDir, entry.Name())
|
||||
lastSnapshotName, err = os.ReadFile(lastSnapshotPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading last snapshot file (%s): %v\n", lastSnapshotPath, err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Last snapshot: " + string(lastSnapshotName))
|
||||
}
|
||||
}
|
||||
|
||||
// If lastSnapshotName is not empty, find its index in the snapshots list.
|
||||
var lastSnapshotIndex int
|
||||
if len(lastSnapshotName) > 0 {
|
||||
for i, snapshot := range snapshots {
|
||||
if snapshot.Name == string(lastSnapshotName) {
|
||||
lastSnapshotIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up and run the "Select snapshot" UI.
|
||||
templates := &promptui.SelectTemplates{
|
||||
Label: "{{ .Name }}",
|
||||
Active: "> {{ .Name }} ({{ .Date }})",
|
||||
Inactive: "{{ .Name }} ({{ .Date }})",
|
||||
Selected: "{{ .Name }} ({{ .Date }})",
|
||||
Label: " {{ .Name }}",
|
||||
Active: "• {{ .Name }} ({{ .Date }})",
|
||||
Inactive: " {{ .Name }} ({{ .Date }})",
|
||||
Selected: " {{ .Name }} ({{ .Date }})",
|
||||
}
|
||||
prompt := promptui.Select{
|
||||
Label: "Select snapshot to restore",
|
||||
Items: snapshots,
|
||||
Templates: templates,
|
||||
Size: 10,
|
||||
CursorPos: lastSnapshotIndex,
|
||||
}
|
||||
index, _, err := prompt.Run()
|
||||
if err != nil {
|
||||
|
|
@ -146,6 +169,12 @@ func restore(homedir string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Write the selected snapshot name to the "last_snapshot" file.
|
||||
err = os.WriteFile(filepath.Join(snapshotsDir, "last_snapshot"), []byte(snapshots[index].Name), 0o644)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing last snapshot file: %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +258,12 @@ func snapshot(homedir string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Write the selected snapshot name to the "last_snapshot" file.
|
||||
err = os.WriteFile(filepath.Join(snapshotsDir, "last_snapshot"), []byte(result), 0o644)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing last snapshot file: %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue