diff --git a/exec/exec.go b/exec/exec.go index 44bc75b..afe3dd6 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -43,23 +43,22 @@ func usage() { func main() { log := new(logger) - app := &execlib.Applier{ + opts := &execlib.Options{ Log: log, } simulate := flag.Bool("n", false, "dry-run") flag.BoolVar(&log.quiet, "q", false, "suppress info messages and failure output") logCommands := flag.Bool("s", false, "show commands run in the log") - flag.IntVar(&app.ConcurrentJobs, "j", 1, "set the maximum number of resources to apply simultaneously") - flag.StringVar(&app.Bash, "bash", execlib.DefaultBashPath, "path to bash shell") + flag.IntVar(&opts.ConcurrentJobs, "j", 1, "set the maximum number of resources to apply simultaneously") + flag.StringVar(&opts.Bash, "bash", execlib.DefaultBashPath, "path to bash shell") flag.Parse() + var sys system.System = system.Local{} if *simulate { - app.System = simulatedSystem{} - } else { - app.System = system.Local{} + sys = simulatedSystem{} } if *logCommands { - app.System = sysLogger{ - System: app.System, + sys = sysLogger{ + System: sys, log: log, } } @@ -91,7 +90,7 @@ func main() { os.Exit(2) } - if err := app.Apply(ctx, cat); err != nil { + if err := execlib.Apply(ctx, sys, cat, opts); err != nil { log.Fatal(ctx, err) } } diff --git a/exec/execlib/apply.go b/exec/execlib/apply.go index 9eaeb0b..2102cb3 100644 --- a/exec/execlib/apply.go +++ b/exec/execlib/apply.go @@ -28,34 +28,65 @@ import ( "github.com/zombiezen/mcm/internal/system" ) -func (app *Applier) applyResource(ctx context.Context, r catalog.Resource, depChanged map[uint64]bool) (changed bool, err error) { - switch r.Which() { +type job struct { + sys system.System + log Logger + resource catalog.Resource + depsChanged map[uint64]bool + + bashPath string +} + +type jobResult struct { + id uint64 + changed bool + err error +} + +func (j *job) run(ctx context.Context) jobResult { + result := jobResult{id: j.resource.ID()} + switch j.resource.Which() { case catalog.Resource_Which_noop: - for _, c := range depChanged { + for _, c := range j.depsChanged { if c { - changed = true + result.changed = true break } } - return changed, nil + return result case catalog.Resource_Which_file: - f, err := r.File() + f, err := j.resource.File() if err != nil { - return false, err + result.err = errorWithResource(j.resource, err) + return result } - return app.applyFile(ctx, f) + changed, err := j.file(ctx, f) + if err != nil { + result.err = errorWithResource(j.resource, err) + return result + } + result.changed = changed + return result case catalog.Resource_Which_exec: - e, err := r.Exec() + e, err := j.resource.Exec() if err != nil { - return false, err + result.err = errorWithResource(j.resource, err) + return result } - return app.applyExec(ctx, e, depChanged) + changed, err := j.exec(ctx, e) + if err != nil { + result.err = errorWithResource(j.resource, err) + return result + } + result.changed = changed + return result default: - return false, errorf("unknown type %v", r.Which()) + result.err = errorWithResource(j.resource, errorf("unknown type %v", j.resource.Which())) + return result } } -func (app *Applier) applyFile(ctx context.Context, f catalog.File) (changed bool, err error) { +func (j *job) file(ctx context.Context, f catalog.File) (changed bool, err error) { path, err := f.Path() if err != nil { return false, errorf("read file path from catalog: %v", err) @@ -65,13 +96,13 @@ func (app *Applier) applyFile(ctx context.Context, f catalog.File) (changed bool } switch f.Which() { case catalog.File_Which_plain: - return app.applyPlainFile(ctx, path, f.Plain()) + return j.plainFile(ctx, path, f.Plain()) case catalog.File_Which_directory: - return app.applyDirectory(ctx, path, f.Directory()) + return j.directory(ctx, path, f.Directory()) case catalog.File_Which_symlink: - return app.applySymlink(ctx, path, f.Symlink()) + return j.symlink(ctx, path, f.Symlink()) case catalog.File_Which_absent: - err := app.System.Remove(ctx, path) + err := j.sys.Remove(ctx, path) if err != nil { if os.IsNotExist(err) { return false, nil @@ -84,9 +115,9 @@ func (app *Applier) applyFile(ctx context.Context, f catalog.File) (changed bool } } -func (app *Applier) applyPlainFile(ctx context.Context, path string, f catalog.File_plain) (changed bool, err error) { +func (j *job) plainFile(ctx context.Context, path string, f catalog.File_plain) (changed bool, err error) { if !f.HasContent() { - info, err := app.System.Lstat(ctx, path) + info, err := j.sys.Lstat(ctx, path) if err != nil { return false, err } @@ -95,29 +126,29 @@ func (app *Applier) applyPlainFile(ctx context.Context, path string, f catalog.F return false, errorf("%s is not a regular file") } mode, _ := f.Mode() - return app.applyFileModeWithInfo(ctx, path, info, mode) + return j.fileModeWithInfo(ctx, path, info, mode) } content, err := f.Content() if err != nil { return false, errorf("read content from catalog: %v", err) } - contentChanged, err := app.applyPlainFileContent(ctx, path, content) + contentChanged, err := j.plainFileContent(ctx, path, content) if err != nil { return false, err } mode, _ := f.Mode() - modeChanged, err := app.applyFileMode(ctx, path, mode) + modeChanged, err := j.fileMode(ctx, path, mode) if err != nil { return false, err } return contentChanged || modeChanged, nil } -func (app *Applier) applyPlainFileContent(ctx context.Context, path string, content []byte) (changed bool, err error) { - w, err := app.System.CreateFile(ctx, path, 0666) // rely on umask to restrict +func (j *job) plainFileContent(ctx context.Context, path string, content []byte) (changed bool, err error) { + w, err := j.sys.CreateFile(ctx, path, 0666) // rely on umask to restrict if os.IsExist(err) { - f, err := app.System.OpenFile(ctx, path) + f, err := j.sys.OpenFile(ctx, path) if err != nil { return false, err } @@ -192,18 +223,18 @@ func (er *errReader) Read(p []byte) (n int, _ error) { return n, er.err } -func (app *Applier) applyDirectory(ctx context.Context, path string, d catalog.File_directory) (changed bool, err error) { - err = app.System.Mkdir(ctx, path, 0777) // rely on umask to restrict +func (j *job) directory(ctx context.Context, path string, d catalog.File_directory) (changed bool, err error) { + err = j.sys.Mkdir(ctx, path, 0777) // rely on umask to restrict if err == nil { mode, _ := d.Mode() - _, err = app.applyFileMode(ctx, path, mode) + _, err = j.fileMode(ctx, path, mode) return true, err } if !os.IsExist(err) { return false, err } // Ensure that what exists is a directory. - info, err := app.System.Lstat(ctx, path) + info, err := j.sys.Lstat(ctx, path) if err != nil { return false, errorf("determine state of %s: %v", path, err) } @@ -212,15 +243,15 @@ func (app *Applier) applyDirectory(ctx context.Context, path string, d catalog.F return false, errorf("%s is not a directory", path) } mode, _ := d.Mode() - return app.applyFileModeWithInfo(ctx, path, info, mode) + return j.fileModeWithInfo(ctx, path, info, mode) } -func (app *Applier) applySymlink(ctx context.Context, path string, l catalog.File_symlink) (changed bool, err error) { +func (j *job) symlink(ctx context.Context, path string, l catalog.File_symlink) (changed bool, err error) { target, err := l.Target() if err != nil { return false, errorf("read target from catalog: %v", err) } - err = app.System.Symlink(ctx, target, path) + err = j.sys.Symlink(ctx, target, path) if err == nil { return true, nil } @@ -228,7 +259,7 @@ func (app *Applier) applySymlink(ctx context.Context, path string, l catalog.Fil return false, err } // Ensure that what exists is a symlink before trying to retarget. - info, err := app.System.Lstat(ctx, path) + info, err := j.sys.Lstat(ctx, path) if err != nil { return false, errorf("determine state of %s: %v", path, err) } @@ -236,7 +267,7 @@ func (app *Applier) applySymlink(ctx context.Context, path string, l catalog.Fil // TODO(soon): what kind of node is it? return false, errorf("%s is not a symlink", path) } - actual, err := app.System.Readlink(ctx, path) + actual, err := j.sys.Readlink(ctx, path) if err != nil { return false, err } @@ -244,16 +275,16 @@ func (app *Applier) applySymlink(ctx context.Context, path string, l catalog.Fil // Already the correct link. return false, nil } - if err := app.System.Remove(ctx, path); err != nil { + if err := j.sys.Remove(ctx, path); err != nil { return false, errorf("retargeting %s: %v", path, err) } - if err := app.System.Symlink(ctx, target, path); err != nil { + if err := j.sys.Symlink(ctx, target, path); err != nil { return false, errorf("retargeting %s: %v", path, err) } return true, nil } -func (app *Applier) applyFileMode(ctx context.Context, path string, mode catalog.File_Mode) (changed bool, err error) { +func (j *job) fileMode(ctx context.Context, path string, mode catalog.File_Mode) (changed bool, err error) { // TODO(someday): avoid the extra capnp read, since WithInfo also accesses these fields. bits := mode.Bits() user, _ := mode.User() @@ -261,29 +292,29 @@ func (app *Applier) applyFileMode(ctx context.Context, path string, mode catalog if bits == catalog.File_Mode_unset && isZeroUserRef(user) && isZeroGroupRef(group) { return false, nil } - st, err := app.System.Lstat(ctx, path) + st, err := j.sys.Lstat(ctx, path) if err != nil { return false, err } - return app.applyFileModeWithInfo(ctx, path, st, mode) + return j.fileModeWithInfo(ctx, path, st, mode) } -func (app *Applier) applyFileModeWithInfo(ctx context.Context, path string, st os.FileInfo, mode catalog.File_Mode) (changed bool, err error) { +func (j *job) fileModeWithInfo(ctx context.Context, path string, st os.FileInfo, mode catalog.File_Mode) (changed bool, err error) { bits := mode.Bits() user, _ := mode.User() group, _ := mode.Group() - changedBits, err := app.applyFileModeBits(ctx, path, st, bits) + changedBits, err := j.fileModeBits(ctx, path, st, bits) if err != nil { return false, err } - changedOwner, err := app.applyFileModeOwner(ctx, path, st, user, group) + changedOwner, err := j.fileModeOwner(ctx, path, st, user, group) if err != nil { return false, err } return changedBits || changedOwner, nil } -func (app *Applier) applyFileModeBits(ctx context.Context, path string, info os.FileInfo, bits uint16) (changed bool, err error) { +func (j *job) fileModeBits(ctx context.Context, path string, info os.FileInfo, bits uint16) (changed bool, err error) { if bits == catalog.File_Mode_unset { return false, nil } @@ -292,31 +323,30 @@ func (app *Applier) applyFileModeBits(ctx context.Context, path string, info os. if info.Mode()&mask == newMode { return false, nil } - if err := app.System.Chmod(ctx, path, newMode); err != nil { + if err := j.sys.Chmod(ctx, path, newMode); err != nil { return false, err } return true, nil } -func (app *Applier) applyFileModeOwner(ctx context.Context, path string, info os.FileInfo, user catalog.UserRef, group catalog.GroupRef) (changed bool, err error) { - uid, err := resolveUserRef(&app.userLookup, user) +func (j *job) fileModeOwner(ctx context.Context, path string, info os.FileInfo, user catalog.UserRef, group catalog.GroupRef) (changed bool, err error) { + uid, err := resolveUserRef(j.sys, user) if err != nil { return false, errorf("resolve user: %v", err) } - gid, err := resolveGroupRef(&app.userLookup, group) + gid, err := resolveGroupRef(j.sys, group) if err != nil { return false, errorf("resolve group: %v", err) } if uid == -1 && gid == -1 { return false, nil } - if oldUID, oldGID, err := app.System.OwnerInfo(info); err != nil { - // TODO(now): which resource? - app.Log.Infof(ctx, "reading file owner: %v; assuming need to chown", err) + if oldUID, oldGID, err := j.sys.OwnerInfo(info); err != nil { + j.log.Infof(ctx, "%s: reading file owner: %v; assuming need to chown", formatResource(j.resource), err) } else if (uid == -1 || oldUID == uid) && (gid == -1 || oldGID == gid) { return false, nil } - if err := app.System.Chown(ctx, path, uid, gid); err != nil { + if err := j.sys.Chown(ctx, path, uid, gid); err != nil { return false, err } return true, nil @@ -382,8 +412,8 @@ func modeFromCatalog(cmode uint16) os.FileMode { return m } -func (app *Applier) applyExec(ctx context.Context, e catalog.Exec, depsChanged map[uint64]bool) (changed bool, err error) { - proceed, err := app.evalExecCondition(ctx, e.Condition(), depsChanged) +func (j *job) exec(ctx context.Context, e catalog.Exec) (changed bool, err error) { + proceed, err := j.evalExecCondition(ctx, e.Condition()) if err != nil { return false, errorf("condition: %v", err) } @@ -394,13 +424,13 @@ func (app *Applier) applyExec(ctx context.Context, e catalog.Exec, depsChanged m if err != nil { return false, errorf("command: %v", err) } - if err := app.runCommand(ctx, cmd); err != nil { + if err := j.runCommand(ctx, cmd); err != nil { return false, errorf("command: %v", err) } return true, nil } -func (app *Applier) evalExecCondition(ctx context.Context, cond catalog.Exec_condition, changed map[uint64]bool) (proceed bool, err error) { +func (j *job) evalExecCondition(ctx context.Context, cond catalog.Exec_condition) (proceed bool, err error) { switch cond.Which() { case catalog.Exec_condition_Which_always: return true, nil @@ -409,20 +439,20 @@ func (app *Applier) evalExecCondition(ctx context.Context, cond catalog.Exec_con if err != nil { return false, err } - return app.runCondition(ctx, c) + return j.runCondition(ctx, c) case catalog.Exec_condition_Which_unless: c, err := cond.Unless() if err != nil { return false, err } - success, err := app.runCondition(ctx, c) + success, err := j.runCondition(ctx, c) if err != nil { return false, err } return !success, nil case catalog.Exec_condition_Which_fileAbsent: path, _ := cond.FileAbsent() - _, err := app.System.Lstat(ctx, path) + _, err := j.sys.Lstat(ctx, path) if err != nil { if os.IsNotExist(err) { return true, nil @@ -441,12 +471,12 @@ func (app *Applier) evalExecCondition(ctx context.Context, cond catalog.Exec_con } for i := 0; i < n; i++ { id := deps.At(i) - if _, ok := changed[id]; !ok { + if _, ok := j.depsChanged[id]; !ok { return false, errorf("depends on ID %d, which is not in resource's direct dependencies", id) } } for i := 0; i < n; i++ { - if changed[deps.At(i)] { + if j.depsChanged[deps.At(i)] { return true, nil } } @@ -456,24 +486,24 @@ func (app *Applier) evalExecCondition(ctx context.Context, cond catalog.Exec_con } } -func (app *Applier) runCommand(ctx context.Context, c catalog.Exec_Command) error { - cmd, err := app.buildCommand(c) +func (j *job) runCommand(ctx context.Context, c catalog.Exec_Command) error { + cmd, err := buildCommand(c, j.bashPath) if err != nil { return err } - out, err := app.System.Run(ctx, cmd) + out, err := j.sys.Run(ctx, cmd) if err != nil { return errorWithOutput(out, err) } return nil } -func (app *Applier) runCondition(ctx context.Context, c catalog.Exec_Command) (success bool, err error) { - cmd, err := app.buildCommand(c) +func (j *job) runCondition(ctx context.Context, c catalog.Exec_Command) (success bool, err error) { + cmd, err := buildCommand(c, j.bashPath) if err != nil { return false, err } - out, err := app.System.Run(ctx, cmd) + out, err := j.sys.Run(ctx, cmd) if _, fail := err.(*exec.ExitError); fail { return false, nil } @@ -483,7 +513,7 @@ func (app *Applier) runCondition(ctx context.Context, c catalog.Exec_Command) (s return true, nil } -func (app *Applier) buildCommand(cmd catalog.Exec_Command) (*system.Cmd, error) { +func buildCommand(cmd catalog.Exec_Command, bashPath string) (*system.Cmd, error) { var c *system.Cmd switch cmd.Which() { case catalog.Exec_Command_Which_argv: @@ -507,17 +537,13 @@ func (app *Applier) buildCommand(cmd catalog.Exec_Command) (*system.Cmd, error) Args: argv, } case catalog.Exec_Command_Which_bash: - p := app.Bash - if p == "" { - p = DefaultBashPath - } b, err := cmd.BashBytes() if err != nil { return nil, errorf("read bash: %v", err) } c = &system.Cmd{ - Path: p, - Args: []string{p}, + Path: bashPath, + Args: []string{bashPath}, Stdin: bytes.NewReader(b), } default: diff --git a/exec/execlib/execlib.go b/exec/execlib/execlib.go index 5732fa3..949ffb5 100644 --- a/exec/execlib/execlib.go +++ b/exec/execlib/execlib.go @@ -26,13 +26,25 @@ import ( "github.com/zombiezen/mcm/internal/system" ) -// DefaultBashPath is the path used if Applier.Bash is empty. -const DefaultBashPath = "/bin/bash" +// Apply changes a system match the resources in a catalog. +// Passing nil options is the same as passing the zero value. +func Apply(ctx context.Context, sys system.System, c catalog.Catalog, opts *Options) error { + res, _ := c.Resources() + g, err := depgraph.New(res) + if err != nil { + return toError(err) + } + if err = apply(ctx, cacheUserLookups(sys), g, opts.normalize()); err != nil { + return toError(err) + } + return nil +} -// An Applier applies a catalog to a system. -type Applier struct { - System system.System - Log Logger +// Options is the set of optional parameters for Apply. The zero value +// is the default set of options. +type Options struct { + // Log will receive progress messages if non-nil. + Log Logger // Bash is the path to the bash executable. // If it's empty, then Apply uses DefaultBashPath. @@ -41,11 +53,34 @@ type Applier struct { // ConcurrentJobs is the number of resources to apply simultaneously. // If non-positive, then it assumes 1. ConcurrentJobs int +} - // TODO(someday): pass this down as an argument - userLookup userLookupCache +// normalize will return a Options struct that is equivalent to opts. +// It will never return nil, and it may return opts. +func (opts *Options) normalize() *Options { + if opts == nil { + return &Options{Log: nullLogger{}} + } + if opts.Log != nil && opts.Bash != "" && opts.ConcurrentJobs >= 1 { + return opts + } + newOpts := new(Options) + *newOpts = *opts + if newOpts.Log == nil { + newOpts.Log = nullLogger{} + } + if newOpts.Bash == "" { + newOpts.Bash = DefaultBashPath + } + if newOpts.ConcurrentJobs < 1 { + newOpts.ConcurrentJobs = 1 + } + return newOpts } +// DefaultBashPath is the path used if Applier.Bash is empty. +const DefaultBashPath = "/bin/bash" + // Logger collects execution messages from an Applier. A Logger must be // safe to call from multiple goroutines. type Logger interface { @@ -53,18 +88,10 @@ type Logger interface { Error(ctx context.Context, err error) } -func (app *Applier) Apply(ctx context.Context, c catalog.Catalog) error { - res, _ := c.Resources() - g, err := depgraph.New(res) - if err != nil { - return toError(err) - } - app.userLookup = userLookupCache{lookup: app.System} - if err = app.applyCatalog(ctx, g); err != nil { - return toError(err) - } - return nil -} +type nullLogger struct{} + +func (nullLogger) Infof(ctx context.Context, format string, args ...interface{}) {} +func (nullLogger) Error(ctx context.Context, err error) {} type applyState struct { graph *depgraph.Graph @@ -72,22 +99,18 @@ type applyState struct { changedResources map[uint64]bool } -func (app *Applier) applyCatalog(ctx context.Context, g *depgraph.Graph) error { - njobs := app.ConcurrentJobs - if njobs < 1 { - njobs = 1 - } - ch, results, done := app.startWorkers(ctx, njobs) +func apply(ctx context.Context, sys system.System, g *depgraph.Graph, opts *Options) error { + ch, results, done := startWorkers(ctx, opts.Log, opts.ConcurrentJobs) defer done() state := &applyState{ graph: g, changedResources: make(map[uint64]bool), } - working := make(workingSet, njobs) - var nextArgs applyArgs + working := make(workingSet, opts.ConcurrentJobs) + var nextJob *job for !g.Done() { - if working.hasIdle() && !nextArgs.resource.IsValid() { + if working.hasIdle() && nextJob == nil { // Find next work, if any. ready := g.Ready() if len(ready) == 0 { @@ -95,29 +118,32 @@ func (app *Applier) applyCatalog(ctx context.Context, g *depgraph.Graph) error { } if id := working.next(ready); id != 0 { res := g.Resource(id) - nextArgs = applyArgs{ - resource: res, - depChanged: mapChangedDeps(state.changedResources, res), + nextJob = &job{ + sys: sys, + log: opts.Log, + bashPath: opts.Bash, + resource: res, + depsChanged: mapChangedDeps(state.changedResources, res), } } } - if !nextArgs.resource.IsValid() { + if nextJob == nil { select { case r := <-results: working.remove(r.id) - app.update(ctx, state, r) + update(ctx, opts.Log, state, r) case <-ctx.Done(): return ctx.Err() } continue } select { - case ch <- nextArgs: - working.add(nextArgs.resource.ID()) - nextArgs = applyArgs{} + case ch <- nextJob: + working.add(nextJob.resource.ID()) + nextJob = nil case r := <-results: working.remove(r.id) - app.update(ctx, state, r) + update(ctx, opts.Log, state, r) case <-ctx.Done(): return ctx.Err() } @@ -129,11 +155,10 @@ func (app *Applier) applyCatalog(ctx context.Context, g *depgraph.Graph) error { return nil } -func (app *Applier) update(ctx context.Context, state *applyState, r applyResult) { +func update(ctx context.Context, log Logger, state *applyState, r jobResult) { if r.err != nil { state.hasFailures = true - res := state.graph.Resource(r.id) - app.Log.Error(ctx, errorWithResource(res, r.err)) + log.Error(ctx, r.err) skipped := state.graph.MarkFailure(r.id) if len(skipped) == 0 { return @@ -142,7 +167,8 @@ func (app *Applier) update(ctx context.Context, state *applyState, r applyResult for i := range skipnames { skipnames[i] = formatResource(state.graph.Resource(skipped[i])) } - app.Log.Infof(ctx, "skipping due to failure of %s: %s", formatResource(res), strings.Join(skipnames, ", ")) + res := state.graph.Resource(r.id) + log.Infof(ctx, "skipping due to failure of %s: %s", formatResource(res), strings.Join(skipnames, ", ")) return } state.graph.Mark(r.id) @@ -206,26 +232,15 @@ func (ws workingSet) next(ready []uint64) uint64 { return 0 } -type applyArgs struct { - resource catalog.Resource - depChanged map[uint64]bool -} - -type applyResult struct { - id uint64 - changed bool - err error -} - -func (app *Applier) startWorkers(ctx context.Context, n int) (chan<- applyArgs, <-chan applyResult, func()) { - ch := make(chan applyArgs) - results := make(chan applyResult) +func startWorkers(ctx context.Context, log Logger, n int) (chan<- *job, <-chan jobResult, func()) { + ch := make(chan *job) + results := make(chan jobResult) workCtx, cancel := context.WithCancel(ctx) var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func() { - app.worker(workCtx, results, ch) + worker(workCtx, log, results, ch) wg.Done() }() } @@ -235,20 +250,15 @@ func (app *Applier) startWorkers(ctx context.Context, n int) (chan<- applyArgs, } } -func (app *Applier) worker(ctx context.Context, results chan<- applyResult, ch <-chan applyArgs) { +func worker(ctx context.Context, log Logger, results chan<- jobResult, ch <-chan *job) { for { select { - case args, ok := <-ch: + case j, ok := <-ch: if !ok { return } - app.Log.Infof(ctx, "applying: %s", formatResource(args.resource)) - changed, err := app.applyResource(ctx, args.resource, args.depChanged) - r := applyResult{ - id: args.resource.ID(), - changed: changed, - err: err, - } + log.Infof(ctx, "applying: %s", formatResource(j.resource)) + r := j.run(ctx) select { case results <- r: case <-ctx.Done(): @@ -260,6 +270,26 @@ func (app *Applier) worker(ctx context.Context, results chan<- applyResult, ch < } } +type cachedUserLookupSystem struct { + system.System + cache userLookupCache +} + +func cacheUserLookups(sys system.System) system.System { + return &cachedUserLookupSystem{ + System: sys, + cache: userLookupCache{lookup: sys}, + } +} + +func (s *cachedUserLookupSystem) LookupUser(name string) (system.UID, error) { + return s.cache.LookupUser(name) +} + +func (s *cachedUserLookupSystem) LookupGroup(name string) (system.GID, error) { + return s.cache.LookupGroup(name) +} + // TODO(someday): ensure lookups are single-flight type userLookupCache struct { diff --git a/exec/execlib/execlib_test.go b/exec/execlib/execlib_test.go index 8996f97..8e930c9 100644 --- a/exec/execlib/execlib_test.go +++ b/exec/execlib/execlib_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package execlib +package execlib_test import ( "bytes" @@ -88,12 +88,10 @@ func TestExecBash(t *testing.T) { t.Fatal("Mkprogram:", err) } - app := &Applier{ - System: sys, - Log: testLogger{t: t}, - Bash: bashPath, - } - err = app.Apply(ctx, cat) + err = Apply(ctx, sys, cat, &Options{ + Log: testLogger{t: t}, + Bash: bashPath, + }) if err != nil { t.Error("Apply:", err) } @@ -169,12 +167,10 @@ func (ff *fixtureFactory) newFixture(ctx context.Context, log applytests.Logger, } func (f *fixture) Apply(ctx context.Context, c catalog.Catalog) error { - app := &Applier{ - System: f.sys, + return Apply(ctx, f.sys, c, &Options{ Log: testLogger{t: f.log}, ConcurrentJobs: f.concurrentJobs, - } - return app.Apply(ctx, c) + }) } func (f *fixture) System() system.System {