Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

### IMPROVEMENTS:

* Add `windows_create_parent_dirs` option to automatically create destination parent directories on uploads to Windows containers by @wbpascal in https://github.com/hashicorp/packer-plugin-docker/pull/230

## 1.1.3 (June 9, 2026)

### BUG FIXES:
Expand Down
6 changes: 6 additions & 0 deletions builder/docker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ type Config struct {
// running on a windows host. This is necessary for building Windows
// containers, because our normal docker bindings do not work for them.
WindowsContainer bool `mapstructure:"windows_container" required:"false"`
// If true, creates parent directories for file and directory uploads to
// Windows containers. This only applies when `windows_container` is true.
WindowsCreateParentDirs bool `mapstructure:"windows_create_parent_dirs" required:"false"`
// Set platform if server is multi-platform capable
Platform string `mapstructure:"platform" required:"false"`

Expand Down Expand Up @@ -205,6 +208,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {

var errs *packersdk.MultiError
var warnings []string
if c.WindowsCreateParentDirs && !c.WindowsContainer {
warnings = append(warnings, "`windows_create_parent_dirs` only applies when `windows_container` is true")
}

if !c.BuildConfig.IsDefault() {
_, err := c.BuildConfig.Prepare()
Expand Down
2 changes: 2 additions & 0 deletions builder/docker/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions builder/docker/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package docker
import (
"io/ioutil"
"os"
"strings"
"testing"
)

Expand Down Expand Up @@ -149,6 +150,40 @@ func TestConfigPrepare_pull(t *testing.T) {
}
}

func TestConfigPrepare_windowsCreateParentDirs(t *testing.T) {
raw := testConfig()
var c Config
warns, errs := c.Prepare(raw)
testConfigOk(t, warns, errs)
if c.WindowsCreateParentDirs {
t.Fatal("windows_create_parent_dirs should default to false")
}

raw = testConfig()
raw["windows_container"] = true
raw["windows_create_parent_dirs"] = true
c = Config{}
warns, errs = c.Prepare(raw)
testConfigOk(t, warns, errs)
if !c.WindowsCreateParentDirs {
t.Fatal("windows_create_parent_dirs should be true")
}

raw = testConfig()
raw["windows_create_parent_dirs"] = true
c = Config{}
warns, errs = c.Prepare(raw)
if errs != nil {
t.Fatalf("bad: %s", errs)
}
if len(warns) != 1 {
t.Fatalf("expected one warning, got %#v", warns)
}
if !strings.Contains(warns[0], "windows_create_parent_dirs") {
t.Fatalf("expected windows_create_parent_dirs warning, got %#v", warns)
}
}

// Test variations of a build bootstrap config; including unset
func TestConfigBuildBootstrapConfig(t *testing.T) {
tests := []struct {
Expand Down
51 changes: 45 additions & 6 deletions builder/docker/windows_container_communicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"log"
"os"
"path/filepath"
"strings"

packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
Expand All @@ -28,6 +29,28 @@ type WindowsContainerCommunicator struct {
Communicator
}

func powerShellSingleQuote(s string) string {
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
}

// Creates the parent and all other preceding directories in the container for the given destination.
// Does not recreate paths that already exist.
func (c *WindowsContainerCommunicator) ensureContainerParentDir(ctx context.Context, destination string) error {
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("$parent = Split-Path -Parent %s; if ($parent -and -not (Test-Path -LiteralPath $parent)) { New-Item -ItemType Directory -Force -LiteralPath $parent -ErrorAction Stop | Out-Null }", powerShellSingleQuote(destination)),
}
if err := c.Start(ctx, cmd); err != nil {
return err
}

cmd.Wait()
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Upload failed to create parent directory for %s: %d", destination, cmd.ExitStatus())
}

return nil
}

// Upload uses docker exec to copy the file from the host to the container
func (c *WindowsContainerCommunicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error {
// Create a temporary file to store the upload
Expand All @@ -48,13 +71,21 @@ func (c *WindowsContainerCommunicator) Upload(dst string, src io.Reader, fi *os.
}
tempfile.Close()

// Before copying the file into place, we need to make sure that the parent folders
// exists if windows_create_parent_dirs was specified in the plugin config.
// See also https://github.com/hashicorp/packer-plugin-docker/issues/208
ctx := context.TODO()
if c.Config.WindowsCreateParentDirs {
if err := c.ensureContainerParentDir(ctx, dst); err != nil {
return err
}
}

// Copy the file into place by copying the temporary file we put
// into the shared folder into the proper location in the container
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("Copy-Item -Path %s/%s -Destination %s", c.ContainerDir,
filepath.Base(tempfile.Name()), dst),
Command: fmt.Sprintf("Copy-Item -LiteralPath %s -Destination %s -Force", powerShellSingleQuote(filepath.Join(c.ContainerDir, filepath.Base(tempfile.Name()))), powerShellSingleQuote(dst)),
}
ctx := context.TODO()
if err := c.Start(ctx, cmd); err != nil {
return err
}
Expand Down Expand Up @@ -135,12 +166,20 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude
containerDst = filepath.Join(dst, filepath.Base(src))
}

// Before copying the files into place, we need to make sure that the parent folders
// exists if windows_create_parent_dirs was specified in the plugin config.
// See also https://github.com/hashicorp/packer-plugin-docker/issues/208
ctx := context.TODO()
if c.Config.WindowsCreateParentDirs {
if err := c.ensureContainerParentDir(ctx, containerDst); err != nil {
return err
}
}

// Make the directory, then copy into it
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("Copy-Item %s -Destination %s -Recurse",
containerSrc, containerDst),
Command: fmt.Sprintf("Copy-Item -LiteralPath %s -Destination %s -Recurse", powerShellSingleQuote(containerSrc), powerShellSingleQuote(containerDst)),
}
ctx := context.TODO()
if err := c.Start(ctx, cmd); err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions docs-partials/builder/docker/Config-not-required.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
running on a windows host. This is necessary for building Windows
containers, because our normal docker bindings do not work for them.

- `windows_create_parent_dirs` (bool) - If true, creates parent directories for file and directory uploads to
Windows containers. This only applies when `windows_container` is true.

- `platform` (string) - Set platform if server is multi-platform capable

- `login` (bool) - This is used to login to a private docker repository (e.g., dockerhub)
Expand Down
7 changes: 7 additions & 0 deletions docs/builders/docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@ If you are building a Windows container, you must set the template option
`"windows_container": true`. Please note that docker cannot export Windows
containers, so you must either commit or discard them.

Set `"windows_create_parent_dirs": true` to have Packer create missing parent
directories before uploading files or directories to Windows containers. This
helps Windows container uploads behave more like Linux container uploads that
use `docker cp`.

The following is a fully functional template for building a Windows
container.

Expand All @@ -428,6 +433,7 @@ source "docker" "windows" {
image = "microsoft/windowsservercore:1709"
container_dir = "c:/app"
windows_container = true
windows_create_parent_dirs = true
commit = true
}

Expand All @@ -446,6 +452,7 @@ build {
"image": "microsoft/windowsservercore:1709",
"container_dir": "c:/app",
"windows_container": true,
"windows_create_parent_dirs": true,
"commit": true
}
]
Expand Down