Add --read-only for service create and service update

This fix tries to address the issue raised in 29972 where
it was not possible to specify `--read-only` for `docker service create`
and `docker service update`, in order to have the container's root file
system to be read only.

This fix adds `--read-only` and update the `ReadonlyRootfs` in `HostConfig`
through `service create` and `service update`.

Related docs has been updated.

Integration test has been added.

This fix fixes 29972.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2017-01-14 00:12:19 -08:00
parent a08da82a28
commit 499a0dd43e
10 changed files with 70 additions and 4 deletions

View file

@ -1884,6 +1884,9 @@ definitions:
TTY:
description: "Whether a pseudo-TTY should be allocated."
type: "boolean"
ReadOnly:
description: "Mount the container's root filesystem as read only."
type: "boolean"
Mounts:
description: "Specification for mounts to be added to containers created as part of the service."
type: "array"

View file

@ -34,6 +34,7 @@ type ContainerSpec struct {
Groups []string `json:",omitempty"`
TTY bool `json:",omitempty"`
OpenStdin bool `json:",omitempty"`
ReadOnly bool `json:",omitempty"`
Mounts []mount.Mount `json:",omitempty"`
StopGracePeriod *time.Duration `json:",omitempty"`
Healthcheck *container.HealthConfig `json:",omitempty"`

View file

@ -303,6 +303,7 @@ type serviceOptions struct {
user string
groups opts.ListOpts
tty bool
readOnly bool
mounts opts.MountOpt
dns opts.ListOpts
dnsSearch opts.ListOpts
@ -384,6 +385,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
User: opts.user,
Groups: opts.groups.GetAll(),
TTY: opts.tty,
ReadOnly: opts.readOnly,
Mounts: opts.mounts.Value(),
DNSConfig: &swarm.DNSConfig{
Nameservers: opts.dns.GetAll(),
@ -488,6 +490,9 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
flags.SetAnnotation(flagTTY, "version", []string{"1.25"})
flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only")
flags.SetAnnotation(flagReadOnly, "version", []string{"1.26"})
}
const (
@ -532,6 +537,7 @@ const (
flagPublish = "publish"
flagPublishRemove = "publish-rm"
flagPublishAdd = "publish-add"
flagReadOnly = "read-only"
flagReplicas = "replicas"
flagReserveCPU = "reserve-cpu"
flagReserveMemory = "reserve-memory"

View file

@ -341,6 +341,14 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
cspec.TTY = tty
}
if flags.Changed(flagReadOnly) {
readOnly, err := flags.GetBool(flagReadOnly)
if err != nil {
return err
}
cspec.ReadOnly = readOnly
}
return nil
}

View file

@ -442,3 +442,25 @@ func TestUpdateSecretUpdateInPlace(t *testing.T) {
assert.Equal(t, updatedSecrets[0].SecretName, "foo")
assert.Equal(t, updatedSecrets[0].File.Name, "foo2")
}
func TestUpdateReadOnly(t *testing.T) {
spec := &swarm.ServiceSpec{}
cspec := &spec.TaskTemplate.ContainerSpec
// Update with --read-only=true, changed to true
flags := newUpdateCommand(nil).Flags()
flags.Set("read-only", "true")
updateService(flags, spec)
assert.Equal(t, cspec.ReadOnly, true)
// Update without --read-only, no change
flags = newUpdateCommand(nil).Flags()
updateService(flags, spec)
assert.Equal(t, cspec.ReadOnly, true)
// Update with --read-only=false, changed to false
flags = newUpdateCommand(nil).Flags()
flags.Set("read-only", "false")
updateService(flags, spec)
assert.Equal(t, cspec.ReadOnly, false)
}

View file

@ -25,6 +25,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
Groups: c.Groups,
TTY: c.TTY,
OpenStdin: c.OpenStdin,
ReadOnly: c.ReadOnly,
Hosts: c.Hosts,
Secrets: secretReferencesFromGRPC(c.Secrets),
}
@ -146,6 +147,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
Groups: c.Groups,
TTY: c.TTY,
OpenStdin: c.OpenStdin,
ReadOnly: c.ReadOnly,
Hosts: c.Hosts,
Secrets: secretReferencesToGRPC(c.Secrets),
}

View file

@ -335,10 +335,11 @@ func (c *containerConfig) healthcheck() *enginecontainer.HealthConfig {
func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
hc := &enginecontainer.HostConfig{
Resources: c.resources(),
GroupAdd: c.spec().Groups,
PortBindings: c.portBindings(),
Mounts: c.mounts(),
Resources: c.resources(),
GroupAdd: c.spec().Groups,
PortBindings: c.portBindings(),
Mounts: c.mounts(),
ReadonlyRootfs: c.spec().ReadOnly,
}
if c.spec().DNSConfig != nil {

View file

@ -48,6 +48,7 @@ Options:
--network list Network attachments (default [])
--no-healthcheck Disable any container-specified HEALTHCHECK
-p, --publish port Publish a port as a node port
--read-only Mount the container's root filesystem as read only
--replicas uint Number of tasks
--reserve-cpu decimal Reserve CPUs (default 0.000)
--reserve-memory bytes Reserve Memory (default 0 B)

View file

@ -58,6 +58,7 @@ Options:
--no-healthcheck Disable any container-specified HEALTHCHECK
--publish-add port Add or update a published port
--publish-rm port Remove a published port by its target port
--read-only Mount the container's root filesystem as read only
--replicas uint Number of tasks
--reserve-cpu decimal Reserve CPUs (default 0.000)
--reserve-memory bytes Reserve Memory (default 0 B)

View file

@ -1644,3 +1644,24 @@ func (s *DockerSwarmSuite) TestSwarmInitWithDrain(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, "Drain")
}
func (s *DockerSwarmSuite) TestSwarmReadonlyRootfs(c *check.C) {
testRequires(c, DaemonIsLinux, UserNamespaceROMount)
d := s.AddDaemon(c, true, true)
out, err := d.Cmd("service", "create", "--name", "top", "--read-only", "busybox", "top")
c.Assert(err, checker.IsNil, check.Commentf(out))
// make sure task has been deployed.
waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.ReadOnly }}", "top")
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, "true")
containers := d.ActiveContainers()
out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.HostConfig.ReadonlyRootfs}}", containers[0])
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(strings.TrimSpace(out), checker.Equals, "true")
}