diff --git a/build/build.go b/build/build.go index 2ebf0898cbbc..1892d41d576e 100644 --- a/build/build.go +++ b/build/build.go @@ -151,11 +151,11 @@ func toRepoOnly(in string) (string, error) { return strings.Join(out, ","), nil } -func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) { - return BuildWithResultHandler(ctx, nodes, opts, docker, configDir, w, nil) +func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) { + return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil) } -func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) { +func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) { if len(nodes) == 0 { return nil, errors.Errorf("driver required for build") } @@ -234,12 +234,12 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[ return nil, err } localOpt := opt - so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, configDir, w, docker) + so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, cfg, w, docker) opts[k] = localOpt if err != nil { return nil, err } - if err := saveLocalState(so, k, opt, np.Node(), configDir); err != nil { + if err := saveLocalState(so, k, opt, np.Node(), cfg); err != nil { return nil, err } addGitAttrs(so) diff --git a/build/localstate.go b/build/localstate.go index 5d902e440efc..f6bd1f712f14 100644 --- a/build/localstate.go +++ b/build/localstate.go @@ -5,10 +5,11 @@ import ( "github.com/docker/buildx/builder" "github.com/docker/buildx/localstate" + "github.com/docker/buildx/util/confutil" "github.com/moby/buildkit/client" ) -func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, configDir string) error { +func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, cfg *confutil.Config) error { var err error if so.Ref == "" { return nil @@ -30,7 +31,7 @@ func saveLocalState(so *client.SolveOpt, target string, opts Options, node build if lp == "" && dp == "" { return nil } - l, err := localstate.New(configDir) + l, err := localstate.New(cfg) if err != nil { return err } diff --git a/build/opt.go b/build/opt.go index 88f11dda7677..b0f7fd35c57f 100644 --- a/build/opt.go +++ b/build/opt.go @@ -35,7 +35,7 @@ import ( "github.com/tonistiigi/fsutil" ) -func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) { +func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, cfg *confutil.Config, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) { nodeDriver := node.Driver defers := make([]func(), 0, 2) releaseF := func() { @@ -271,7 +271,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O // add node identifier to shared key if one was specified if so.SharedKey != "" { - so.SharedKey += ":" + confutil.TryNodeIdentifier(configDir) + so.SharedKey += ":" + confutil.TryNodeIdentifier(cfg) } if opt.Pull { @@ -401,7 +401,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro } // stdin is dockerfile dockerfileReader = rc - inp.ContextPath, _ = os.MkdirTemp("", "empty-dir") + inp.ContextPath, _ = osutil.MkdirTemp("", "empty-dir") toRemove = append(toRemove, inp.ContextPath) if err := setLocalMount("context", inp.ContextPath, target); err != nil { return nil, err @@ -593,11 +593,11 @@ func setLocalMount(name, dir string, so *client.SolveOpt) error { } func createTempDockerfile(r io.Reader, multiReader *SyncMultiReader) (string, error) { - dir, err := os.MkdirTemp("", "dockerfile") + dir, err := osutil.MkdirTemp("", "dockerfile") if err != nil { return "", err } - f, err := os.Create(filepath.Join(dir, "Dockerfile")) + f, err := osutil.Create(filepath.Join(dir, "Dockerfile")) if err != nil { return "", err } diff --git a/build/url.go b/build/url.go index 9188b1b03366..e464b020e442 100644 --- a/build/url.go +++ b/build/url.go @@ -2,10 +2,10 @@ package build import ( "context" - "os" "path/filepath" "github.com/docker/buildx/driver" + "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/progress" "github.com/docker/go-units" "github.com/moby/buildkit/client" @@ -56,11 +56,11 @@ func createTempDockerfileFromURL(ctx context.Context, d *driver.DriverHandle, ur if err != nil { return nil, err } - dir, err := os.MkdirTemp("", "buildx") + dir, err := osutil.MkdirTemp("", "buildx") if err != nil { return nil, err } - if err := os.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil { + if err := osutil.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil { return nil, err } out = dir diff --git a/builder/builder.go b/builder/builder.go index 1b25ebe868f8..dfa5051f0a9f 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -439,7 +439,7 @@ func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Cre if buildkitdConfigFile == "" { // if buildkit daemon config is not provided, check if the default one // is available and use it - if f, ok := confutil.DefaultConfigFile(dockerCli); ok { + if f, ok := confutil.NewConfig(dockerCli).BuildKitConfigFile(); ok { buildkitdConfigFile = f } } @@ -584,7 +584,7 @@ func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Leav return err } - ls, err := localstate.New(confutil.ConfigDir(dockerCli)) + ls, err := localstate.New(confutil.NewConfig(dockerCli)) if err != nil { return err } diff --git a/commands/bake.go b/commands/bake.go index 785a4364745f..d3e87af74964 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -265,7 +265,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba } done := timeBuildCommand(mp, attributes) - resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer) + resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), printer) if err := printer.Wait(); retErr == nil { retErr = err } @@ -470,7 +470,7 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string refs = append(refs, b.Ref) bo[k] = b } - l, err := localstate.New(confutil.ConfigDir(dockerCli)) + l, err := localstate.New(confutil.NewConfig(dockerCli)) if err != nil { return err } @@ -621,7 +621,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str commandNameAttribute.String("bake"), attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{ bakeOptions: options, - configDir: confutil.ConfigDir(dockerCli), + cfg: confutil.NewConfig(dockerCli), url: url, cmdContext: cmdContext, targets: targets, @@ -633,7 +633,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str type bakeOptionsHash struct { *bakeOptions - configDir string + cfg *confutil.Config url string cmdContext string targets []string @@ -657,7 +657,7 @@ func (o *bakeOptionsHash) String() string { joinedFiles := strings.Join(files, ",") joinedTargets := strings.Join(targets, ",") - salt := confutil.TryNodeIdentifier(o.configDir) + salt := confutil.TryNodeIdentifier(o.cfg) h := sha256.New() for _, s := range []string{url, cmdContext, joinedFiles, joinedTargets, salt} { diff --git a/commands/build.go b/commands/build.go index ba035bb6b136..258444cce190 100644 --- a/commands/build.go +++ b/commands/build.go @@ -41,7 +41,6 @@ import ( "github.com/docker/cli/cli/command" dockeropts "github.com/docker/cli/opts" "github.com/docker/docker/api/types/versions" - "github.com/docker/docker/pkg/ioutils" "github.com/moby/buildkit/client" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend/subrequests" @@ -238,7 +237,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu commandNameAttribute.String("build"), attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{ buildOptions: options, - configDir: confutil.ConfigDir(dockerCli), + cfg: confutil.NewConfig(dockerCli), }), driverNameAttribute.String(options.builder), driverTypeAttribute.String(driverType), @@ -250,7 +249,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu // the fmt.Stringer interface. type buildOptionsHash struct { *buildOptions - configDir string + cfg *confutil.Config result string resultOnce sync.Once } @@ -267,7 +266,7 @@ func (o *buildOptionsHash) String() string { if contextPath != "-" && osutil.IsLocalDir(contextPath) { contextPath = osutil.ToAbs(contextPath) } - salt := confutil.TryNodeIdentifier(o.configDir) + salt := confutil.TryNodeIdentifier(o.cfg) h := sha256.New() for _, s := range []string{target, contextPath, dockerfile, salt} { @@ -374,7 +373,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term) } if options.imageIDFile != "" { - if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil { + if err := osutil.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil { return errors.Wrap(err, "writing image ID file") } } @@ -742,7 +741,7 @@ func writeMetadataFile(filename string, dt interface{}) error { if err != nil { return err } - return ioutils.AtomicWriteFile(filename, b, 0644) + return osutil.AtomicWriteFile(filename, b, 0644) } func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} { diff --git a/commands/install.go b/commands/install.go index 0014a52abba1..d2373080ab7a 100644 --- a/commands/install.go +++ b/commands/install.go @@ -1,10 +1,9 @@ package commands import ( - "os" - "github.com/docker/buildx/util/cobrautil" "github.com/docker/buildx/util/cobrautil/completion" + "github.com/docker/buildx/util/osutil" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" @@ -17,7 +16,7 @@ type installOptions struct { func runInstall(_ command.Cli, _ installOptions) error { dir := config.Dir() - if err := os.MkdirAll(dir, 0755); err != nil { + if err := osutil.MkdirAll(dir, 0755); err != nil { return errors.Wrap(err, "could not create docker config") } diff --git a/controller/build/build.go b/controller/build/build.go index 71c5adf237b3..7a57dc7d2228 100644 --- a/controller/build/build.go +++ b/controller/build/build.go @@ -214,7 +214,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No if generateResult { var mu sync.Mutex var idx int - resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) { + resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) { mu.Lock() defer mu.Unlock() if res == nil || driverIndex < idx { @@ -222,7 +222,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No } }) } else { - resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress) + resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress) } if err != nil { return nil, res, err diff --git a/controller/pb/export.go b/controller/pb/export.go index 3de33eb3fe4e..57058d80779b 100644 --- a/controller/pb/export.go +++ b/controller/pb/export.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/containerd/console" + "github.com/docker/buildx/util/osutil" "github.com/moby/buildkit/client" "github.com/pkg/errors" ) @@ -85,7 +86,7 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) { if err == nil && fi.IsDir() { return nil, errors.Errorf("destination file %s is a directory", entry.Destination) } - f, err := os.Create(entry.Destination) + f, err := osutil.Create(entry.Destination) if err != nil { return nil, errors.Errorf("failed to open %s", err) } diff --git a/controller/remote/controller.go b/controller/remote/controller.go index 64dfcc0f639f..c38b299a63f6 100644 --- a/controller/remote/controller.go +++ b/controller/remote/controller.go @@ -21,6 +21,7 @@ import ( "github.com/docker/buildx/controller/control" controllerapi "github.com/docker/buildx/controller/pb" "github.com/docker/buildx/util/confutil" + "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/progress" "github.com/docker/buildx/version" "github.com/docker/cli/cli/command" @@ -138,7 +139,7 @@ func serveCmd(dockerCli command.Cli) *cobra.Command { return err } pidF := filepath.Join(root, defaultPIDFilename) - if err := os.WriteFile(pidF, []byte(fmt.Sprintf("%d", os.Getpid())), 0600); err != nil { + if err := osutil.WriteFile(pidF, []byte(fmt.Sprintf("%d", os.Getpid())), 0600); err != nil { return err } defer func() { @@ -247,18 +248,18 @@ func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error) if rootDir == "" { return "", errors.New("buildx root dir must be determined") } - if err := os.MkdirAll(rootDir, 0700); err != nil { + if err := osutil.MkdirAll(rootDir, 0700); err != nil { return "", err } serverRoot := filepath.Join(rootDir, "shared") - if err := os.MkdirAll(serverRoot, 0700); err != nil { + if err := osutil.MkdirAll(serverRoot, 0700); err != nil { return "", err } return serverRoot, nil } func rootDataDir(dockerCli command.Cli) string { - return filepath.Join(confutil.ConfigDir(dockerCli), "controller") + return filepath.Join(confutil.NewConfig(dockerCli).Dir(), "controller") } func newBuildxClientAndCheck(ctx context.Context, addr string) (*Client, error) { diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index 2eede55bb77a..f7b6127bcd47 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -16,6 +16,7 @@ import ( "github.com/docker/buildx/driver/bkimage" "github.com/docker/buildx/util/confutil" "github.com/docker/buildx/util/imagetools" + "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/progress" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" @@ -462,7 +463,7 @@ func (l *logWriter) Write(dt []byte) (int, error) { func writeConfigFiles(m map[string][]byte) (_ string, err error) { // Temp dir that will be copied to the container - tmpDir, err := os.MkdirTemp("", "buildkitd-config") + tmpDir, err := osutil.MkdirTemp("", "buildkitd-config") if err != nil { return "", err } @@ -474,10 +475,10 @@ func writeConfigFiles(m map[string][]byte) (_ string, err error) { configDir := filepath.Base(confutil.DefaultBuildKitConfigDir) for f, dt := range m { p := filepath.Join(tmpDir, configDir, f) - if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { + if err := osutil.MkdirAll(filepath.Dir(p), 0755); err != nil { return "", err } - if err := os.WriteFile(p, dt, 0644); err != nil { + if err := osutil.WriteFile(p, dt, 0644); err != nil { return "", err } } diff --git a/localstate/localstate.go b/localstate/localstate.go index 943494c79d0b..4d48601843f2 100644 --- a/localstate/localstate.go +++ b/localstate/localstate.go @@ -8,7 +8,7 @@ import ( "path/filepath" "sync" - "github.com/docker/docker/pkg/ioutils" + "github.com/docker/buildx/util/confutil" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) @@ -42,18 +42,18 @@ type StateGroup struct { } type LocalState struct { - root string + cfg *confutil.Config } -func New(root string) (*LocalState, error) { - if root == "" { - return nil, errors.Errorf("root dir empty") +func New(cfg *confutil.Config) (*LocalState, error) { + if cfg.Dir() == "" { + return nil, errors.Errorf("config dir empty") } - if err := os.MkdirAll(filepath.Join(root, refsDir), 0700); err != nil { + if err := cfg.MkdirAll(refsDir, 0700); err != nil { return nil, err } return &LocalState{ - root: root, + cfg: cfg, }, nil } @@ -61,7 +61,7 @@ func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error) if err := ls.validate(builderName, nodeName, id); err != nil { return nil, err } - dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, builderName, nodeName, id)) + dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName, id)) if err != nil { return nil, err } @@ -76,19 +76,19 @@ func (ls *LocalState) SaveRef(builderName, nodeName, id string, st State) error if err := ls.validate(builderName, nodeName, id); err != nil { return err } - refDir := filepath.Join(ls.root, refsDir, builderName, nodeName) - if err := os.MkdirAll(refDir, 0700); err != nil { + refDir := filepath.Join(refsDir, builderName, nodeName) + if err := ls.cfg.MkdirAll(refDir, 0700); err != nil { return err } dt, err := json.Marshal(st) if err != nil { return err } - return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600) + return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0644) } func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) { - dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, groupDir, id)) + dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id)) if err != nil { return nil, err } @@ -100,15 +100,15 @@ func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) { } func (ls *LocalState) SaveGroup(id string, stg StateGroup) error { - refDir := filepath.Join(ls.root, refsDir, groupDir) - if err := os.MkdirAll(refDir, 0700); err != nil { + refDir := filepath.Join(refsDir, groupDir) + if err := ls.cfg.MkdirAll(refDir, 0700); err != nil { return err } dt, err := json.Marshal(stg) if err != nil { return err } - return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600) + return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600) } func (ls *LocalState) RemoveBuilder(builderName string) error { @@ -116,7 +116,7 @@ func (ls *LocalState) RemoveBuilder(builderName string) error { return errors.Errorf("builder name empty") } - dir := filepath.Join(ls.root, refsDir, builderName) + dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName) if _, err := os.Lstat(dir); err != nil { if !os.IsNotExist(err) { return err @@ -147,7 +147,7 @@ func (ls *LocalState) RemoveBuilderNode(builderName string, nodeName string) err return errors.Errorf("node name empty") } - dir := filepath.Join(ls.root, refsDir, builderName, nodeName) + dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName) if _, err := os.Lstat(dir); err != nil { if !os.IsNotExist(err) { return err @@ -208,7 +208,7 @@ func (ls *LocalState) removeGroup(id string) error { if id == "" { return errors.Errorf("group ref empty") } - f := filepath.Join(ls.root, refsDir, groupDir, id) + f := filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id) if _, err := os.Lstat(f); err != nil { if !os.IsNotExist(err) { return err diff --git a/localstate/localstate_test.go b/localstate/localstate_test.go index 94d4ee8e8edd..180f8e4cc909 100644 --- a/localstate/localstate_test.go +++ b/localstate/localstate_test.go @@ -4,6 +4,7 @@ import ( "path/filepath" "testing" + "github.com/docker/buildx/util/confutil" "github.com/stretchr/testify/require" ) @@ -39,10 +40,10 @@ func newls(t *testing.T) *LocalState { t.Helper() tmpdir := t.TempDir() - l, err := New(tmpdir) + l, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir))) require.NoError(t, err) require.DirExists(t, filepath.Join(tmpdir, refsDir)) - require.Equal(t, tmpdir, l.root) + require.Equal(t, tmpdir, l.cfg.Dir()) require.NoError(t, l.SaveRef(testBuilderName, testNodeName, testStateRefID, testStateRef)) diff --git a/store/store.go b/store/store.go index e1a6937de13c..d27fda623da6 100644 --- a/store/store.go +++ b/store/store.go @@ -8,7 +8,7 @@ import ( "time" "github.com/docker/buildx/localstate" - "github.com/docker/docker/pkg/ioutils" + "github.com/docker/buildx/util/confutil" "github.com/gofrs/flock" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -20,25 +20,25 @@ const ( activityDir = "activity" ) -func New(root string) (*Store, error) { - if err := os.MkdirAll(filepath.Join(root, instanceDir), 0700); err != nil { +func New(cfg *confutil.Config) (*Store, error) { + if err := cfg.MkdirAll(instanceDir, 0700); err != nil { return nil, err } - if err := os.MkdirAll(filepath.Join(root, defaultsDir), 0700); err != nil { + if err := cfg.MkdirAll(defaultsDir, 0700); err != nil { return nil, err } - if err := os.MkdirAll(filepath.Join(root, activityDir), 0700); err != nil { + if err := cfg.MkdirAll(activityDir, 0700); err != nil { return nil, err } - return &Store{root: root}, nil + return &Store{cfg: cfg}, nil } type Store struct { - root string + cfg *confutil.Config } func (s *Store) Txn() (*Txn, func(), error) { - l := flock.New(filepath.Join(s.root, ".lock")) + l := flock.New(filepath.Join(s.cfg.Dir(), ".lock")) if err := l.Lock(); err != nil { return nil, nil, err } @@ -54,7 +54,7 @@ type Txn struct { } func (t *Txn) List() ([]*NodeGroup, error) { - pp := filepath.Join(t.s.root, instanceDir) + pp := filepath.Join(t.s.cfg.Dir(), instanceDir) fis, err := os.ReadDir(pp) if err != nil { return nil, err @@ -84,7 +84,7 @@ func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) { if err != nil { return nil, err } - dt, err := os.ReadFile(filepath.Join(t.s.root, instanceDir, name)) + dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), instanceDir, name)) if err != nil { return nil, err } @@ -110,7 +110,7 @@ func (t *Txn) Save(ng *NodeGroup) error { if err != nil { return err } - return ioutils.AtomicWriteFile(filepath.Join(t.s.root, instanceDir, name), dt, 0600) + return t.s.cfg.AtomicWriteFile(filepath.Join(instanceDir, name), dt, 0600) } func (t *Txn) Remove(name string) error { @@ -121,14 +121,14 @@ func (t *Txn) Remove(name string) error { if err := t.RemoveLastActivity(name); err != nil { return err } - ls, err := localstate.New(t.s.root) + ls, err := localstate.New(t.s.cfg) if err != nil { return err } if err := ls.RemoveBuilder(name); err != nil { return err } - return os.RemoveAll(filepath.Join(t.s.root, instanceDir, name)) + return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), instanceDir, name)) } func (t *Txn) SetCurrent(key, name string, global, def bool) error { @@ -141,28 +141,28 @@ func (t *Txn) SetCurrent(key, name string, global, def bool) error { if err != nil { return err } - if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600); err != nil { + if err := t.s.cfg.AtomicWriteFile("current", dt, 0600); err != nil { return err } h := toHash(key) if def { - if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, defaultsDir, h), []byte(name), 0600); err != nil { + if err := t.s.cfg.AtomicWriteFile(filepath.Join(defaultsDir, h), []byte(name), 0600); err != nil { return err } } else { - os.RemoveAll(filepath.Join(t.s.root, defaultsDir, h)) // ignore error + os.RemoveAll(filepath.Join(t.s.cfg.Dir(), defaultsDir, h)) // ignore error } return nil } func (t *Txn) UpdateLastActivity(ng *NodeGroup) error { - return ioutils.AtomicWriteFile(filepath.Join(t.s.root, activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600) + return t.s.cfg.AtomicWriteFile(filepath.Join(activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600) } func (t *Txn) GetLastActivity(ng *NodeGroup) (la time.Time, _ error) { - dt, err := os.ReadFile(filepath.Join(t.s.root, activityDir, ng.Name)) + dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), activityDir, ng.Name)) if err != nil { if os.IsNotExist(errors.Cause(err)) { return la, nil @@ -177,7 +177,7 @@ func (t *Txn) RemoveLastActivity(name string) error { if err != nil { return err } - return os.RemoveAll(filepath.Join(t.s.root, activityDir, name)) + return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), activityDir, name)) } func (t *Txn) reset(key string) error { @@ -185,11 +185,11 @@ func (t *Txn) reset(key string) error { if err != nil { return err } - return ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600) + return t.s.cfg.AtomicWriteFile("current", dt, 0600) } func (t *Txn) Current(key string) (*NodeGroup, error) { - dt, err := os.ReadFile(filepath.Join(t.s.root, "current")) + dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), "current")) if err != nil { if !os.IsNotExist(err) { return nil, err @@ -220,7 +220,7 @@ func (t *Txn) Current(key string) (*NodeGroup, error) { h := toHash(key) - dt, err = os.ReadFile(filepath.Join(t.s.root, defaultsDir, h)) + dt, err = os.ReadFile(filepath.Join(t.s.cfg.Dir(), defaultsDir, h)) if err != nil { if os.IsNotExist(err) { t.reset(key) diff --git a/store/storeutil/storeutil.go b/store/storeutil/storeutil.go index 41e7c264aa63..e9b2a14fac67 100644 --- a/store/storeutil/storeutil.go +++ b/store/storeutil/storeutil.go @@ -17,7 +17,7 @@ import ( // GetStore returns current builder instance store func GetStore(dockerCli command.Cli) (*store.Txn, func(), error) { - s, err := store.New(confutil.ConfigDir(dockerCli)) + s, err := store.New(confutil.NewConfig(dockerCli)) if err != nil { return nil, nil, err } diff --git a/util/confutil/config.go b/util/confutil/config.go index 54d5e0ec711b..6ce7c25e15f9 100644 --- a/util/confutil/config.go +++ b/util/confutil/config.go @@ -6,34 +6,115 @@ import ( "path/filepath" "github.com/docker/cli/cli/command" + "github.com/docker/docker/pkg/ioutils" "github.com/pelletier/go-toml" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) -// ConfigDir will look for correct configuration store path; -// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory -// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`) -func ConfigDir(dockerCli command.Cli) string { - if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" { - logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig) - return buildxConfig +var ( + sudoerUID = -1 + sudoerGID = -1 +) + +type Config struct { + dir string +} + +type ConfigOption func(*configOptions) + +type configOptions struct { + dir string +} + +func WithDir(dir string) ConfigOption { + return func(o *configOptions) { + o.dir = dir } +} - buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx") - logrus.Debugf("using default config store %q", buildxConfig) - return buildxConfig +func NewConfig(dockerCli command.Cli, opts ...ConfigOption) *Config { + co := configOptions{} + for _, opt := range opts { + opt(&co) + } + configDir := co.dir + if configDir == "" { + configDir = os.Getenv("BUILDX_CONFIG") + if configDir == "" { + configDir = filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx") + } + } + return &Config{ + dir: configDir, + } +} + +// Dir will look for correct configuration store path; +// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory +// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`) +func (c *Config) Dir() string { + return c.dir } -// DefaultConfigFile returns the default BuildKit configuration file path -func DefaultConfigFile(dockerCli command.Cli) (string, bool) { - f := path.Join(ConfigDir(dockerCli), "buildkitd.default.toml") +// BuildKitConfigFile returns the default BuildKit configuration file path +func (c *Config) BuildKitConfigFile() (string, bool) { + f := path.Join(c.dir, "buildkitd.default.toml") if _, err := os.Stat(f); err == nil { return f, true } return "", false } +// MkdirAll creates a directory and all necessary parents within the config dir +func (c *Config) MkdirAll(dir string, perm os.FileMode) error { + d := path.Join(c.dir, dir) + if err := os.MkdirAll(d, perm); err != nil { + return err + } + if sudoerUID != -1 && sudoerGID != -1 { + return os.Chown(d, sudoerUID, sudoerGID) + } + return nil +} + +// WriteFile writes data to a file within the config dir +func (c *Config) WriteFile(filename string, data []byte, perm os.FileMode) error { + f := path.Join(c.dir, filename) + if err := os.WriteFile(f, data, perm); err != nil { + return err + } + if sudoerUID != -1 && sudoerGID != -1 { + return os.Chown(f, sudoerUID, sudoerGID) + } + return nil +} + +// AtomicWriteFile writes data to a file within the config dir atomically +func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { + f := path.Join(c.dir, filename) + if err := ioutils.AtomicWriteFile(f, data, perm); err != nil { + return err + } + if sudoerUID != -1 && sudoerGID != -1 { + return os.Chown(f, sudoerUID, sudoerGID) + } + return nil +} + +// Create creates a file within the config dir +func (c *Config) Create(name string) (*os.File, error) { + f, err := os.Create(path.Join(c.dir, name)) + if err != nil { + return nil, err + } + if sudoerUID != -1 && sudoerGID != -1 { + if err := f.Chown(sudoerUID, sudoerGID); err != nil { + return nil, errors.Wrapf(err, "failed to chown %s", name) + } + } + return f, nil +} + // LoadConfigTree loads BuildKit config toml tree func LoadConfigTree(fp string) (*toml.Tree, error) { f, err := os.Open(fp) diff --git a/util/confutil/config_unix.go b/util/confutil/config_unix.go new file mode 100644 index 000000000000..d1b34021c20d --- /dev/null +++ b/util/confutil/config_unix.go @@ -0,0 +1,46 @@ +//go:build !windows +// +build !windows + +package confutil + +import ( + "os" + "os/user" + "strconv" +) + +func init() { + // If the SUDO_COMMAND environment variable is set, we are likely running + // as a sudoer. In this case, we need to ensure that the user and group + // IDs are set to the correct values only if sudo HOME env matches the home + // directory of the user that ran sudo. This is necessary to ensure the + // correct permissions are set on the files that are created in the + // configuration directory. + sudoerUID, sudoerGID = func() (int, int) { + if _, ok := os.LookupEnv("SUDO_COMMAND"); !ok { + return -1, -1 + } + suidenv := os.Getenv("SUDO_UID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_UID + sgidenv := os.Getenv("SUDO_GID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_GID + if suidenv == "" || sgidenv == "" { + return -1, -1 + } + usr, err := user.LookupId(suidenv) + if err != nil { + return -1, -1 + } + suid, err := strconv.Atoi(suidenv) + if err != nil { + return -1, -1 + } + sgid, err := strconv.Atoi(sgidenv) + if err != nil { + return -1, -1 + } + home, _ := os.UserHomeDir() + if home == "" || usr.HomeDir != home { + return -1, -1 + } + return suid, sgid + }() +} diff --git a/util/confutil/node.go b/util/confutil/node.go index b63596083ad2..3c26ef5fb02e 100644 --- a/util/confutil/node.go +++ b/util/confutil/node.go @@ -10,23 +10,24 @@ import ( var nodeIdentifierMu sync.Mutex -func TryNodeIdentifier(configDir string) (out string) { +func TryNodeIdentifier(config *Config) (out string) { nodeIdentifierMu.Lock() defer nodeIdentifierMu.Unlock() - sessionFile := filepath.Join(configDir, ".buildNodeID") - if _, err := os.Lstat(sessionFile); err != nil { + sessionFilename := ".buildNodeID" + sessionFilepath := filepath.Join(config.Dir(), sessionFilename) + if _, err := os.Lstat(sessionFilepath); err != nil { if os.IsNotExist(err) { // create a new file with stored randomness b := make([]byte, 8) if _, err := rand.Read(b); err != nil { return out } - if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil { + if err := config.WriteFile(sessionFilename, []byte(hex.EncodeToString(b)), 0600); err != nil { return out } } } - dt, err := os.ReadFile(sessionFile) + dt, err := os.ReadFile(sessionFilepath) if err == nil { return string(dt) } diff --git a/util/osutil/fs.go b/util/osutil/fs.go new file mode 100644 index 000000000000..ea0c30e95f3e --- /dev/null +++ b/util/osutil/fs.go @@ -0,0 +1,83 @@ +package osutil + +import ( + "os" + "strconv" + + "github.com/docker/docker/pkg/ioutils" + "github.com/pkg/errors" +) + +func MkdirAll(path string, perm os.FileMode) error { + if err := os.MkdirAll(path, perm); err != nil { + return err + } + if suid, sgid, ok := sudoOwner(); ok { + return os.Chown(path, suid, sgid) + } + return nil +} + +func MkdirTemp(dir, pattern string) (string, error) { + tmpdir, err := os.MkdirTemp(dir, pattern) + if err != nil { + return "", err + } + if suid, sgid, ok := sudoOwner(); ok { + return tmpdir, os.Chown(tmpdir, suid, sgid) + } + return tmpdir, nil +} + +func WriteFile(filename string, data []byte, perm os.FileMode) error { + if err := os.WriteFile(filename, data, perm); err != nil { + return err + } + if suid, sgid, ok := sudoOwner(); ok { + return os.Chown(filename, suid, sgid) + } + return nil +} + +func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { + if err := ioutils.AtomicWriteFile(filename, data, perm); err != nil { + return err + } + if suid, sgid, ok := sudoOwner(); ok { + return os.Chown(filename, suid, sgid) + } + return nil +} + +func Create(name string) (*os.File, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + if suid, sgid, ok := sudoOwner(); ok { + if err := f.Chown(suid, sgid); err != nil { + return nil, errors.Wrapf(err, "failed to chown %s", name) + } + } + return f, nil +} + +func sudoOwner() (int, int, bool) { + if _, ok := os.LookupEnv("SUDO_COMMAND"); !ok { + return -1, -1, false + } + sudoUID := os.Getenv("SUDO_UID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_UID + sudoGID := os.Getenv("SUDO_GID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_GID + if sudoUID == "" || sudoGID == "" { + return -1, -1, false + } + suid, err := strconv.Atoi(sudoUID) + if err != nil { + return -1, -1, false + } + sgid, err := strconv.Atoi(sudoGID) + if err != nil { + return -1, -1, false + } + return suid, sgid, true +}