diff --git a/components/engine/Makefile b/components/engine/Makefile index d9fb68b1de..7d50afa7d4 100644 --- a/components/engine/Makefile +++ b/components/engine/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary dynbinary build cross deb help init-go-pkg-cache install manpages rpm run shell test test-docker-py test-integration test-unit tgz validate win +.PHONY: all binary dynbinary build cross deb help init-go-pkg-cache install manpages rpm run shell test test-docker-py test-integration test-unit validate win # set the graph driver as the current graphdriver if not set DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info 2>&1 | grep "Storage Driver" | sed 's/.*: //')) @@ -164,9 +164,6 @@ test-integration: build ## run the integration tests test-unit: build ## run the unit tests $(DOCKER_RUN_DOCKER) hack/test/unit -tgz: build ## build the archives (.zip on windows and .tgz\notherwise) containing the binaries - $(DOCKER_RUN_DOCKER) hack/make.sh dynbinary binary cross tgz - validate: build ## validate DCO, Seccomp profile generation, gofmt,\n./pkg/ isolation, golint, tests, tomls, go vet and vendor $(DOCKER_RUN_DOCKER) hack/validate/all diff --git a/components/engine/api/server/router/container/container_routes.go b/components/engine/api/server/router/container/container_routes.go index 30fd3a15bf..95bbe0e533 100644 --- a/components/engine/api/server/router/container/container_routes.go +++ b/components/engine/api/server/router/container/container_routes.go @@ -28,7 +28,7 @@ func (s *containerRouter) getContainersJSON(ctx context.Context, w http.Response if err := httputils.ParseForm(r); err != nil { return err } - filter, err := filters.FromParam(r.Form.Get("filters")) + filter, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } @@ -588,7 +588,7 @@ func (s *containerRouter) postContainersPrune(ctx context.Context, w http.Respon return err } - pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return validationError{err} } diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go index 86d73df0e1..7214f4eb7e 100644 --- a/components/engine/api/server/router/image/image_routes.go +++ b/components/engine/api/server/router/image/image_routes.go @@ -302,7 +302,7 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, return err } - imageFilters, err := filters.FromParam(r.Form.Get("filters")) + imageFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } @@ -385,7 +385,7 @@ func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter return err } - pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/server/router/network/filter.go b/components/engine/api/server/router/network/filter.go index 21fc828b8b..6a5fce6892 100644 --- a/components/engine/api/server/router/network/filter.go +++ b/components/engine/api/server/router/network/filter.go @@ -45,27 +45,27 @@ func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.N displayNet := []types.NetworkResource{} for _, nw := range nws { - if filter.Include("driver") { + if filter.Contains("driver") { if !filter.ExactMatch("driver", nw.Driver) { continue } } - if filter.Include("name") { + if filter.Contains("name") { if !filter.Match("name", nw.Name) { continue } } - if filter.Include("id") { + if filter.Contains("id") { if !filter.Match("id", nw.ID) { continue } } - if filter.Include("label") { + if filter.Contains("label") { if !filter.MatchKVList("label", nw.Labels) { continue } } - if filter.Include("scope") { + if filter.Contains("scope") { if !filter.ExactMatch("scope", nw.Scope) { continue } @@ -73,7 +73,7 @@ func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.N displayNet = append(displayNet, nw) } - if filter.Include("type") { + if filter.Contains("type") { typeNet := []types.NetworkResource{} errFilter := filter.WalkValues("type", func(fval string) error { passList, err := filterNetworkByType(displayNet, fval) diff --git a/components/engine/api/server/router/network/network_routes.go b/components/engine/api/server/router/network/network_routes.go index ad3e74e6eb..ebf4bf635e 100644 --- a/components/engine/api/server/router/network/network_routes.go +++ b/components/engine/api/server/router/network/network_routes.go @@ -37,7 +37,7 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit } filter := r.Form.Get("filters") - netFilters, err := filters.FromParam(filter) + netFilters, err := filters.FromJSON(filter) if err != nil { return err } @@ -489,7 +489,7 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr return err } - pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/server/router/plugin/plugin_routes.go b/components/engine/api/server/router/plugin/plugin_routes.go index 79e3cf5de8..d74a01f300 100644 --- a/components/engine/api/server/router/plugin/plugin_routes.go +++ b/components/engine/api/server/router/plugin/plugin_routes.go @@ -290,7 +290,7 @@ func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, return err } - pluginFilters, err := filters.FromParam(r.Form.Get("filters")) + pluginFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/server/router/swarm/cluster_routes.go b/components/engine/api/server/router/swarm/cluster_routes.go index 55743a6218..5b6c19d9c1 100644 --- a/components/engine/api/server/router/swarm/cluster_routes.go +++ b/components/engine/api/server/router/swarm/cluster_routes.go @@ -151,7 +151,7 @@ func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r if err := httputils.ParseForm(r); err != nil { return err } - filter, err := filters.FromParam(r.Form.Get("filters")) + filter, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return invalidRequestError{err} } @@ -277,7 +277,7 @@ func (sr *swarmRouter) getNodes(ctx context.Context, w http.ResponseWriter, r *h if err := httputils.ParseForm(r); err != nil { return err } - filter, err := filters.FromParam(r.Form.Get("filters")) + filter, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } @@ -339,7 +339,7 @@ func (sr *swarmRouter) getTasks(ctx context.Context, w http.ResponseWriter, r *h if err := httputils.ParseForm(r); err != nil { return err } - filter, err := filters.FromParam(r.Form.Get("filters")) + filter, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } @@ -367,7 +367,7 @@ func (sr *swarmRouter) getSecrets(ctx context.Context, w http.ResponseWriter, r if err := httputils.ParseForm(r); err != nil { return err } - filters, err := filters.FromParam(r.Form.Get("filters")) + filters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } @@ -434,7 +434,7 @@ func (sr *swarmRouter) getConfigs(ctx context.Context, w http.ResponseWriter, r if err := httputils.ParseForm(r); err != nil { return err } - filters, err := filters.FromParam(r.Form.Get("filters")) + filters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/server/router/system/system_routes.go b/components/engine/api/server/router/system/system_routes.go index 5884388ebe..62060a4ccf 100644 --- a/components/engine/api/server/router/system/system_routes.go +++ b/components/engine/api/server/router/system/system_routes.go @@ -127,7 +127,7 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r * } } - ef, err := filters.FromParam(r.Form.Get("filters")) + ef, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/server/router/volume/volume_routes.go b/components/engine/api/server/router/volume/volume_routes.go index f0f490119b..bfff51ab1e 100644 --- a/components/engine/api/server/router/volume/volume_routes.go +++ b/components/engine/api/server/router/volume/volume_routes.go @@ -72,7 +72,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit return err } - pruneFilters, err := filters.FromParam(r.Form.Get("filters")) + pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) if err != nil { return err } diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index 275c2e9cec..afec91e72a 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -6941,16 +6941,20 @@ paths: description: | A JSON encoded value of filters (a `map[string][]string`) to process on the event list. Available filters: + - `config=` config name or ID - `container=` container name or ID - `daemon=` daemon name or ID - `event=` event type - `image=` image name or ID - `label=` image or container label - `network=` network name or ID + - `node=` node ID - `plugin`= plugin name or ID - `scope`= local or swarm - - `type=` object to filter by, one of `container`, `image`, `volume`, `network`, `daemon`, `plugin`, `node`, `service` or `secret` - - `volume=` volume name or ID + - `secret=` secret name or ID + - `service=` service name or ID + - `type=` object to filter by, one of `container`, `image`, `volume`, `network`, `daemon`, `plugin`, `node`, `service`, `secret` or `config` + - `volume=` volume name type: "string" tags: ["System"] /system/df: diff --git a/components/engine/api/types/filters/parse_test.go b/components/engine/api/types/filters/parse_test.go index 5279054854..67b2ec930c 100644 --- a/components/engine/api/types/filters/parse_test.go +++ b/components/engine/api/types/filters/parse_test.go @@ -42,14 +42,14 @@ func TestParseArgsEdgeCase(t *testing.T) { } } -func TestToParam(t *testing.T) { +func TestToJSON(t *testing.T) { fields := map[string]map[string]bool{ "created": {"today": true}, "image.name": {"ubuntu*": true, "*untu": true}, } a := Args{fields: fields} - _, err := ToParam(a) + _, err := ToJSON(a) if err != nil { t.Errorf("failed to marshal the filters: %s", err) } @@ -80,7 +80,7 @@ func TestToParamWithVersion(t *testing.T) { } } -func TestFromParam(t *testing.T) { +func TestFromJSON(t *testing.T) { invalids := []string{ "anything", "['a','list']", @@ -103,14 +103,14 @@ func TestFromParam(t *testing.T) { } for _, invalid := range invalids { - if _, err := FromParam(invalid); err == nil { + if _, err := FromJSON(invalid); err == nil { t.Fatalf("Expected an error with %v, got nothing", invalid) } } for expectedArgs, matchers := range valid { for _, json := range matchers { - args, err := FromParam(json) + args, err := FromJSON(json) if err != nil { t.Fatal(err) } @@ -136,11 +136,11 @@ func TestFromParam(t *testing.T) { func TestEmpty(t *testing.T) { a := Args{} - v, err := ToParam(a) + v, err := ToJSON(a) if err != nil { t.Errorf("failed to marshal the filters: %s", err) } - v1, err := FromParam(v) + v1, err := FromJSON(v) if err != nil { t.Errorf("%s", err) } @@ -337,6 +337,17 @@ func TestOnlyOneExactMatch(t *testing.T) { } } +func TestContains(t *testing.T) { + f := NewArgs() + if f.Contains("status") { + t.Fatal("Expected to not contain a status key, got true") + } + f.Add("status", "running") + if !f.Contains("status") { + t.Fatal("Expected to contain a status key, got false") + } +} + func TestInclude(t *testing.T) { f := NewArgs() if f.Include("status") { diff --git a/components/engine/client/config_list.go b/components/engine/client/config_list.go index 8483ca14d1..57febc9ffb 100644 --- a/components/engine/client/config_list.go +++ b/components/engine/client/config_list.go @@ -18,7 +18,7 @@ func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptio query := url.Values{} if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } diff --git a/components/engine/client/image_search.go b/components/engine/client/image_search.go index b0fcd5c23d..5566e92555 100644 --- a/components/engine/client/image_search.go +++ b/components/engine/client/image_search.go @@ -21,7 +21,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I query.Set("limit", fmt.Sprintf("%d", options.Limit)) if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return results, err } diff --git a/components/engine/client/node_list.go b/components/engine/client/node_list.go index 3e8440f08e..fed22992c9 100644 --- a/components/engine/client/node_list.go +++ b/components/engine/client/node_list.go @@ -15,7 +15,7 @@ func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) query := url.Values{} if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err diff --git a/components/engine/client/secret_list.go b/components/engine/client/secret_list.go index 0d33ecfbc9..fdee6e2e0b 100644 --- a/components/engine/client/secret_list.go +++ b/components/engine/client/secret_list.go @@ -18,7 +18,7 @@ func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptio query := url.Values{} if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } diff --git a/components/engine/client/service_list.go b/components/engine/client/service_list.go index c29e6d407d..eb3ff9739c 100644 --- a/components/engine/client/service_list.go +++ b/components/engine/client/service_list.go @@ -15,7 +15,7 @@ func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOpt query := url.Values{} if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } diff --git a/components/engine/client/task_list.go b/components/engine/client/task_list.go index 66324da959..01bd695257 100644 --- a/components/engine/client/task_list.go +++ b/components/engine/client/task_list.go @@ -15,7 +15,7 @@ func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions) query := url.Values{} if options.Filters.Len() > 0 { - filterJSON, err := filters.ToParam(options.Filters) + filterJSON, err := filters.ToJSON(options.Filters) if err != nil { return nil, err } diff --git a/components/engine/client/utils.go b/components/engine/client/utils.go index f3d8877df7..137705065c 100644 --- a/components/engine/client/utils.go +++ b/components/engine/client/utils.go @@ -24,7 +24,7 @@ func getDockerOS(serverHeader string) string { func getFiltersQuery(f filters.Args) (url.Values, error) { query := url.Values{} if f.Len() > 0 { - filterJSON, err := filters.ToParam(f) + filterJSON, err := filters.ToJSON(f) if err != nil { return query, err } diff --git a/components/engine/daemon/cluster/errors.go b/components/engine/daemon/cluster/errors.go index 1698229427..0ffe78b98b 100644 --- a/components/engine/daemon/cluster/errors.go +++ b/components/engine/daemon/cluster/errors.go @@ -15,6 +15,9 @@ const ( // errSwarmCertificatesExpired is returned if docker was not started for the whole validity period and they had no chance to renew automatically. errSwarmCertificatesExpired notAvailableError = "Swarm certificates have expired. To replace them, leave the swarm and join again." + + // errSwarmNotManager is returned if the node is not a swarm manager. + errSwarmNotManager notAvailableError = "This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager." ) type notFoundError struct { diff --git a/components/engine/daemon/cluster/services.go b/components/engine/daemon/cluster/services.go index 0edb407d3a..f3fba8d9d1 100644 --- a/components/engine/daemon/cluster/services.go +++ b/components/engine/daemon/cluster/services.go @@ -74,7 +74,7 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv services := make([]types.Service, 0, len(r.Services)) for _, service := range r.Services { - if options.Filters.Include("mode") { + if options.Filters.Contains("mode") { var mode string switch service.Spec.GetMode().(type) { case *swarmapi.ServiceSpec_Global: diff --git a/components/engine/daemon/cluster/swarm.go b/components/engine/daemon/cluster/swarm.go index 1fa62920e3..e3fffe983d 100644 --- a/components/engine/daemon/cluster/swarm.go +++ b/components/engine/daemon/cluster/swarm.go @@ -26,9 +26,13 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) { defer c.controlMutex.Unlock() if c.nr != nil { if req.ForceNewCluster { + // Take c.mu temporarily to wait for presently running // API handlers to finish before shutting down the node. c.mu.Lock() + if !c.nr.nodeState.IsManager() { + return "", errSwarmNotManager + } c.mu.Unlock() if err := c.nr.Stop(); err != nil { diff --git a/components/engine/daemon/cluster/tasks.go b/components/engine/daemon/cluster/tasks.go index 26706a2fa5..f52301fe47 100644 --- a/components/engine/daemon/cluster/tasks.go +++ b/components/engine/daemon/cluster/tasks.go @@ -15,7 +15,7 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error { filterTransform := func(filter filters.Args) error { - if filter.Include("service") { + if filter.Contains("service") { serviceFilters := filter.Get("service") for _, serviceFilter := range serviceFilters { service, err := getService(ctx, state.controlClient, serviceFilter, false) @@ -26,7 +26,7 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro filter.Add("service", service.ID) } } - if filter.Include("node") { + if filter.Contains("node") { nodeFilters := filter.Get("node") for _, nodeFilter := range nodeFilters { node, err := getNode(ctx, state.controlClient, nodeFilter) @@ -37,7 +37,7 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro filter.Add("node", node.ID) } } - if !filter.Include("runtime") { + if !filter.Contains("runtime") { // default to only showing container tasks filter.Add("runtime", "container") filter.Add("runtime", "") diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index c187f29ceb..c4762d3eae 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/daemon/daemon.go @@ -140,10 +140,7 @@ func (daemon *Daemon) StoreHosts(hosts []string) { // HasExperimental returns whether the experimental features of the daemon are enabled or not func (daemon *Daemon) HasExperimental() bool { - if daemon.configStore != nil && daemon.configStore.Experimental { - return true - } - return false + return daemon.configStore != nil && daemon.configStore.Experimental } func (daemon *Daemon) restore() error { diff --git a/components/engine/daemon/daemon_windows.go b/components/engine/daemon/daemon_windows.go index bbf5383c14..8dcea03b1a 100644 --- a/components/engine/daemon/daemon_windows.go +++ b/components/engine/daemon/daemon_windows.go @@ -27,8 +27,10 @@ import ( "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" blkiodev "github.com/opencontainers/runc/libcontainer/configs" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/mgr" ) const ( @@ -238,6 +240,29 @@ func checkSystem() error { return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed") } + // Ensure that the required Host Network Service and vmcompute services + // are running. Docker will fail in unexpected ways if this is not present. + var requiredServices = []string{"hns", "vmcompute"} + if err := ensureServicesInstalled(requiredServices); err != nil { + return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed") + } + + return nil +} + +func ensureServicesInstalled(services []string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + for _, service := range services { + s, err := m.OpenService(service) + if err != nil { + return errors.Wrapf(err, "failed to open service %s", service) + } + s.Close() + } return nil } diff --git a/components/engine/daemon/daemon_windows_test.go b/components/engine/daemon/daemon_windows_test.go new file mode 100644 index 0000000000..8b350cf20f --- /dev/null +++ b/components/engine/daemon/daemon_windows_test.go @@ -0,0 +1,72 @@ +// +build windows + +package daemon + +import ( + "strings" + "testing" + + "golang.org/x/sys/windows/svc/mgr" +) + +const existingService = "Power" + +func TestEnsureServicesExist(t *testing.T) { + m, err := mgr.Connect() + if err != nil { + t.Fatal("failed to connect to service manager, this test needs admin") + } + defer m.Disconnect() + s, err := m.OpenService(existingService) + if err != nil { + t.Fatalf("expected to find known inbox service %q, this test needs a known inbox service to run correctly", existingService) + } + defer s.Close() + + input := []string{existingService} + err = ensureServicesInstalled(input) + if err != nil { + t.Fatalf("unexpected error for input %q: %q", input, err) + } +} + +func TestEnsureServicesExistErrors(t *testing.T) { + m, err := mgr.Connect() + if err != nil { + t.Fatal("failed to connect to service manager, this test needs admin") + } + defer m.Disconnect() + s, err := m.OpenService(existingService) + if err != nil { + t.Fatalf("expected to find known inbox service %q, this test needs a known inbox service to run correctly", existingService) + } + defer s.Close() + + for _, testcase := range []struct { + input []string + expectedError string + }{ + { + input: []string{"daemon_windows_test_fakeservice"}, + expectedError: "failed to open service daemon_windows_test_fakeservice", + }, + { + input: []string{"daemon_windows_test_fakeservice1", "daemon_windows_test_fakeservice2"}, + expectedError: "failed to open service daemon_windows_test_fakeservice1", + }, + { + input: []string{existingService, "daemon_windows_test_fakeservice"}, + expectedError: "failed to open service daemon_windows_test_fakeservice", + }, + } { + t.Run(strings.Join(testcase.input, ";"), func(t *testing.T) { + err := ensureServicesInstalled(testcase.input) + if err == nil { + t.Fatalf("expected error for input %v", testcase.input) + } + if !strings.Contains(err.Error(), testcase.expectedError) { + t.Fatalf("expected error %q to contain %q", err.Error(), testcase.expectedError) + } + }) + } +} diff --git a/components/engine/daemon/events/filter.go b/components/engine/daemon/events/filter.go index 645f1ca917..61e3459bf8 100644 --- a/components/engine/daemon/events/filter.go +++ b/components/engine/daemon/events/filter.go @@ -27,6 +27,10 @@ func (ef *Filter) Include(ev events.Message) bool { ef.matchVolume(ev) && ef.matchNetwork(ev) && ef.matchImage(ev) && + ef.matchNode(ev) && + ef.matchService(ev) && + ef.matchSecret(ev) && + ef.matchConfig(ev) && ef.matchLabels(ev.Actor.Attributes) } @@ -49,14 +53,14 @@ func (ef *Filter) filterContains(field string, values map[string]struct{}) bool } func (ef *Filter) matchScope(scope string) bool { - if !ef.filter.Include("scope") { + if !ef.filter.Contains("scope") { return true } return ef.filter.ExactMatch("scope", scope) } func (ef *Filter) matchLabels(attributes map[string]string) bool { - if !ef.filter.Include("label") { + if !ef.filter.Contains("label") { return true } return ef.filter.MatchKVList("label", attributes) diff --git a/components/engine/daemon/images.go b/components/engine/daemon/images.go index f4110ce789..27860d9b92 100644 --- a/components/engine/daemon/images.go +++ b/components/engine/daemon/images.go @@ -67,7 +67,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs return nil, err } - if imageFilters.Include("dangling") { + if imageFilters.Contains("dangling") { if imageFilters.ExactMatch("dangling", "true") { danglingOnly = true } else if !imageFilters.ExactMatch("dangling", "false") { @@ -116,7 +116,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs } } - if imageFilters.Include("label") { + if imageFilters.Contains("label") { // Very old image that do not have image.Config (or even labels) if img.Config == nil { continue @@ -150,7 +150,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs newImage := newImage(img, size) for _, ref := range daemon.referenceStore.References(id.Digest()) { - if imageFilters.Include("reference") { + if imageFilters.Contains("reference") { var found bool var matchErr error for _, pattern := range imageFilters.Get("reference") { @@ -173,11 +173,11 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs if newImage.RepoDigests == nil && newImage.RepoTags == nil { if all || len(daemon.stores[platform].imageStore.Children(id)) == 0 { - if imageFilters.Include("dangling") && !danglingOnly { + if imageFilters.Contains("dangling") && !danglingOnly { //dangling=false case, so dangling image is not needed continue } - if imageFilters.Include("reference") { // skip images with no references if filtering by reference + if imageFilters.Contains("reference") { // skip images with no references if filtering by reference continue } newImage.RepoDigests = []string{"@"} diff --git a/components/engine/daemon/list.go b/components/engine/daemon/list.go index b171ffb1b8..9d43be2720 100644 --- a/components/engine/daemon/list.go +++ b/components/engine/daemon/list.go @@ -276,7 +276,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis } var taskFilter, isTask bool - if psFilters.Include("is-task") { + if psFilters.Contains("is-task") { if psFilters.ExactMatch("is-task", "true") { taskFilter = true isTask = true @@ -319,7 +319,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis imagesFilter := map[image.ID]bool{} var ancestorFilter bool - if psFilters.Include("ancestor") { + if psFilters.Contains("ancestor") { ancestorFilter = true psFilters.WalkValues("ancestor", func(ancestor string) error { id, platform, err := daemon.GetImageIDAndPlatform(ancestor) @@ -465,7 +465,7 @@ func includeContainerInList(container *container.Snapshot, ctx *listContext) ite return excludeContainer } - if ctx.filters.Include("volume") { + if ctx.filters.Contains("volume") { volumesByName := make(map[string]types.MountPoint) for _, m := range container.Mounts { if m.Name != "" { @@ -509,7 +509,7 @@ func includeContainerInList(container *container.Snapshot, ctx *listContext) ite networkExist = errors.New("container part of network") noNetworks = errors.New("container is not part of any networks") ) - if ctx.filters.Include("network") { + if ctx.filters.Contains("network") { err := ctx.filters.WalkValues("network", func(value string) error { if container.NetworkSettings == nil { return noNetworks @@ -585,7 +585,7 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) var ( volumesOut []*types.Volume ) - volFilters, err := filters.FromParam(filter) + volFilters, err := filters.FromJSON(filter) if err != nil { return nil, nil, err } @@ -627,17 +627,17 @@ func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ( var retVols []volume.Volume for _, vol := range vols { - if filter.Include("name") { + if filter.Contains("name") { if !filter.Match("name", vol.Name()) { continue } } - if filter.Include("driver") { + if filter.Contains("driver") { if !filter.ExactMatch("driver", vol.DriverName()) { continue } } - if filter.Include("label") { + if filter.Contains("label") { v, ok := vol.(volume.DetailedVolume) if !ok { continue @@ -649,7 +649,7 @@ func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ( retVols = append(retVols, vol) } danglingOnly := false - if filter.Include("dangling") { + if filter.Contains("dangling") { if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") { danglingOnly = true } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") { diff --git a/components/engine/daemon/prune.go b/components/engine/daemon/prune.go index 66eca2b6e5..837cb83941 100644 --- a/components/engine/daemon/prune.go +++ b/components/engine/daemon/prune.go @@ -182,7 +182,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args rep := &types.ImagesPruneReport{} danglingOnly := true - if pruneFilters.Include("dangling") { + if pruneFilters.Contains("dangling") { if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") { danglingOnly = false } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") { @@ -440,7 +440,7 @@ func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Ar func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { until := time.Time{} - if !pruneFilters.Include("until") { + if !pruneFilters.Contains("until") { return until, nil } untilFilters := pruneFilters.Get("until") @@ -464,8 +464,8 @@ func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { return false } // By default MatchKVList will return true if field (like 'label!') does not exist - // So we have to add additional Include("label!") check - if pruneFilters.Include("label!") { + // So we have to add additional Contains("label!") check + if pruneFilters.Contains("label!") { if pruneFilters.MatchKVList("label!", labels) { return false } diff --git a/components/engine/daemon/search.go b/components/engine/daemon/search.go index 540e247fe4..25744cb723 100644 --- a/components/engine/daemon/search.go +++ b/components/engine/daemon/search.go @@ -23,7 +23,7 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s authConfig *types.AuthConfig, headers map[string][]string) (*registrytypes.SearchResults, error) { - searchFilters, err := filters.FromParam(filtersArgs) + searchFilters, err := filters.FromJSON(filtersArgs) if err != nil { return nil, err } @@ -33,21 +33,21 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s var isAutomated, isOfficial bool var hasStarFilter = 0 - if searchFilters.Include("is-automated") { + if searchFilters.Contains("is-automated") { if searchFilters.UniqueExactMatch("is-automated", "true") { isAutomated = true } else if !searchFilters.UniqueExactMatch("is-automated", "false") { return nil, invalidFilter{"is-automated", searchFilters.Get("is-automated")} } } - if searchFilters.Include("is-official") { + if searchFilters.Contains("is-official") { if searchFilters.UniqueExactMatch("is-official", "true") { isOfficial = true } else if !searchFilters.UniqueExactMatch("is-official", "false") { return nil, invalidFilter{"is-official", searchFilters.Get("is-official")} } } - if searchFilters.Include("stars") { + if searchFilters.Contains("stars") { hasStars := searchFilters.Get("stars") for _, hasStar := range hasStars { iHasStar, err := strconv.Atoi(hasStar) @@ -67,17 +67,17 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s filteredResults := []registrytypes.SearchResult{} for _, result := range unfilteredResult.Results { - if searchFilters.Include("is-automated") { + if searchFilters.Contains("is-automated") { if isAutomated != result.IsAutomated { continue } } - if searchFilters.Include("is-official") { + if searchFilters.Contains("is-official") { if isOfficial != result.IsOfficial { continue } } - if searchFilters.Include("stars") { + if searchFilters.Contains("stars") { if result.StarCount < hasStarFilter { continue } diff --git a/components/engine/docs/api/version-history.md b/components/engine/docs/api/version-history.md index 1144e95447..a8507ce465 100644 --- a/components/engine/docs/api/version-history.md +++ b/components/engine/docs/api/version-history.md @@ -18,7 +18,8 @@ keywords: "API, Docker, rcli, REST, documentation" [Docker Engine API v1.33](https://docs.docker.com/engine/api/v1.33/) documentation - +* `GET /events` now supports filtering 4 more kinds of events: `config`, `node`, +`secret` and `service`. ## v1.32 API changes diff --git a/components/engine/hack/ci/arm b/components/engine/hack/ci/arm new file mode 100755 index 0000000000..e60332a608 --- /dev/null +++ b/components/engine/hack/ci/arm @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Entrypoint for jenkins arm CI build +set -eu -o pipefail + +hack/test/unit + +hack/make.sh \ + binary-daemon \ + dynbinary \ + test-integration diff --git a/components/engine/hack/ci/experimental b/components/engine/hack/ci/experimental new file mode 100755 index 0000000000..9ccbc8425f --- /dev/null +++ b/components/engine/hack/ci/experimental @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Entrypoint for jenkins experimental CI +set -eu -o pipefail + +export DOCKER_EXPERIMENTAL=y + +hack/make.sh \ + binary-daemon \ + test-integration diff --git a/components/engine/hack/ci/janky b/components/engine/hack/ci/janky new file mode 100755 index 0000000000..fe04908cbc --- /dev/null +++ b/components/engine/hack/ci/janky @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Entrypoint for jenkins janky CI build +set -eu -o pipefail + +hack/validate/default +hack/test/unit + +hack/make.sh \ + binary-daemon \ + dynbinary \ + test-integration \ + test-docker-py \ + cross diff --git a/components/engine/hack/ci/powerpc b/components/engine/hack/ci/powerpc new file mode 100755 index 0000000000..c36cf37dbf --- /dev/null +++ b/components/engine/hack/ci/powerpc @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Entrypoint for jenkins powerpc CI build +set -eu -o pipefail + +hack/test/unit +hack/make.sh dynbinary test-integration diff --git a/components/engine/hack/ci/z b/components/engine/hack/ci/z new file mode 100755 index 0000000000..5ba868e816 --- /dev/null +++ b/components/engine/hack/ci/z @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Entrypoint for jenkins s390x (z) CI build +set -eu -o pipefail + +hack/test/unit +hack/make.sh dynbinary test-integration diff --git a/components/engine/hack/dockerfile/binaries-commits b/components/engine/hack/dockerfile/binaries-commits index 4338a3b48b..fde89d24e4 100644 --- a/components/engine/hack/dockerfile/binaries-commits +++ b/components/engine/hack/dockerfile/binaries-commits @@ -3,7 +3,7 @@ TOMLV_COMMIT=9baf8a8a9f2ed20a8e54160840c492f937eeaf9a # When updating RUNC_COMMIT, also update runc in vendor.conf accordingly -RUNC_COMMIT=1c81e2a794c6e26a4c650142ae8893c47f619764 +RUNC_COMMIT=0351df1c5a66838d0c392b4ac4cf9450de844e2d CONTAINERD_COMMIT=06b9cb35161009dcb7123345749fef02f7cea8e0 TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574 LIBNETWORK_COMMIT=7b2b1feb1de4817d522cc372af149ff48d25028e diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh index c52e634f97..bc18c066b6 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/hack/make.sh @@ -59,12 +59,10 @@ DEFAULT_BUNDLES=( binary-daemon dynbinary - test-unit test-integration test-docker-py cross - tgz ) VERSION=${VERSION:-$(< ./VERSION)} diff --git a/components/engine/hack/make/test-unit b/components/engine/hack/make/test-unit deleted file mode 100644 index d985a6ec2d..0000000000 --- a/components/engine/hack/make/test-unit +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "DEPRECATED: use hack/test/unit instead of hack/make.sh test-unit" >&2 - -$SCRIPTDIR/test/unit 2>&1 | tee -a "$DEST/test.log" diff --git a/components/engine/hack/make/tgz b/components/engine/hack/make/tgz deleted file mode 100644 index 1fd37b6b54..0000000000 --- a/components/engine/hack/make/tgz +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "tgz is deprecated" diff --git a/components/engine/integration-cli/docker_api_network_test.go b/components/engine/integration-cli/docker_api_network_test.go index a49fbae3d4..108dd4c6ac 100644 --- a/components/engine/integration-cli/docker_api_network_test.go +++ b/components/engine/integration-cli/docker_api_network_test.go @@ -282,7 +282,7 @@ func getNetworkIDByName(c *check.C, name string) string { filterArgs = filters.NewArgs() ) filterArgs.Add("name", name) - filterJSON, err := filters.ToParam(filterArgs) + filterJSON, err := filters.ToJSON(filterArgs) c.Assert(err, checker.IsNil) v.Set("filters", filterJSON) diff --git a/components/engine/integration-cli/requirements_test.go b/components/engine/integration-cli/requirements_test.go index 28c70c70ce..838977e70e 100644 --- a/components/engine/integration-cli/requirements_test.go +++ b/components/engine/integration-cli/requirements_test.go @@ -56,10 +56,6 @@ func ExperimentalDaemon() bool { return testEnv.DaemonInfo.ExperimentalBuild } -func NotExperimentalDaemon() bool { - return !testEnv.DaemonInfo.ExperimentalBuild -} - func IsAmd64() bool { return os.Getenv("DOCKER_ENGINE_GOARCH") == "amd64" } diff --git a/components/engine/plugin/backend_linux.go b/components/engine/plugin/backend_linux.go index 8a31e97e51..66de6cb993 100644 --- a/components/engine/plugin/backend_linux.go +++ b/components/engine/plugin/backend_linux.go @@ -365,7 +365,7 @@ func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) { enabledOnly := false disabledOnly := false - if pluginFilters.Include("enabled") { + if pluginFilters.Contains("enabled") { if pluginFilters.ExactMatch("enabled", "true") { enabledOnly = true } else if pluginFilters.ExactMatch("enabled", "false") { @@ -386,7 +386,7 @@ next: if disabledOnly && p.PluginObj.Enabled { continue } - if pluginFilters.Include("capability") { + if pluginFilters.Contains("capability") { for _, f := range p.GetTypes() { if !pluginFilters.Match("capability", f.Capability) { continue next diff --git a/components/engine/vendor.conf b/components/engine/vendor.conf index 6641dea1da..368e4f940d 100644 --- a/components/engine/vendor.conf +++ b/components/engine/vendor.conf @@ -66,7 +66,7 @@ github.com/pborman/uuid v1.0 google.golang.org/grpc v1.3.0 # When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly -github.com/opencontainers/runc 1c81e2a794c6e26a4c650142ae8893c47f619764 +github.com/opencontainers/runc 0351df1c5a66838d0c392b4ac4cf9450de844e2d github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13 github.com/opencontainers/runtime-spec v1.0.0 diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go index 269fffff35..3cae4fd8d9 100644 --- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go +++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go @@ -187,6 +187,10 @@ type Config struct { // Rootless specifies whether the container is a rootless container. Rootless bool `json:"rootless"` + + // IntelRdt specifies settings for Intel RDT/CAT group that the container is placed into + // to limit the resources (e.g., L3 cache) the container has available + IntelRdt *IntelRdt `json:"intel_rdt,omitempty"` } type Hooks struct { diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go new file mode 100644 index 0000000000..36bd5f96a1 --- /dev/null +++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go @@ -0,0 +1,7 @@ +package configs + +type IntelRdt struct { + // The schema for L3 cache id and capacity bitmask (CBM) + // Format: "L3:=;=;..." + L3CacheSchema string `json:"l3_cache_schema,omitempty"` +} diff --git a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c index 6814a5abbf..a6a107e6e6 100644 --- a/components/engine/vendor/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c +++ b/components/engine/vendor/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c @@ -1,3 +1,4 @@ + #define _GNU_SOURCE #include #include @@ -19,6 +20,8 @@ #include #include #include +#include + #include #include @@ -64,7 +67,13 @@ struct clone_t { struct nlconfig_t { char *data; + + /* Process settings. */ uint32_t cloneflags; + char *oom_score_adj; + size_t oom_score_adj_len; + + /* User namespace settings.*/ char *uidmap; size_t uidmap_len; char *gidmap; @@ -72,9 +81,13 @@ struct nlconfig_t { char *namespaces; size_t namespaces_len; uint8_t is_setgroup; + + /* Rootless container settings.*/ uint8_t is_rootless; - char *oom_score_adj; - size_t oom_score_adj_len; + char *uidmappath; + size_t uidmappath_len; + char *gidmappath; + size_t gidmappath_len; }; /* @@ -89,6 +102,8 @@ struct nlconfig_t { #define SETGROUP_ATTR 27285 #define OOM_SCORE_ADJ_ATTR 27286 #define ROOTLESS_ATTR 27287 +#define UIDMAPPATH_ATTR 27288 +#define GIDMAPPATH_ATTR 27289 /* * Use the raw syscall for versions of glibc which don't include a function for @@ -191,22 +206,96 @@ static void update_setgroups(int pid, enum policy_t setgroup) } } -static void update_uidmap(int pid, char *map, size_t map_len) +static int try_mapping_tool(const char *app, int pid, char *map, size_t map_len) { - if (map == NULL || map_len <= 0) - return; + int child; - if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) - bail("failed to update /proc/%d/uid_map", pid); + /* + * If @app is NULL, execve will segfault. Just check it here and bail (if + * we're in this path, the caller is already getting desparate and there + * isn't a backup to this failing). This usually would be a configuration + * or programming issue. + */ + if (!app) + bail("mapping tool not present"); + + child = fork(); + if (child < 0) + bail("failed to fork"); + + if (!child) { +#define MAX_ARGV 20 + char *argv[MAX_ARGV]; + char *envp[] = {NULL}; + char pid_fmt[16]; + int argc = 0; + char *next; + + snprintf(pid_fmt, 16, "%d", pid); + + argv[argc++] = (char *) app; + argv[argc++] = pid_fmt; + /* + * Convert the map string into a list of argument that + * newuidmap/newgidmap can understand. + */ + + while (argc < MAX_ARGV) { + if (*map == '\0') { + argv[argc++] = NULL; + break; + } + argv[argc++] = map; + next = strpbrk(map, "\n "); + if (next == NULL) + break; + *next++ = '\0'; + map = next + strspn(next, "\n "); + } + + execve(app, argv, envp); + bail("failed to execv"); + } else { + int status; + + while (true) { + if (waitpid(child, &status, 0) < 0) { + if (errno == EINTR) + continue; + bail("failed to waitpid"); + } + if (WIFEXITED(status) || WIFSIGNALED(status)) + return WEXITSTATUS(status); + } + } + + return -1; } -static void update_gidmap(int pid, char *map, size_t map_len) +static void update_uidmap(const char *path, int pid, char *map, size_t map_len) { if (map == NULL || map_len <= 0) return; - if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) - bail("failed to update /proc/%d/gid_map", pid); + if (write_file(map, map_len, "/proc/%d/uid_map", pid) < 0) { + if (errno != EPERM) + bail("failed to update /proc/%d/uid_map", pid); + if (try_mapping_tool(path, pid, map, map_len)) + bail("failed to use newuid map on %d", pid); + } +} + +static void update_gidmap(const char *path, int pid, char *map, size_t map_len) +{ + if (map == NULL || map_len <= 0) + return; + + if (write_file(map, map_len, "/proc/%d/gid_map", pid) < 0) { + if (errno != EPERM) + bail("failed to update /proc/%d/gid_map", pid); + if (try_mapping_tool(path, pid, map, map_len)) + bail("failed to use newgid map on %d", pid); + } } static void update_oom_score_adj(char *data, size_t len) @@ -350,6 +439,14 @@ static void nl_parse(int fd, struct nlconfig_t *config) config->gidmap = current; config->gidmap_len = payload_len; break; + case UIDMAPPATH_ATTR: + config->uidmappath = current; + config->uidmappath_len = payload_len; + break; + case GIDMAPPATH_ATTR: + config->gidmappath = current; + config->gidmappath_len = payload_len; + break; case SETGROUP_ATTR: config->is_setgroup = readint8(current); break; @@ -596,8 +693,8 @@ void nsexec(void) update_setgroups(child, SETGROUPS_DENY); /* Set up mappings. */ - update_uidmap(child, config.uidmap, config.uidmap_len); - update_gidmap(child, config.gidmap, config.gidmap_len); + update_uidmap(config.uidmappath, child, config.uidmap, config.uidmap_len); + update_gidmap(config.gidmappath, child, config.gidmap, config.gidmap_len); s = SYNC_USERMAP_ACK; if (write(syncfd, &s, sizeof(s)) != sizeof(s)) { diff --git a/components/engine/vendor/github.com/opencontainers/runc/vendor.conf b/components/engine/vendor/github.com/opencontainers/runc/vendor.conf index 9506b5c67c..1266ee485f 100644 --- a/components/engine/vendor/github.com/opencontainers/runc/vendor.conf +++ b/components/engine/vendor/github.com/opencontainers/runc/vendor.conf @@ -18,4 +18,8 @@ github.com/golang/protobuf 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 github.com/docker/docker 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d github.com/docker/go-units v0.2.0 github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e -golang.org/x/sys 0e0164865330d5cf1c00247be08330bf96e2f87c https://github.com/golang/sys +golang.org/x/sys 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce https://github.com/golang/sys + +# console dependencies +github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e +github.com/pkg/errors v0.8.0 diff --git a/components/engine/volume/volume.go b/components/engine/volume/volume.go index 4aa4de513d..b8ec1e5a00 100644 --- a/components/engine/volume/volume.go +++ b/components/engine/volume/volume.go @@ -3,6 +3,7 @@ package volume import ( "fmt" "os" + "path/filepath" "syscall" "time" @@ -155,13 +156,20 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun f return } - err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)) + var sourcePath string + sourcePath, err = filepath.EvalSymlinks(m.Source) + if err != nil { + path = "" + err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source) + return + } + err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode)) if err == syscall.ENOTSUP { err = nil } if err != nil { path = "" - err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source) + err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath) } }()