diff --git a/.gitignore b/.gitignore index 11231c2a99e..4782fce99f2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ _bin/ *.swp *.orig /agent/version/_version.go +/ecs-agent/daemonimages/csidriver/tarfiles /ecs-init/version/version.go .agignore *.sublime-* diff --git a/agent/engine/daemonmanager/daemon_manager_linux.go b/agent/engine/daemonmanager/daemon_manager_linux.go index 1b75f94cd01..925c195f4d5 100644 --- a/agent/engine/daemonmanager/daemon_manager_linux.go +++ b/agent/engine/daemonmanager/daemon_manager_linux.go @@ -37,6 +37,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/docker/docker/api/types" dockercontainer "github.com/docker/docker/api/types/container" + dockermount "github.com/docker/docker/api/types/mount" "github.com/pborman/uuid" ) @@ -55,24 +56,46 @@ func (dm *daemonManager) CreateDaemonTask() (*apitask.Task, error) { imageName := dm.managedDaemon.GetImageName() loadedImageRef := dm.managedDaemon.GetLoadedDaemonImageRef() containerRunning := apicontainerstatus.ContainerRunning + stringCaps := []string{} + if dm.managedDaemon.GetLinuxParameters() != nil { + caps := dm.managedDaemon.GetLinuxParameters().Capabilities.Add + for _, cap := range caps { + stringCaps = append(stringCaps, *cap) + } + } dockerHostConfig := dockercontainer.HostConfig{ + Mounts: []dockermount.Mount{}, NetworkMode: apitask.HostNetworkMode, // the default value of 0 for MaximumRetryCount means retry indefinitely RestartPolicy: dockercontainer.RestartPolicy{ Name: "on-failure", MaximumRetryCount: 0, }, + Privileged: dm.managedDaemon.GetPrivileged(), + CapAdd: stringCaps, } if !dm.managedDaemon.IsValidManagedDaemon() { return nil, fmt.Errorf("%s is an invalid managed daemon", imageName) } - for _, mount := range dm.managedDaemon.GetMountPoints() { - err := mkdirAllAndChown(mount.SourceVolumeHostPath, daemonMountPermission, daemonUID, os.Getegid()) + + for _, mp := range dm.managedDaemon.GetMountPoints() { + err := mkdirAllAndChown(mp.SourceVolumeHostPath, daemonMountPermission, daemonUID, os.Getegid()) if err != nil { return nil, err } - dockerHostConfig.Binds = append(dockerHostConfig.Binds, - fmt.Sprintf("%s:%s", mount.SourceVolumeHostPath, mount.ContainerPath)) + var bindOptions = dockermount.BindOptions{} + + if mp.PropagationShared { + // https://github.com/moby/moby/blob/master/api/types/mount/mount.go#L52 + bindOptions.Propagation = dockermount.PropagationShared + } + mountPoint := dockermount.Mount{ + Type: dockermount.TypeBind, + Source: mp.SourceVolumeHostPath, + Target: mp.ContainerPath, + BindOptions: &bindOptions, + } + dockerHostConfig.Mounts = append(dockerHostConfig.Mounts, mountPoint) } rawHostConfig, err := json.Marshal(&dockerHostConfig) if err != nil { diff --git a/agent/engine/daemonmanager/daemon_manager_linux_test.go b/agent/engine/daemonmanager/daemon_manager_linux_test.go index cbf508832d4..460db6b2e4e 100644 --- a/agent/engine/daemonmanager/daemon_manager_linux_test.go +++ b/agent/engine/daemonmanager/daemon_manager_linux_test.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io/fs" + "strings" "testing" "time" @@ -54,28 +55,28 @@ func TestCreateDaemonTask(t *testing.T) { testName: "Basic Daemon", testDaemonName: TestDaemonName, testImageRef: TestImageRef, - testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/"}, + testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/", PropagationShared: false}, testHealthCheck: []string{TestHealthString}, }, { testName: "Daemon Updated Daemon Name", testDaemonName: "TestDeemen", testImageRef: TestImageRef, - testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/"}, + testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/", PropagationShared: false}, testHealthCheck: []string{TestHealthString}, }, { testName: "Daemon Updated Image ref", testDaemonName: TestDaemonName, testImageRef: "TestOtherImage", - testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/"}, + testOtherMount: &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/", PropagationShared: false}, testHealthCheck: []string{TestHealthString}, }, { testName: "Daemon With Updated Mounts", testDaemonName: TestDaemonName, testImageRef: TestImageRef, - testOtherMount: &md.MountPoint{SourceVolumeID: "testUpdatedMountVolume", ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/"}, + testOtherMount: &md.MountPoint{SourceVolumeID: "testUpdatedMountVolume", ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/", PropagationShared: false}, testHealthCheck: []string{TestHealthString}, }, { @@ -124,9 +125,12 @@ func TestCreateDaemonTask(t *testing.T) { var hostConfigMap map[string]interface{} json.Unmarshal([]byte(aws.StringValue(configRaw)), &configMap) json.Unmarshal([]byte(aws.StringValue(hostConfigRaw)), &hostConfigMap) - // validate mount points - containerBinds := hostConfigMap["Binds"].([]interface{}) - assert.Equal(t, true, containsString(containerBinds, "/var/ecs/other/:/container/other/"), "Container Missing Optional Container Bind") + // validate mount point count + if containerMounts, ok := hostConfigMap["Mounts"].([]interface{}); ok { + assert.Equal(t, len(containerMounts), 3, "Task should have Required container binds (2) + 1 other bind") + } else { + t.Errorf("Unable to find 'Mounts' in container definition map") + } // validate healthcheck containerHealthCheck := configMap["Healthcheck"].(map[string]interface{}) containerHealthCheckTest := containerHealthCheck["Test"].([]interface{}) @@ -186,10 +190,80 @@ func TestFailCreateDaemonTask_MissingMount(t *testing.T) { var hostConfigMap map[string]interface{} json.Unmarshal([]byte(aws.StringValue(configRaw)), &configMap) json.Unmarshal([]byte(aws.StringValue(hostConfigRaw)), &hostConfigMap) - // validate mount point count - containerBinds := hostConfigMap["Binds"].([]interface{}) - assert.Equal(t, len(containerBinds), 3, "Task should have Required container binds (2) + 1 other bind") + if containerMounts, ok := hostConfigMap["Mounts"].([]interface{}); ok { + assert.Equal(t, len(containerMounts), 3, "Task should have Required container binds (2) + 1 other bind") + } else { + t.Errorf("Unable to find 'Mounts' in container definition map") + } + // validate healthcheck + containerHealthCheck := configMap["Healthcheck"].(map[string]interface{}) + containerHealthCheckTest := containerHealthCheck["Test"].([]interface{}) + assert.Equal(t, testHealthCheck[0], containerHealthCheckTest[0].(string), "Container health check has changed") +} + +func TestCreateDaemonTask_PrivilegeAndMountPropagation(t *testing.T) { + // mock mkdirAllAndChown + origMkdir := mkdirAllAndChown + defer func() { mkdirAllAndChown = origMkdir }() + mkdirAllAndChown = func(path string, perm fs.FileMode, uid, gid int) error { + return nil + } + // set up test managed daemon + tmd := md.NewManagedDaemon(TestDaemonName, TestImageTag) + tmd.SetLoadedDaemonImageRef(TestImageRef) + tmd.SetPrivileged(true) + // test failure with a missing applicationLogMount + testAgentCommunicationMount := &md.MountPoint{SourceVolumeID: "agentCommunicationMount", ContainerPath: "/container/run/"} + testOtherMount := &md.MountPoint{SourceVolumeID: TestOtherVolumeID, ContainerPath: "/container/other/", SourceVolumeHostPath: "/var/ecs/other/", PropagationShared: true} + testMountPoints := []*md.MountPoint{} + testMountPoints = append(testMountPoints, testAgentCommunicationMount, testOtherMount) + tmd.SetMountPoints(testMountPoints) + testDaemonManager := NewDaemonManager(tmd) + _, err := testDaemonManager.CreateDaemonTask() + assert.EqualError(t, err, fmt.Sprintf("%s is an invalid managed daemon", TestDaemonName)) + + // add required log mount but no healthcheck + testApplicationLogMount := &md.MountPoint{SourceVolumeID: "applicationLogMount", ContainerPath: "/container/log/"} + testMountPoints = append(testMountPoints, testApplicationLogMount) + tmd.SetMountPoints(testMountPoints) + testDaemonManager = NewDaemonManager(tmd) + _, err = testDaemonManager.CreateDaemonTask() + assert.Nil(t, err) + + // add required healthcheck + testHealthCheck := []string{"test"} + tmd.SetHealthCheck(testHealthCheck, 2*time.Minute, 2*time.Minute, 1) + testDaemonManager = NewDaemonManager(tmd) + resultDaemonTask, err := testDaemonManager.CreateDaemonTask() + + // validate daemon task configs + assert.Equal(t, fmt.Sprintf("arn:::::/%s-", TestDaemonName), resultDaemonTask.Arn[:20], "Task Arn prefix should match Image Name ") + assert.Equal(t, apitaskstatus.TaskRunning, resultDaemonTask.DesiredStatusUnsafe, "Task DesiredStatus should be running") + assert.Equal(t, apitask.HostNetworkMode, resultDaemonTask.NetworkMode, "Task NetworkMode should be Host") + assert.Equal(t, "EC2", resultDaemonTask.LaunchType, "Task LaunchType should be EC2") + assert.Equal(t, true, resultDaemonTask.IsInternal, "Task IsInteral should be true") + + // validate task container + assert.Equal(t, TestImageRef, resultDaemonTask.Containers[0].Image, "Task Container Image Name should match image ref") + + // validate daemon container configs + configRaw := resultDaemonTask.Containers[0].DockerConfig.Config + hostConfigRaw := resultDaemonTask.Containers[0].DockerConfig.HostConfig + var configMap map[string]interface{} + var hostConfigMap map[string]interface{} + json.Unmarshal([]byte(aws.StringValue(configRaw)), &configMap) + json.Unmarshal([]byte(aws.StringValue(hostConfigRaw)), &hostConfigMap) + // validate mount point has One mount with Shared Propagation + if containerMounts, ok := hostConfigMap["Mounts"].([]interface{}); ok { + res := strings.Count(fmt.Sprintf("%v", containerMounts), "Propagation:shared") + assert.Equal(t, res, 1, "Task should have only one mount with shared Propagation") + } else { + t.Errorf("missing 'Mounts' in hostConfigMap") + } + // validate privileged + containerPrivileged := hostConfigMap["Privileged"].(bool) + assert.True(t, containerPrivileged, "Daemon Container should be privileged") // validate healthcheck containerHealthCheck := configMap["Healthcheck"].(map[string]interface{}) containerHealthCheckTest := containerHealthCheck["Test"].([]interface{}) diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs/api.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs/api.go index 71309884c43..e49d9aab525 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs/api.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs/api.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/private/protocol" ) @@ -509,6 +510,8 @@ type Container struct { Links []*string `locationName:"links" type:"list"` + LinuxParameters *LinuxParameters `locationName:"linuxParameters" type:"structure"` + LogsAuthStrategy *string `locationName:"logsAuthStrategy" type:"string" enum:"AuthStrategy"` ManagedAgents []*ManagedAgent `locationName:"managedAgents" type:"list"` @@ -525,6 +528,8 @@ type Container struct { PortMappings []*PortMapping `locationName:"portMappings" type:"list"` + Privileged *bool `locationName:"privileged" type:"boolean"` + RegistryAuthentication *RegistryAuthenticationData `locationName:"registryAuthentication" type:"structure"` RestartMaxAttempts *int64 `locationName:"restartMaxAttempts" type:"integer"` @@ -550,6 +555,21 @@ func (s Container) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *Container) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Container"} + if s.LinuxParameters != nil { + if err := s.LinuxParameters.Validate(); err != nil { + invalidParams.AddNested("LinuxParameters", err.(request.ErrInvalidParams)) + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type ContainerDependency struct { _ struct{} `type:"structure"` @@ -568,6 +588,40 @@ func (s ContainerDependency) GoString() string { return s.String() } +type Device struct { + _ struct{} `type:"structure"` + + ContainerPath *string `locationName:"containerPath" type:"string"` + + // HostPath is a required field + HostPath *string `locationName:"hostPath" type:"string" required:"true"` + + Permissions []*string `locationName:"permissions" type:"list"` +} + +// String returns the string representation +func (s Device) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Device) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *Device) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Device"} + if s.HostPath == nil { + invalidParams.Add(request.NewErrParamRequired("HostPath")) + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type DockerConfig struct { _ struct{} `type:"structure"` @@ -1233,6 +1287,66 @@ func (s *InvalidInstanceException) RequestID() string { return s.RespMetadata.RequestID } +type KernelCapabilities struct { + _ struct{} `type:"structure"` + + Add []*string `locationName:"add" type:"list"` + + Drop []*string `locationName:"drop" type:"list"` +} + +// String returns the string representation +func (s KernelCapabilities) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s KernelCapabilities) GoString() string { + return s.String() +} + +type LinuxParameters struct { + _ struct{} `type:"structure"` + + Capabilities *KernelCapabilities `locationName:"capabilities" type:"structure"` + + Devices []*Device `locationName:"devices" type:"list"` + + InitProcessEnabled *bool `locationName:"initProcessEnabled" type:"boolean"` + + SharedMemorySize *int64 `locationName:"sharedMemorySize" type:"integer"` +} + +// String returns the string representation +func (s LinuxParameters) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s LinuxParameters) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *LinuxParameters) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "LinuxParameters"} + if s.Devices != nil { + for i, v := range s.Devices { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Devices", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type ManagedAgent struct { _ struct{} `type:"structure"` @@ -1373,6 +1487,26 @@ func (s PayloadInput) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *PayloadInput) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "PayloadInput"} + if s.Tasks != nil { + for i, v := range s.Tasks { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Tasks", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type PayloadMessage struct { _ struct{} `type:"structure"` @@ -1872,6 +2006,26 @@ func (s Task) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *Task) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Task"} + if s.Containers != nil { + for i, v := range s.Containers { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Containers", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type TaskIdentifier struct { _ struct{} `type:"structure"` diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/managed_daemon.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/managed_daemon.go index ee2bc61aa62..ccd1180e1cd 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/managed_daemon.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/managed_daemon.go @@ -17,6 +17,8 @@ import ( "fmt" "time" + "github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs" + dockercontainer "github.com/docker/docker/api/types/container" ) @@ -55,6 +57,9 @@ type ManagedDaemon struct { loadedDaemonImageRef string command []string + + linuxParameters *ecsacs.LinuxParameters + privileged bool } // A valid managed daemon will require @@ -82,16 +87,12 @@ func ImportAll() ([]*ManagedDaemon, error) { return []*ManagedDaemon{}, nil } -func (md *ManagedDaemon) SetHealthCheck( - healthCheckTest []string, - healthCheckInterval time.Duration, - healthCheckTimeout time.Duration, - healthCheckRetries int) { - md.healthCheckInterval = healthCheckInterval - md.healthCheckTimeout = healthCheckTimeout - md.healthCheckRetries = healthCheckRetries - md.healthCheckTest = make([]string, len(healthCheckTest)) - copy(md.healthCheckTest, healthCheckTest) +func (md *ManagedDaemon) GetLinuxParameters() *ecsacs.LinuxParameters { + return md.linuxParameters +} + +func (md *ManagedDaemon) GetPrivileged() bool { + return md.privileged } func (md *ManagedDaemon) GetImageName() string { @@ -149,6 +150,18 @@ func (md *ManagedDaemon) GetLoadedDaemonImageRef() string { return md.loadedDaemonImageRef } +func (md *ManagedDaemon) SetHealthCheck( + healthCheckTest []string, + healthCheckInterval time.Duration, + healthCheckTimeout time.Duration, + healthCheckRetries int) { + md.healthCheckInterval = healthCheckInterval + md.healthCheckTimeout = healthCheckTimeout + md.healthCheckRetries = healthCheckRetries + md.healthCheckTest = make([]string, len(healthCheckTest)) + copy(md.healthCheckTest, healthCheckTest) +} + // filter mount points for agentCommunicationMount // set required mounts // and override host paths in favor of agent defaults @@ -207,6 +220,10 @@ func (md *ManagedDaemon) SetLoadedDaemonImageRef(loadedImageRef string) { md.loadedDaemonImageRef = loadedImageRef } +func (md *ManagedDaemon) SetPrivileged(isPrivileged bool) { + md.privileged = isPrivileged +} + // AddMountPoint will add by MountPoint.SourceVolume // which is unique to the task and is a required field // and will throw an error if an existing diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/mountpoint.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/mountpoint.go index e3f48f1b5ad..e28262ea0e7 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/mountpoint.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon/mountpoint.go @@ -26,4 +26,7 @@ type MountPoint struct { ContainerPath string `json:"ContainerPath,omitempty"` ReadOnly bool `json:"ReadOnly,omitempty"` Internal bool `json:"Internal,omitempty"` + // BindOptions.Propagation comes from: + // https://github.com/moby/moby/blob/master/api/types/mount/mount.go#L46-L56 + PropagationShared bool `json:"PropagationShared,omitempty"` } diff --git a/ecs-agent/acs/model/api/api-2.json b/ecs-agent/acs/model/api/api-2.json index a29bf536e42..2e1da4dae83 100644 --- a/ecs-agent/acs/model/api/api-2.json +++ b/ecs-agent/acs/model/api/api-2.json @@ -251,6 +251,14 @@ "exception":true }, "Boolean":{"type":"boolean"}, + "BoxedBoolean":{ + "type":"boolean", + "box":true + }, + "BoxedInteger":{ + "type":"integer", + "box":true + }, "CloseMessage":{ "type":"structure", "members":{ @@ -290,6 +298,8 @@ "portMappings":{"shape":"PortMappingList"}, "managedAgents":{"shape":"ManagedAgentList"}, "mountPoints":{"shape":"MountPointList"}, + "linuxParameters":{"shape":"LinuxParameters"}, + "privileged":{"shape":"BoxedBoolean"}, "networkInterfaceNames":{"shape":"StringList"}, "volumesFrom":{"shape":"VolumeFromList"}, "dockerConfig":{"shape":"DockerConfig"}, @@ -618,6 +628,48 @@ "type":"list", "member":{"shape":"MountPoint"} }, + "KernelCapabilities":{ + "type":"structure", + "members":{ + "add":{"shape":"StringList"}, + "drop":{"shape":"StringList"} + } + }, + "Device":{ + "type":"structure", + "required":["hostPath"], + "members":{ + "hostPath":{"shape":"String"}, + "containerPath":{"shape":"String"}, + "permissions":{"shape":"DeviceCgroupPermissions"} + } + }, + "DeviceCgroupPermission":{ + "type":"string", + "enum":[ + "read", + "write", + "mknod" + ] + }, + "DeviceCgroupPermissions":{ + "type":"list", + "member":{"shape":"DeviceCgroupPermission"} + }, + "DevicesList":{ + "type":"list", + "member":{"shape":"Device"} + }, + "LinuxParameters":{ + "type":"structure", + "members":{ + "capabilities":{"shape":"KernelCapabilities"}, + "devices":{"shape":"DevicesList"}, + "initProcessEnabled":{"shape":"BoxedBoolean"}, + "sharedMemorySize":{"shape":"BoxedInteger"} + } + }, + "NackRequest":{ "type":"structure", "members":{ @@ -951,4 +1003,4 @@ ] } } -} \ No newline at end of file +} diff --git a/ecs-agent/acs/model/ecsacs/api.go b/ecs-agent/acs/model/ecsacs/api.go index 71309884c43..e49d9aab525 100644 --- a/ecs-agent/acs/model/ecsacs/api.go +++ b/ecs-agent/acs/model/ecsacs/api.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/private/protocol" ) @@ -509,6 +510,8 @@ type Container struct { Links []*string `locationName:"links" type:"list"` + LinuxParameters *LinuxParameters `locationName:"linuxParameters" type:"structure"` + LogsAuthStrategy *string `locationName:"logsAuthStrategy" type:"string" enum:"AuthStrategy"` ManagedAgents []*ManagedAgent `locationName:"managedAgents" type:"list"` @@ -525,6 +528,8 @@ type Container struct { PortMappings []*PortMapping `locationName:"portMappings" type:"list"` + Privileged *bool `locationName:"privileged" type:"boolean"` + RegistryAuthentication *RegistryAuthenticationData `locationName:"registryAuthentication" type:"structure"` RestartMaxAttempts *int64 `locationName:"restartMaxAttempts" type:"integer"` @@ -550,6 +555,21 @@ func (s Container) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *Container) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Container"} + if s.LinuxParameters != nil { + if err := s.LinuxParameters.Validate(); err != nil { + invalidParams.AddNested("LinuxParameters", err.(request.ErrInvalidParams)) + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type ContainerDependency struct { _ struct{} `type:"structure"` @@ -568,6 +588,40 @@ func (s ContainerDependency) GoString() string { return s.String() } +type Device struct { + _ struct{} `type:"structure"` + + ContainerPath *string `locationName:"containerPath" type:"string"` + + // HostPath is a required field + HostPath *string `locationName:"hostPath" type:"string" required:"true"` + + Permissions []*string `locationName:"permissions" type:"list"` +} + +// String returns the string representation +func (s Device) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Device) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *Device) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Device"} + if s.HostPath == nil { + invalidParams.Add(request.NewErrParamRequired("HostPath")) + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type DockerConfig struct { _ struct{} `type:"structure"` @@ -1233,6 +1287,66 @@ func (s *InvalidInstanceException) RequestID() string { return s.RespMetadata.RequestID } +type KernelCapabilities struct { + _ struct{} `type:"structure"` + + Add []*string `locationName:"add" type:"list"` + + Drop []*string `locationName:"drop" type:"list"` +} + +// String returns the string representation +func (s KernelCapabilities) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s KernelCapabilities) GoString() string { + return s.String() +} + +type LinuxParameters struct { + _ struct{} `type:"structure"` + + Capabilities *KernelCapabilities `locationName:"capabilities" type:"structure"` + + Devices []*Device `locationName:"devices" type:"list"` + + InitProcessEnabled *bool `locationName:"initProcessEnabled" type:"boolean"` + + SharedMemorySize *int64 `locationName:"sharedMemorySize" type:"integer"` +} + +// String returns the string representation +func (s LinuxParameters) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s LinuxParameters) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *LinuxParameters) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "LinuxParameters"} + if s.Devices != nil { + for i, v := range s.Devices { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Devices", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type ManagedAgent struct { _ struct{} `type:"structure"` @@ -1373,6 +1487,26 @@ func (s PayloadInput) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *PayloadInput) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "PayloadInput"} + if s.Tasks != nil { + for i, v := range s.Tasks { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Tasks", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type PayloadMessage struct { _ struct{} `type:"structure"` @@ -1872,6 +2006,26 @@ func (s Task) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *Task) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Task"} + if s.Containers != nil { + for i, v := range s.Containers { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Containers", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + type TaskIdentifier struct { _ struct{} `type:"structure"` diff --git a/ecs-agent/manageddaemon/managed_daemon.go b/ecs-agent/manageddaemon/managed_daemon.go index ee2bc61aa62..ccd1180e1cd 100644 --- a/ecs-agent/manageddaemon/managed_daemon.go +++ b/ecs-agent/manageddaemon/managed_daemon.go @@ -17,6 +17,8 @@ import ( "fmt" "time" + "github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs" + dockercontainer "github.com/docker/docker/api/types/container" ) @@ -55,6 +57,9 @@ type ManagedDaemon struct { loadedDaemonImageRef string command []string + + linuxParameters *ecsacs.LinuxParameters + privileged bool } // A valid managed daemon will require @@ -82,16 +87,12 @@ func ImportAll() ([]*ManagedDaemon, error) { return []*ManagedDaemon{}, nil } -func (md *ManagedDaemon) SetHealthCheck( - healthCheckTest []string, - healthCheckInterval time.Duration, - healthCheckTimeout time.Duration, - healthCheckRetries int) { - md.healthCheckInterval = healthCheckInterval - md.healthCheckTimeout = healthCheckTimeout - md.healthCheckRetries = healthCheckRetries - md.healthCheckTest = make([]string, len(healthCheckTest)) - copy(md.healthCheckTest, healthCheckTest) +func (md *ManagedDaemon) GetLinuxParameters() *ecsacs.LinuxParameters { + return md.linuxParameters +} + +func (md *ManagedDaemon) GetPrivileged() bool { + return md.privileged } func (md *ManagedDaemon) GetImageName() string { @@ -149,6 +150,18 @@ func (md *ManagedDaemon) GetLoadedDaemonImageRef() string { return md.loadedDaemonImageRef } +func (md *ManagedDaemon) SetHealthCheck( + healthCheckTest []string, + healthCheckInterval time.Duration, + healthCheckTimeout time.Duration, + healthCheckRetries int) { + md.healthCheckInterval = healthCheckInterval + md.healthCheckTimeout = healthCheckTimeout + md.healthCheckRetries = healthCheckRetries + md.healthCheckTest = make([]string, len(healthCheckTest)) + copy(md.healthCheckTest, healthCheckTest) +} + // filter mount points for agentCommunicationMount // set required mounts // and override host paths in favor of agent defaults @@ -207,6 +220,10 @@ func (md *ManagedDaemon) SetLoadedDaemonImageRef(loadedImageRef string) { md.loadedDaemonImageRef = loadedImageRef } +func (md *ManagedDaemon) SetPrivileged(isPrivileged bool) { + md.privileged = isPrivileged +} + // AddMountPoint will add by MountPoint.SourceVolume // which is unique to the task and is a required field // and will throw an error if an existing diff --git a/ecs-agent/manageddaemon/mountpoint.go b/ecs-agent/manageddaemon/mountpoint.go index e3f48f1b5ad..e28262ea0e7 100644 --- a/ecs-agent/manageddaemon/mountpoint.go +++ b/ecs-agent/manageddaemon/mountpoint.go @@ -26,4 +26,7 @@ type MountPoint struct { ContainerPath string `json:"ContainerPath,omitempty"` ReadOnly bool `json:"ReadOnly,omitempty"` Internal bool `json:"Internal,omitempty"` + // BindOptions.Propagation comes from: + // https://github.com/moby/moby/blob/master/api/types/mount/mount.go#L46-L56 + PropagationShared bool `json:"PropagationShared,omitempty"` }