c8d: Fix `docker diff`
- fixes https://github.com/moby/moby/issues/46607

**- What I did**

Diffing a container yielded some extra changes that come from the files/directories that we mount inside the container (/etc/resolv.conf for example). To avoid that we create an intermediate snapshot that has these files, with this we can now diff the container fs with its parent and only get the differences that were made inside the container.

**- How I did it**

Added a layer for the base snapshot of a container containing the files that are created by the runtime.

**- How to verify it**

Run `TestDiff`

```console
$ make DOCKER_GRAPHDRIVER=overlayfs TEST_FILTER=TestDiff TEST_INTEGRATION_USE_SNAPSHOTTER=1 TEST_IGNORE_CGROUP_CHECK=1 test-integration
```

**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->
- containerd integration: Fixed `docker diff` containing extra differences

**- A picture of a cute animal (not mandatory but encouraged)**


integration/container/diff_test.go
package container // import "github.com/docker/docker/integration/container"

import (
	"testing"
	"time"

	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/integration/internal/container"
	"gotest.tools/v3/assert"
	"gotest.tools/v3/poll"
	"gotest.tools/v3/skip"
)

func TestDiff(t *testing.T) {
	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
	ctx := setupTest(t)
	apiClient := testEnv.APIClient()

	cID := container.Run(ctx, t, apiClient, container.WithCmd("sh", "-c", `mkdir /foo; echo xyzzy > /foo/bar`))

	// Wait for it to exit as cannot diff a running container on Windows, and
	// it will take a few seconds to exit. Also there's no way in Windows to
	// differentiate between an Add or a Modify, and all files are under
	// a "Files/" prefix.
	expected := []containertypes.FilesystemChange{
		{Kind: containertypes.ChangeAdd, Path: "/foo"},
		{Kind: containertypes.ChangeAdd, Path: "/foo/bar"},
	}
	if testEnv.DaemonInfo.OSType == "windows" {
		poll.WaitOn(t, container.IsInState(ctx, apiClient, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second))
		expected = []containertypes.FilesystemChange{
			{Kind: containertypes.ChangeModify, Path: "Files/foo"},
			{Kind: containertypes.ChangeModify, Path: "Files/foo/bar"},
		}
	}

	items, err := apiClient.ContainerDiff(ctx, cID)
	assert.NilError(t, err)
	assert.DeepEqual(t, expected, items)
}

package container // import "github.com/docker/docker/integration/container"

import (
	"testing"
	"time"

	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/integration/internal/container"
	"gotest.tools/v3/assert"
	"gotest.tools/v3/poll"
	"gotest.tools/v3/skip"
)

func TestDiff(t *testing.T) {
	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "cannot diff a running container on Windows")
	ctx := setupTest(t)
	apiClient := testEnv.APIClient()

	cID := container.Run(ctx, t, apiClient, container.WithCmd("sh", "-c", `mkdir /foo; echo xyzzy > /foo/bar`))

	expected := []containertypes.FilesystemChange{
		{Kind: containertypes.ChangeAdd, Path: "/foo"},
		{Kind: containertypes.ChangeAdd, Path: "/foo/bar"},
	}

	items, err := apiClient.ContainerDiff(ctx, cID)
	assert.NilError(t, err)
	assert.DeepEqual(t, expected, items)
}

func TestDiffStoppedContainer(t *testing.T) {
	// There's no way in Windows to differentiate between an Add or a Modify,
	// and all files are under a "Files/" prefix.
	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
	ctx := setupTest(t)
	apiClient := testEnv.APIClient()

	cID := container.Run(ctx, t, apiClient, container.WithCmd("sh", "-c", `mkdir /foo; echo xyzzy > /foo/bar`))

	poll.WaitOn(t, container.IsInState(ctx, apiClient, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second))

	expected := []containertypes.FilesystemChange{
		{Kind: containertypes.ChangeAdd, Path: "/foo"},
		{Kind: containertypes.ChangeAdd, Path: "/foo/bar"},
	}
	if testEnv.DaemonInfo.OSType == "windows" {
		expected = []containertypes.FilesystemChange{
			{Kind: containertypes.ChangeModify, Path: "Files/foo"},
			{Kind: containertypes.ChangeModify, Path: "Files/foo/bar"},
		}
	}

	items, err := apiClient.ContainerDiff(ctx, cID)
	assert.NilError(t, err)
	assert.DeepEqual(t, expected, items)
}

Right, so I (perhaps incorrectly) assumed you added the new test to split the "won't work on windows" from "should work on windows" ones, so that we could _skip_ the new test, but _run_ the existing one.

If we think this should work on Windows, perhaps we should try removing the skip and see if it does :)


> FIXME is quite criptic and doesn't really give any useful info

Yes, those are quite unfortunate. I _think_ those were all added when we started to "enable CI for Windows", but there may have been _too many_ tests failing at the time to document the "why" for all individually and/or the people working on enabling CI not having access to a Windows machine (it may even have been that there was no public Windows release yet that could run, and only Microsoft internal builds). In hindsight; we should have asked Microsoft to look at all of them and document the reason.

That said, I always interpreted the `FIXME` ones as "from our perspective, we don't see any reason why this would _not_ be able to work on Windows, but someone just needs to do the work to fix (the test) to make it so".

