Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(run): Add the --dry option to the run command #550

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions internals/cli/cmd_run.go
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ package cli
import (
"errors"
"fmt"
"maps"
"os"
"os/signal"
"strconv"
@@ -62,22 +63,29 @@ var sharedRunEnterArgsHelp = map[string]string{
"--args": "Provide additional arguments to a service",
"--identities": "Seed identities from file (like update-identities --replace)",
}
var runArgsHelp = map[string]string{
"--dry": "Initializes {{.DisplayName}} without starting the daemon or side-effects",
}

type cmdRun struct {
client *client.Client

socketPath string
pebbleDir string

DryRun bool `long:"dry"`
sharedRunEnterOpts
}

func init() {
argsHelp := runArgsHelp
maps.Copy(argsHelp, sharedRunEnterArgsHelp)

AddCommand(&CmdInfo{
Name: "run",
Summary: cmdRunSummary,
Description: cmdRunDescription,
ArgsHelp: sharedRunEnterArgsHelp,
ArgsHelp: argsHelp,
New: func(opts *CmdOptions) flags.Commander {
return &cmdRun{
client: opts.Client,
@@ -172,19 +180,25 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
t0 := time.Now().Truncate(time.Millisecond)

if rcmd.CreateDirs {
if rcmd.DryRun {
return errors.New("cannot use --create-dirs and --dry at the same time")
}
err := os.MkdirAll(rcmd.pebbleDir, 0755)
if err != nil {
return err
}
}
err = maybeCopyPebbleDir(rcmd.pebbleDir, getCopySource())
if err != nil {
return err
if !rcmd.DryRun {
err = maybeCopyPebbleDir(rcmd.pebbleDir, getCopySource())
if err != nil {
return err
}
}

dopts := daemon.Options{
Dir: rcmd.pebbleDir,
SocketPath: rcmd.socketPath,
DryRun: rcmd.DryRun,
}
if rcmd.Verbose {
dopts.ServiceOutput = os.Stdout
@@ -195,20 +209,41 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
if err != nil {
return err
}
if err := d.Init(); err != nil {
return err

if !rcmd.DryRun {
if err := d.Init(); err != nil {
return err
}
}

if rcmd.Args != nil {
mappedArgs, err := convertArgs(rcmd.Args)
if err != nil {
return err
}
if rcmd.DryRun {
logger.Noticef("Setting service args: %v", mappedArgs)
}
if err := d.SetServiceArgs(mappedArgs); err != nil {
return err
}
}

var identities map[string]*client.Identity
if rcmd.Identities != "" {
identities, err = readIdentities(rcmd.Identities)
if err != nil {
return fmt.Errorf("cannot read identities: %w", err)
}
}

if rcmd.DryRun {
// If a hook for manager-specific dry run logic is added in the future, it
// should be run immediately above this block.
logger.Noticef("No error encountered: dry-run successful.")
return nil
}

// Run sanity check now, if anything goes wrong with the
// check we go into "degraded" mode where we always report
// the given error to any client.
@@ -238,10 +273,6 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal, ready chan<- func()) error {
logger.Debugf("activation done in %v", time.Now().Truncate(time.Millisecond).Sub(t0))

if rcmd.Identities != "" {
identities, err := readIdentities(rcmd.Identities)
if err != nil {
return fmt.Errorf("cannot read identities: %w", err)
}
err = rcmd.client.ReplaceIdentities(identities)
if err != nil {
return fmt.Errorf("cannot replace identities: %w", err)
4 changes: 4 additions & 0 deletions internals/daemon/daemon.go
Original file line number Diff line number Diff line change
@@ -84,6 +84,9 @@ type Options struct {
// OverlordExtension is an optional interface used to extend the capabilities
// of the Overlord.
OverlordExtension overlord.Extension

// If true, no state will be written to file.
DryRun bool
}

// A Daemon listens for requests and routes them to the right command
@@ -852,6 +855,7 @@ func New(opts *Options) (*Daemon, error) {
RestartHandler: d,
ServiceOutput: opts.ServiceOutput,
Extension: opts.OverlordExtension,
DryRun: opts.DryRun,
}

ovld, err := overlord.New(&ovldOptions)
13 changes: 13 additions & 0 deletions internals/overlord/backend.go
Original file line number Diff line number Diff line change
@@ -34,3 +34,16 @@ func (osb *overlordStateBackend) Checkpoint(data []byte) error {
func (osb *overlordStateBackend) EnsureBefore(d time.Duration) {
osb.ensureBefore(d)
}

// dryRunStateBackend is a backend that does not actually write anything
type dryRunStateBackend struct {
ensureBefore func(d time.Duration)
}

func (b *dryRunStateBackend) Checkpoint(data []byte) error {
return nil
}

func (b *dryRunStateBackend) EnsureBefore(d time.Duration) {
b.ensureBefore(d)
}
20 changes: 17 additions & 3 deletions internals/overlord/overlord.go
Original file line number Diff line number Diff line change
@@ -76,6 +76,9 @@ type Options struct {
ServiceOutput io.Writer
// Extension allows extending the overlord with externally defined features.
Extension Extension
// DryRun must be true if state in storage is not meant to be altered.
// Otherwise, the Overlord will operate normally.
DryRun bool
}

// Overlord is the central manager of the system, keeping track
@@ -106,6 +109,9 @@ type Overlord struct {
logMgr *logstate.LogManager

extension Extension

// If true, no state will be written to file.
DryRun bool
}

// New creates an Overlord with all its state managers.
@@ -116,6 +122,7 @@ func New(opts *Options) (*Overlord, error) {
loopTomb: new(tomb.Tomb),
inited: true,
extension: opts.Extension,
DryRun: opts.DryRun,
}

if !filepath.IsAbs(o.pebbleDir) {
@@ -130,9 +137,16 @@ func New(opts *Options) (*Overlord, error) {

statePath := filepath.Join(o.pebbleDir, cmd.StateFile)

backend := &overlordStateBackend{
path: statePath,
ensureBefore: o.ensureBefore,
var backend state.Backend
if opts.DryRun {
backend = &dryRunStateBackend{
ensureBefore: o.ensureBefore,
}
} else {
backend = &overlordStateBackend{
path: statePath,
ensureBefore: o.ensureBefore,
}
}
s, restartMgr, err := loadState(statePath, opts.RestartHandler, backend)
if err != nil {