From 9e9fc7b57c1764c008e568ed52bcd1aade7eb40c Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 16 Mar 2017 14:23:33 -0700 Subject: [PATCH] Add config support to executor backend Signed-off-by: Aaron Lehmann --- container/container.go | 14 ++- container/container_unix.go | 17 ++++ daemon/cluster/executor/backend.go | 3 +- daemon/cluster/executor/container/adapter.go | 27 +++--- .../cluster/executor/container/attachment.go | 4 +- .../cluster/executor/container/controller.go | 4 +- daemon/cluster/executor/container/executor.go | 22 +++-- daemon/configs.go | 23 +++++ daemon/configs_linux.go | 7 ++ daemon/configs_unsupported.go | 7 ++ daemon/container_operations_unix.go | 94 ++++++++++++++++--- daemon/dependency.go | 17 ++++ daemon/oci_linux.go | 6 ++ daemon/secrets.go | 13 --- 14 files changed, 208 insertions(+), 50 deletions(-) create mode 100644 daemon/configs.go create mode 100644 daemon/configs_linux.go create mode 100644 daemon/configs_unsupported.go create mode 100644 daemon/dependency.go diff --git a/container/container.go b/container/container.go index b86bff6ec6..23573d659f 100644 --- a/container/container.go +++ b/container/container.go @@ -87,8 +87,9 @@ type CommonContainer struct { MountPoints map[string]*volume.MountPoint HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable ExecCommands *exec.Store `json:"-"` - SecretStore agentexec.SecretGetter `json:"-"` + DependencyStore agentexec.DependencyGetter `json:"-"` SecretReferences []*swarmtypes.SecretReference + ConfigReferences []*swarmtypes.ConfigReference // logDriver for closing LogDriver logger.Logger `json:"-"` LogCopier *logger.Copier `json:"-"` @@ -966,3 +967,14 @@ func getSecretTargetPath(r *swarmtypes.SecretReference) string { return filepath.Join(containerSecretMountPath, r.File.Name) } + +// ConfigsDirPath returns the path to the directory where configs are stored on +// disk. +func (container *Container) ConfigsDirPath() string { + return filepath.Join(container.Root, "configs") +} + +// ConfigFilePath returns the path to the on-disk location of a config. +func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string { + return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID) +} diff --git a/container/container_unix.go b/container/container_unix.go index 265bea84d2..b0a32e6d5b 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -277,6 +277,23 @@ func (container *Container) UnmountSecrets() error { return detachMounted(container.SecretMountPath()) } +// ConfigMounts returns the mounts for configs. +func (container *Container) ConfigMounts() []Mount { + var mounts []Mount + for _, configRef := range container.ConfigReferences { + if configRef.File == nil { + continue + } + mounts = append(mounts, Mount{ + Source: container.ConfigFilePath(*configRef), + Destination: configRef.File.Name, + Writable: false, + }) + } + + return mounts +} + // UpdateContainer updates configuration of a container. func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error { container.Lock() diff --git a/daemon/cluster/executor/backend.go b/daemon/cluster/executor/backend.go index 1e1d83655d..65e257748e 100644 --- a/daemon/cluster/executor/backend.go +++ b/daemon/cluster/executor/backend.go @@ -42,8 +42,9 @@ type Backend interface { ContainerWaitWithContext(ctx context.Context, name string) error ContainerRm(name string, config *types.ContainerRmConfig) error ContainerKill(name string, sig uint64) error - SetContainerSecretStore(name string, store exec.SecretGetter) error + SetContainerDependencyStore(name string, store exec.DependencyGetter) error SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error + SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error SystemInfo() (*types.Info, error) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) Containers(config *types.ContainerListOptions) ([]*types.Container, error) diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index 1c669e68e2..ae27194e21 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -33,21 +33,21 @@ import ( // are mostly naked calls to the client API, seeded with information from // containerConfig. type containerAdapter struct { - backend executorpkg.Backend - container *containerConfig - secrets exec.SecretGetter + backend executorpkg.Backend + container *containerConfig + dependencies exec.DependencyGetter } -func newContainerAdapter(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*containerAdapter, error) { +func newContainerAdapter(b executorpkg.Backend, task *api.Task, dependencies exec.DependencyGetter) (*containerAdapter, error) { ctnr, err := newContainerConfig(task) if err != nil { return nil, err } return &containerAdapter{ - container: ctnr, - backend: b, - secrets: secrets, + container: ctnr, + backend: b, + dependencies: dependencies, }, nil } @@ -243,13 +243,18 @@ func (c *containerAdapter) create(ctx context.Context) error { return errors.New("unable to get container from task spec") } - // configure secrets - if err := c.backend.SetContainerSecretStore(cr.ID, c.secrets); err != nil { + if err := c.backend.SetContainerDependencyStore(cr.ID, c.dependencies); err != nil { return err } - refs := convert.SecretReferencesFromGRPC(container.Secrets) - if err := c.backend.SetContainerSecretReferences(cr.ID, refs); err != nil { + // configure secrets + secretRefs := convert.SecretReferencesFromGRPC(container.Secrets) + if err := c.backend.SetContainerSecretReferences(cr.ID, secretRefs); err != nil { + return err + } + + configRefs := convert.ConfigReferencesFromGRPC(container.Configs) + if err := c.backend.SetContainerConfigReferences(cr.ID, configRefs); err != nil { return err } diff --git a/daemon/cluster/executor/container/attachment.go b/daemon/cluster/executor/container/attachment.go index e0ee81a8b9..54f95a1fbf 100644 --- a/daemon/cluster/executor/container/attachment.go +++ b/daemon/cluster/executor/container/attachment.go @@ -20,8 +20,8 @@ type networkAttacherController struct { closed chan struct{} } -func newNetworkAttacherController(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*networkAttacherController, error) { - adapter, err := newContainerAdapter(b, task, secrets) +func newNetworkAttacherController(b executorpkg.Backend, task *api.Task, dependencies exec.DependencyGetter) (*networkAttacherController, error) { + adapter, err := newContainerAdapter(b, task, dependencies) if err != nil { return nil, err } diff --git a/daemon/cluster/executor/container/controller.go b/daemon/cluster/executor/container/controller.go index d20040afbd..460fe97efe 100644 --- a/daemon/cluster/executor/container/controller.go +++ b/daemon/cluster/executor/container/controller.go @@ -39,8 +39,8 @@ type controller struct { var _ exec.Controller = &controller{} // NewController returns a docker exec runner for the provided task. -func newController(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*controller, error) { - adapter, err := newContainerAdapter(b, task, secrets) +func newController(b executorpkg.Backend, task *api.Task, dependencies exec.DependencyGetter) (*controller, error) { + adapter, err := newContainerAdapter(b, task, dependencies) if err != nil { return nil, err } diff --git a/daemon/cluster/executor/container/executor.go b/daemon/cluster/executor/container/executor.go index cb4ce9e825..03a00cc87b 100644 --- a/daemon/cluster/executor/container/executor.go +++ b/daemon/cluster/executor/container/executor.go @@ -14,23 +14,23 @@ import ( executorpkg "github.com/docker/docker/daemon/cluster/executor" clustertypes "github.com/docker/docker/daemon/cluster/provider" networktypes "github.com/docker/libnetwork/types" + "github.com/docker/swarmkit/agent" "github.com/docker/swarmkit/agent/exec" - "github.com/docker/swarmkit/agent/secrets" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api/naming" "golang.org/x/net/context" ) type executor struct { - backend executorpkg.Backend - secrets exec.SecretsManager + backend executorpkg.Backend + dependencies exec.DependencyManager } // NewExecutor returns an executor from the docker client. func NewExecutor(b executorpkg.Backend) exec.Executor { return &executor{ - backend: b, - secrets: secrets.NewManager(), + backend: b, + dependencies: agent.NewDependencyManager(), } } @@ -162,8 +162,10 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error { // Controller returns a docker container runner. func (e *executor) Controller(t *api.Task) (exec.Controller, error) { + dependencyGetter := agent.Restrict(e.dependencies, t) + if t.Spec.GetAttachment() != nil { - return newNetworkAttacherController(e.backend, t, e.secrets) + return newNetworkAttacherController(e.backend, t, dependencyGetter) } var ctlr exec.Controller @@ -188,7 +190,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) { return ctlr, fmt.Errorf("unsupported runtime type: %q", r.Generic.Kind) } case *api.TaskSpec_Container: - c, err := newController(e.backend, t, secrets.Restrict(e.secrets, t)) + c, err := newController(e.backend, t, dependencyGetter) if err != nil { return ctlr, err } @@ -218,7 +220,11 @@ func (e *executor) SetNetworkBootstrapKeys(keys []*api.EncryptionKey) error { } func (e *executor) Secrets() exec.SecretsManager { - return e.secrets + return e.dependencies.Secrets() +} + +func (e *executor) Configs() exec.ConfigsManager { + return e.dependencies.Configs() } type sortedPlugins []api.PluginDescription diff --git a/daemon/configs.go b/daemon/configs.go new file mode 100644 index 0000000000..31da56b2d3 --- /dev/null +++ b/daemon/configs.go @@ -0,0 +1,23 @@ +package daemon + +import ( + "github.com/Sirupsen/logrus" + swarmtypes "github.com/docker/docker/api/types/swarm" +) + +// SetContainerConfigReferences sets the container config references needed +func (daemon *Daemon) SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error { + if !configsSupported() && len(refs) > 0 { + logrus.Warn("configs are not supported on this platform") + return nil + } + + c, err := daemon.GetContainer(name) + if err != nil { + return err + } + + c.ConfigReferences = refs + + return nil +} diff --git a/daemon/configs_linux.go b/daemon/configs_linux.go new file mode 100644 index 0000000000..af20ad78bd --- /dev/null +++ b/daemon/configs_linux.go @@ -0,0 +1,7 @@ +// +build linux + +package daemon + +func configsSupported() bool { + return true +} diff --git a/daemon/configs_unsupported.go b/daemon/configs_unsupported.go new file mode 100644 index 0000000000..93c56d3e60 --- /dev/null +++ b/daemon/configs_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package daemon + +func configsSupported() bool { + return false +} diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 21eebaf178..09733925c5 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -145,6 +145,13 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { localMountPath := c.SecretMountPath() logrus.Debugf("secrets: setting up secret dir: %s", localMountPath) + // retrieve possible remapped range start for root UID, GID + rootUID, rootGID := daemon.GetRemappedUIDGID() + // create tmpfs + if err := idtools.MkdirAllAs(localMountPath, 0700, rootUID, rootGID); err != nil { + return errors.Wrap(err, "error creating secret local mount path") + } + defer func() { if setupErr != nil { // cleanup @@ -156,25 +163,20 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { } }() - // retrieve possible remapped range start for root UID, GID - rootUID, rootGID := daemon.GetRemappedUIDGID() - // create tmpfs - if err := idtools.MkdirAllAs(localMountPath, 0700, rootUID, rootGID); err != nil { - return errors.Wrap(err, "error creating secret local mount path") - } tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootUID, rootGID) if err := mount.Mount("tmpfs", localMountPath, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil { return errors.Wrap(err, "unable to setup secret mount") } - for _, s := range c.SecretReferences { - if c.SecretStore == nil { - return fmt.Errorf("secret store is not initialized") - } + if c.DependencyStore == nil { + return fmt.Errorf("secret store is not initialized") + } + for _, s := range c.SecretReferences { // TODO (ehazlett): use type switch when more are supported if s.File == nil { - return fmt.Errorf("secret target type is not a file target") + logrus.Error("secret target type is not a file target") + continue } // secrets are created in the SecretMountPath on the host, at a @@ -188,7 +190,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { "name": s.File.Name, "path": fPath, }).Debug("injecting secret") - secret := c.SecretStore.Get(s.SecretID) + secret := c.DependencyStore.Secrets().Get(s.SecretID) if secret == nil { return fmt.Errorf("unable to get secret from secret store") } @@ -220,6 +222,74 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { return nil } +func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) { + if len(c.ConfigReferences) == 0 { + return nil + } + + localPath := c.ConfigsDirPath() + logrus.Debugf("configs: setting up config dir: %s", localPath) + + // retrieve possible remapped range start for root UID, GID + rootUID, rootGID := daemon.GetRemappedUIDGID() + // create tmpfs + if err := idtools.MkdirAllAs(localPath, 0700, rootUID, rootGID); err != nil { + return errors.Wrap(err, "error creating config dir") + } + + defer func() { + if setupErr != nil { + if err := os.RemoveAll(localPath); err != nil { + logrus.Errorf("error cleaning up config dir: %s", err) + } + } + }() + + if c.DependencyStore == nil { + return fmt.Errorf("config store is not initialized") + } + + for _, configRef := range c.ConfigReferences { + // TODO (ehazlett): use type switch when more are supported + if configRef.File == nil { + logrus.Error("config target type is not a file target") + continue + } + + fPath := c.ConfigFilePath(*configRef) + + log := logrus.WithFields(logrus.Fields{"name": configRef.File.Name, "path": fPath}) + + if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil { + return errors.Wrap(err, "error creating config path") + } + + log.Debug("injecting config") + config := c.DependencyStore.Configs().Get(configRef.ConfigID) + if config == nil { + return fmt.Errorf("unable to get config from config store") + } + if err := ioutil.WriteFile(fPath, config.Spec.Data, configRef.File.Mode); err != nil { + return errors.Wrap(err, "error injecting config") + } + + uid, err := strconv.Atoi(configRef.File.UID) + if err != nil { + return err + } + gid, err := strconv.Atoi(configRef.File.GID) + if err != nil { + return err + } + + if err := os.Chown(fPath, rootUID+uid, rootGID+gid); err != nil { + return errors.Wrap(err, "error setting ownership for config") + } + } + + return nil +} + func killProcessDirectly(container *container.Container) error { if _, err := container.WaitStop(10 * time.Second); err != nil { // Ensure that we don't kill ourselves diff --git a/daemon/dependency.go b/daemon/dependency.go new file mode 100644 index 0000000000..83144e6865 --- /dev/null +++ b/daemon/dependency.go @@ -0,0 +1,17 @@ +package daemon + +import ( + "github.com/docker/swarmkit/agent/exec" +) + +// SetContainerDependencyStore sets the dependency store backend for the container +func (daemon *Daemon) SetContainerDependencyStore(name string, store exec.DependencyGetter) error { + c, err := daemon.GetContainer(name) + if err != nil { + return err + } + + c.DependencyStore = store + + return nil +} diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index f04188b171..55a6cd8ae0 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -737,6 +737,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { return nil, err } + if err := daemon.setupConfigDir(c); err != nil { + return nil, err + } + ms, err := daemon.setupMounts(c) if err != nil { return nil, err @@ -754,6 +758,8 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { ms = append(ms, m...) } + ms = append(ms, c.ConfigMounts()...) + sort.Sort(mounts(ms)) if err := setMounts(daemon, &s, c, ms); err != nil { return nil, fmt.Errorf("linux mounts: %v", err) diff --git a/daemon/secrets.go b/daemon/secrets.go index 355cb1e139..90fa99e987 100644 --- a/daemon/secrets.go +++ b/daemon/secrets.go @@ -3,21 +3,8 @@ package daemon import ( "github.com/Sirupsen/logrus" swarmtypes "github.com/docker/docker/api/types/swarm" - "github.com/docker/swarmkit/agent/exec" ) -// SetContainerSecretStore sets the secret store backend for the container -func (daemon *Daemon) SetContainerSecretStore(name string, store exec.SecretGetter) error { - c, err := daemon.GetContainer(name) - if err != nil { - return err - } - - c.SecretStore = store - - return nil -} - // SetContainerSecretReferences sets the container secret references needed func (daemon *Daemon) SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error { if !secretsSupported() && len(refs) > 0 {