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:
parent
a08da82a28
commit
499a0dd43e
10 changed files with 70 additions and 4 deletions
|
@ -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"
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue