// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "fmt" "io" "os" "strings" "time" "github.com/hashicorp/nomad/api" "github.com/posener/complete" ) type OperatorSnapshotSaveCommand struct { Meta } func (c *OperatorSnapshotSaveCommand) Help() string { helpText := ` Usage: nomad operator snapshot save [options] Retrieves an atomic, point-in-time snapshot of the state of the Nomad servers which includes jobs, nodes, allocations, periodic jobs, and ACLs. If ACLs are enabled, a management token must be supplied in order to perform snapshot operations. To create a snapshot from the leader server and save it to "backup.snap": $ nomad snapshot save backup.snap To create a potentially stale snapshot from any available server (useful if no leader is available): $ nomad snapshot save -stale backup.snap This is useful for situations where a cluster is in a degraded state and no leader is available. To target a specific server for a snapshot, you can run the 'nomad operator snapshot save' command on that specific server. General Options: ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + ` Snapshot Save Options: -stale=[true|false] The -stale argument defaults to "false" which means the leader provides the result. If the cluster is in an outage state without a leader, you may need to set -stale to "true" to get the configuration from a non-leader server. ` return strings.TrimSpace(helpText) } func (c *OperatorSnapshotSaveCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ "-stale": complete.PredictAnything, }) } func (c *OperatorSnapshotSaveCommand) AutocompleteArgs() complete.Predictor { return complete.PredictNothing } func (c *OperatorSnapshotSaveCommand) Synopsis() string { return "Saves snapshot of Nomad server state" } func (c *OperatorSnapshotSaveCommand) Name() string { return "operator snapshot save" } func (c *OperatorSnapshotSaveCommand) Run(args []string) int { var stale bool flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&stale, "stale", false, "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse args: %v", err)) return 1 } // Check for misuse // Check that we either got no filename or exactly one. args = flags.Args() if len(args) > 1 { c.Ui.Error("This command takes either no arguments or one: ") c.Ui.Error(commandErrorText(c)) return 1 } now := time.Now() filename := fmt.Sprintf("nomad-state-%04d%02d%0d-%d.snap", now.Year(), now.Month(), now.Day(), now.Unix()) if len(args) == 1 { filename = args[0] } if _, err := os.Lstat(filename); err == nil { c.Ui.Error(fmt.Sprintf("Destination file already exists: %q", filename)) c.Ui.Error(commandErrorText(c)) return 1 } else if !os.IsNotExist(err) { c.Ui.Error(fmt.Sprintf("Unexpected failure checking %q: %v", filename, err)) return 1 } // Set up a client. client, err := c.Meta.Client() if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 } tmpFile, err := os.Create(filename + ".tmp") if err != nil { c.Ui.Error(fmt.Sprintf("Failed to create file: %v", err)) return 1 } // Fetch the current configuration. q := &api.QueryOptions{ AllowStale: stale, } snapIn, err := client.Operator().Snapshot(q) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to get snapshot file: %v", err)) return 1 } defer snapIn.Close() _, err = io.Copy(tmpFile, snapIn) if err != nil { c.Ui.Error(fmt.Sprintf("Filed to download snapshot file: %v", err)) return 1 } err = os.Rename(tmpFile.Name(), filename) if err != nil { c.Ui.Error(fmt.Sprintf("Filed to finalize snapshot file: %v", err)) return 1 } c.Ui.Output(fmt.Sprintf("State file written to %v", filename)) return 0 }