mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #30162 from yongtang/29972-service-read-only
Add `--read-only` for `service create` and `service update`
This commit is contained in:
commit
1d2f5de49a
12 changed files with 72 additions and 4 deletions
|
@ -1884,6 +1884,9 @@ definitions:
|
||||||
TTY:
|
TTY:
|
||||||
description: "Whether a pseudo-TTY should be allocated."
|
description: "Whether a pseudo-TTY should be allocated."
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
|
ReadOnly:
|
||||||
|
description: "Mount the container's root filesystem as read only."
|
||||||
|
type: "boolean"
|
||||||
Mounts:
|
Mounts:
|
||||||
description: "Specification for mounts to be added to containers created as part of the service."
|
description: "Specification for mounts to be added to containers created as part of the service."
|
||||||
type: "array"
|
type: "array"
|
||||||
|
|
|
@ -34,6 +34,7 @@ type ContainerSpec struct {
|
||||||
Groups []string `json:",omitempty"`
|
Groups []string `json:",omitempty"`
|
||||||
TTY bool `json:",omitempty"`
|
TTY bool `json:",omitempty"`
|
||||||
OpenStdin bool `json:",omitempty"`
|
OpenStdin bool `json:",omitempty"`
|
||||||
|
ReadOnly bool `json:",omitempty"`
|
||||||
Mounts []mount.Mount `json:",omitempty"`
|
Mounts []mount.Mount `json:",omitempty"`
|
||||||
StopGracePeriod *time.Duration `json:",omitempty"`
|
StopGracePeriod *time.Duration `json:",omitempty"`
|
||||||
Healthcheck *container.HealthConfig `json:",omitempty"`
|
Healthcheck *container.HealthConfig `json:",omitempty"`
|
||||||
|
|
|
@ -303,6 +303,7 @@ type serviceOptions struct {
|
||||||
user string
|
user string
|
||||||
groups opts.ListOpts
|
groups opts.ListOpts
|
||||||
tty bool
|
tty bool
|
||||||
|
readOnly bool
|
||||||
mounts opts.MountOpt
|
mounts opts.MountOpt
|
||||||
dns opts.ListOpts
|
dns opts.ListOpts
|
||||||
dnsSearch opts.ListOpts
|
dnsSearch opts.ListOpts
|
||||||
|
@ -384,6 +385,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
||||||
User: opts.user,
|
User: opts.user,
|
||||||
Groups: opts.groups.GetAll(),
|
Groups: opts.groups.GetAll(),
|
||||||
TTY: opts.tty,
|
TTY: opts.tty,
|
||||||
|
ReadOnly: opts.readOnly,
|
||||||
Mounts: opts.mounts.Value(),
|
Mounts: opts.mounts.Value(),
|
||||||
DNSConfig: &swarm.DNSConfig{
|
DNSConfig: &swarm.DNSConfig{
|
||||||
Nameservers: opts.dns.GetAll(),
|
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.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
|
||||||
flags.SetAnnotation(flagTTY, "version", []string{"1.25"})
|
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 (
|
const (
|
||||||
|
@ -532,6 +537,7 @@ const (
|
||||||
flagPublish = "publish"
|
flagPublish = "publish"
|
||||||
flagPublishRemove = "publish-rm"
|
flagPublishRemove = "publish-rm"
|
||||||
flagPublishAdd = "publish-add"
|
flagPublishAdd = "publish-add"
|
||||||
|
flagReadOnly = "read-only"
|
||||||
flagReplicas = "replicas"
|
flagReplicas = "replicas"
|
||||||
flagReserveCPU = "reserve-cpu"
|
flagReserveCPU = "reserve-cpu"
|
||||||
flagReserveMemory = "reserve-memory"
|
flagReserveMemory = "reserve-memory"
|
||||||
|
|
|
@ -341,6 +341,14 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
||||||
cspec.TTY = tty
|
cspec.TTY = tty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.Changed(flagReadOnly) {
|
||||||
|
readOnly, err := flags.GetBool(flagReadOnly)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cspec.ReadOnly = readOnly
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,3 +442,25 @@ func TestUpdateSecretUpdateInPlace(t *testing.T) {
|
||||||
assert.Equal(t, updatedSecrets[0].SecretName, "foo")
|
assert.Equal(t, updatedSecrets[0].SecretName, "foo")
|
||||||
assert.Equal(t, updatedSecrets[0].File.Name, "foo2")
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -2851,6 +2851,7 @@ _docker_service_update() {
|
||||||
|
|
||||||
local boolean_options="
|
local boolean_options="
|
||||||
--help
|
--help
|
||||||
|
--read-only
|
||||||
--tty -t
|
--tty -t
|
||||||
--with-registry-auth
|
--with-registry-auth
|
||||||
"
|
"
|
||||||
|
|
|
@ -1795,6 +1795,7 @@ __docker_service_subcommand() {
|
||||||
"($help)*--network=[Network attachments]:network: "
|
"($help)*--network=[Network attachments]:network: "
|
||||||
"($help)--no-healthcheck[Disable any container-specified HEALTHCHECK]"
|
"($help)--no-healthcheck[Disable any container-specified HEALTHCHECK]"
|
||||||
"($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
|
"($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
|
||||||
|
"($help)--read-only[Mount the container's root filesystem as read only]"
|
||||||
"($help)--replicas=[Number of tasks]:replicas: "
|
"($help)--replicas=[Number of tasks]:replicas: "
|
||||||
"($help)--reserve-cpu=[Reserve CPUs]:value: "
|
"($help)--reserve-cpu=[Reserve CPUs]:value: "
|
||||||
"($help)--reserve-memory=[Reserve Memory]:value: "
|
"($help)--reserve-memory=[Reserve Memory]:value: "
|
||||||
|
|
|
@ -25,6 +25,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
||||||
Groups: c.Groups,
|
Groups: c.Groups,
|
||||||
TTY: c.TTY,
|
TTY: c.TTY,
|
||||||
OpenStdin: c.OpenStdin,
|
OpenStdin: c.OpenStdin,
|
||||||
|
ReadOnly: c.ReadOnly,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
Secrets: secretReferencesFromGRPC(c.Secrets),
|
Secrets: secretReferencesFromGRPC(c.Secrets),
|
||||||
}
|
}
|
||||||
|
@ -146,6 +147,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
Groups: c.Groups,
|
Groups: c.Groups,
|
||||||
TTY: c.TTY,
|
TTY: c.TTY,
|
||||||
OpenStdin: c.OpenStdin,
|
OpenStdin: c.OpenStdin,
|
||||||
|
ReadOnly: c.ReadOnly,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
Secrets: secretReferencesToGRPC(c.Secrets),
|
Secrets: secretReferencesToGRPC(c.Secrets),
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,10 +335,11 @@ func (c *containerConfig) healthcheck() *enginecontainer.HealthConfig {
|
||||||
|
|
||||||
func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
||||||
hc := &enginecontainer.HostConfig{
|
hc := &enginecontainer.HostConfig{
|
||||||
Resources: c.resources(),
|
Resources: c.resources(),
|
||||||
GroupAdd: c.spec().Groups,
|
GroupAdd: c.spec().Groups,
|
||||||
PortBindings: c.portBindings(),
|
PortBindings: c.portBindings(),
|
||||||
Mounts: c.mounts(),
|
Mounts: c.mounts(),
|
||||||
|
ReadonlyRootfs: c.spec().ReadOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.spec().DNSConfig != nil {
|
if c.spec().DNSConfig != nil {
|
||||||
|
|
|
@ -48,6 +48,7 @@ Options:
|
||||||
--network list Network attachments (default [])
|
--network list Network attachments (default [])
|
||||||
--no-healthcheck Disable any container-specified HEALTHCHECK
|
--no-healthcheck Disable any container-specified HEALTHCHECK
|
||||||
-p, --publish port Publish a port as a node port
|
-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
|
--replicas uint Number of tasks
|
||||||
--reserve-cpu decimal Reserve CPUs (default 0.000)
|
--reserve-cpu decimal Reserve CPUs (default 0.000)
|
||||||
--reserve-memory bytes Reserve Memory (default 0 B)
|
--reserve-memory bytes Reserve Memory (default 0 B)
|
||||||
|
|
|
@ -58,6 +58,7 @@ Options:
|
||||||
--no-healthcheck Disable any container-specified HEALTHCHECK
|
--no-healthcheck Disable any container-specified HEALTHCHECK
|
||||||
--publish-add port Add or update a published port
|
--publish-add port Add or update a published port
|
||||||
--publish-rm port Remove a published port by its target 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
|
--replicas uint Number of tasks
|
||||||
--reserve-cpu decimal Reserve CPUs (default 0.000)
|
--reserve-cpu decimal Reserve CPUs (default 0.000)
|
||||||
--reserve-memory bytes Reserve Memory (default 0 B)
|
--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(err, checker.IsNil)
|
||||||
c.Assert(out, checker.Contains, "Drain")
|
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…
Add table
Reference in a new issue