From ee59ba3226033d3fb5ff71111785fec5469070d2 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:07:02 +0100 Subject: [PATCH] add global flag to define download directory for remote configuration (oci & git) Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/compose.go | 23 +++++++++++++++++++-- docs/reference/docker_compose.yaml | 9 +++++++++ internal/paths/paths.go | 20 +++++++++++++++++++ pkg/remote/git.go | 30 +++++++++++++++++++--------- pkg/remote/oci.go | 32 ++++++++++++++++++++---------- 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index f5bfb013243..1e04c600038 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -150,6 +150,7 @@ type ProjectOptions struct { Progress string Offline bool All bool + DownloadDir string } // ProjectFunc does stuff within a types.Project @@ -226,7 +227,9 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) { f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode") f.StringVar(&o.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", "))) f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services") + f.StringVar(&o.DownloadDir, "download-dir", "", "Directory to store OCI or GIT Compose configurations") _ = f.MarkHidden("workdir") + _ = f.MarkHidden("download-dir") } // get default value for a command line flag that is set by a coma-separated value in environment variable @@ -372,8 +375,8 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL if o.Offline { return nil } - git := remote.NewGitRemoteLoader(o.Offline) - oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline) + git := remote.NewGitRemoteLoader(o.Offline, o.DownloadDir) + oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline, o.DownloadDir) return []loader.ResourceLoader{git, oci} } @@ -537,6 +540,22 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli } } + if opts.DownloadDir != "" { + if len(opts.ConfigPaths) == 0 { + return fmt.Errorf("cannot use --download-dir without --file using oci:// or git:// prefix") + } + remoteResources := false + for _, file := range opts.ConfigPaths { + if strings.HasPrefix(file, "oci://") || strings.HasPrefix(file, "git://") { + remoteResources = true + break + } + } + if !remoteResources { + return fmt.Errorf("cannot use --download-dir without --file using oci:// or git:// prefix") + } + } + composeCmd := cmd for { if composeCmd.Name() == PluginName { diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml index 1c6fb4970e7..c88fb4b995e 100644 --- a/docs/reference/docker_compose.yaml +++ b/docs/reference/docker_compose.yaml @@ -102,6 +102,15 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: download-dir + value_type: string + description: Directory to store OCI or GIT Compose configurations + deprecated: false + hidden: true + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: dry-run value_type: bool default_value: "false" diff --git a/internal/paths/paths.go b/internal/paths/paths.go index 4e4c01b8cc4..b074803406b 100644 --- a/internal/paths/paths.go +++ b/internal/paths/paths.go @@ -118,3 +118,23 @@ func EncompassingPaths(paths []string) []string { } return result } + +func GetAbsPath(path string) (string, error) { + if filepath.IsAbs(path) { + return path, nil + } + + if strings.HasPrefix(path, "~") { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, path[1:]), nil + } + + wd, err := os.Getwd() + if err != nil { + return "", err + } + return filepath.Join(wd, path), nil +} diff --git a/pkg/remote/git.go b/pkg/remote/git.go index 86916537218..8c5c5e9b726 100644 --- a/pkg/remote/git.go +++ b/pkg/remote/git.go @@ -25,6 +25,8 @@ import ( "regexp" "strconv" + "github.com/docker/compose/v2/internal/paths" + "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/loader" "github.com/compose-spec/compose-go/v2/types" @@ -45,16 +47,18 @@ func gitRemoteLoaderEnabled() (bool, error) { return false, nil } -func NewGitRemoteLoader(offline bool) loader.ResourceLoader { +func NewGitRemoteLoader(offline bool, downloadDirectory string) loader.ResourceLoader { return gitRemoteLoader{ - offline: offline, - known: map[string]string{}, + offline: offline, + known: map[string]string{}, + downloadDirectory: downloadDirectory, } } type gitRemoteLoader struct { - offline bool - known map[string]string + offline bool + known map[string]string + downloadDirectory string } func (g gitRemoteLoader) Accept(path string) bool { @@ -89,12 +93,20 @@ func (g gitRemoteLoader) Load(ctx context.Context, path string) (string, error) return "", err } - cache, err := cacheDir() - if err != nil { - return "", fmt.Errorf("initializing remote resource cache: %w", err) + if local == "" { + cache, err := cacheDir() + if err != nil { + return "", fmt.Errorf("initializing remote resource cache: %w", err) + } + local = cache + } else { + local, err = paths.GetAbsPath(g.downloadDirectory) + if err != nil { + return "", err + } } - local = filepath.Join(cache, ref.Commit) + local = filepath.Join(local, ref.Commit) if _, err := os.Stat(local); os.IsNotExist(err) { if g.offline { return "", nil diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go index 0f53a4c7e6f..1bb429021e9 100644 --- a/pkg/remote/oci.go +++ b/pkg/remote/oci.go @@ -31,6 +31,7 @@ import ( "github.com/docker/buildx/util/imagetools" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/internal/ocipush" + "github.com/docker/compose/v2/internal/paths" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -47,18 +48,20 @@ func ociRemoteLoaderEnabled() (bool, error) { return false, nil } -func NewOCIRemoteLoader(dockerCli command.Cli, offline bool) loader.ResourceLoader { +func NewOCIRemoteLoader(dockerCli command.Cli, offline bool, downloadDirectory string) loader.ResourceLoader { return ociRemoteLoader{ - dockerCli: dockerCli, - offline: offline, - known: map[string]string{}, + dockerCli: dockerCli, + offline: offline, + downloadDirectory: downloadDirectory, + known: map[string]string{}, } } type ociRemoteLoader struct { - dockerCli command.Cli - offline bool - known map[string]string + dockerCli command.Cli + offline bool + known map[string]string + downloadDirectory string } const prefix = "oci://" @@ -98,12 +101,19 @@ func (g ociRemoteLoader) Load(ctx context.Context, path string) (string, error) return "", err } - cache, err := cacheDir() - if err != nil { - return "", fmt.Errorf("initializing remote resource cache: %w", err) + if local == "" { + cache, err := cacheDir() + if err != nil { + return "", fmt.Errorf("initializing remote resource cache: %w", err) + } + local = filepath.Join(cache, descriptor.Digest.Hex()) + } else { + local, err = paths.GetAbsPath(g.downloadDirectory) + if err != nil { + return "", err + } } - local = filepath.Join(cache, descriptor.Digest.Hex()) composeFile := filepath.Join(local, "compose.yaml") if _, err = os.Stat(local); os.IsNotExist(err) { var manifest v1.Manifest