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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
Expand Up @@ -17,6 +17,7 @@ package cli
import (
"errors"
"fmt"
"maps"
"os"
"os/signal"
"strconv"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions internals/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions internals/overlord/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down
Loading