diff --git a/ecs-init/config/common.go b/ecs-init/config/common.go index 1d34ce30..8d08d476 100644 --- a/ecs-init/config/common.go +++ b/ecs-init/config/common.go @@ -14,11 +14,15 @@ package config import ( + "encoding/json" "fmt" + "io" "os" "runtime" "strings" + "github.com/cihub/seelog" + "github.com/aws/aws-sdk-go/aws/endpoints" godocker "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" @@ -67,6 +71,17 @@ const ( // be used to override the default value used of // dockerJSONLogMaxFiles for managed containers. dockerJSONLogMaxFilesEnvVar = "ECS_INIT_DOCKER_LOG_FILE_NUM" + + // agentLogDriverEnvVar is the environment variable that may be used + // to set a log driver for the agent container + agentLogDriverEnvVar = "ECS_LOG_DRIVER" + // agentLogOptionsEnvVar is the environment variable that may be used to specify options + // for the log driver set in agentLogDriverEnvVar + agentLogOptionsEnvVar = "ECS_LOG_OPTS" + // defaultLogDriver is the logging driver that will be used if one is not explicitly + // set in agentLogDriverEnvVar + defaultLogDriver = "json-file" + // GPUSupportEnvVar indicates that the AMI has support for GPU GPUSupportEnvVar = "ECS_ENABLE_GPU_SUPPORT" @@ -86,6 +101,19 @@ var partitionBucketRegion = map[string]string{ // formatting of configuration for supported architectures. var goarch string = runtime.GOARCH +// validDrivers is the set of all supported Docker logging drivers that +// can be used as the log driver for the Agent container +var validDrivers = map[string]struct{}{ + "awslogs": {}, + "fluentd": {}, + "gelf": {}, + "json-file": {}, + "journald": {}, + "logentries": {}, + "syslog": {}, + "splunk": {}, +} + // GetAgentPartitionBucketRegion returns the s3 bucket region where ECS Agent artifact is located func GetAgentPartitionBucketRegion(region string) (string, error) { regionPartition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region) @@ -202,23 +230,46 @@ func HostPKIDirPath() string { // AgentDockerLogDriverConfiguration returns a LogConfig object // suitable for used with the managed container. func AgentDockerLogDriverConfiguration() godocker.LogConfig { - maxSize := dockerJSONLogMaxSize - if fromEnv := os.Getenv(dockerJSONLogMaxSizeEnvVar); fromEnv != "" { - maxSize = fromEnv - } - - maxFiles := dockerJSONLogMaxFiles - if fromEnv := os.Getenv(dockerJSONLogMaxFilesEnvVar); fromEnv != "" { - maxFiles = fromEnv + driver := defaultLogDriver + options := parseLogOptions() + if envDriver := os.Getenv(agentLogDriverEnvVar); envDriver != "" { + if _, ok := validDrivers[envDriver]; ok { + driver = envDriver + } else { + seelog.Warnf("Input value for \"ECS_LOG_DRIVER\" is not a supported log driver, overriding to %s and using default log options", defaultLogDriver) + options = nil + } } - - return godocker.LogConfig{ - Type: "json-file", - Config: map[string]string{ + if driver == defaultLogDriver && options == nil { + maxSize := dockerJSONLogMaxSize + if fromEnv := os.Getenv(dockerJSONLogMaxSizeEnvVar); fromEnv != "" { + maxSize = fromEnv + } + maxFiles := dockerJSONLogMaxFiles + if fromEnv := os.Getenv(dockerJSONLogMaxFilesEnvVar); fromEnv != "" { + maxFiles = fromEnv + } + options = map[string]string{ "max-size": maxSize, "max-file": maxFiles, - }, + } + } + return godocker.LogConfig{ + Type: driver, + Config: options, + } +} + +func parseLogOptions() map[string]string { + opts := os.Getenv(agentLogOptionsEnvVar) + logOptsDecoder := json.NewDecoder(strings.NewReader(opts)) + var logOptions map[string]string + err := logOptsDecoder.Decode(&logOptions) + // blank string is not a warning + if err != io.EOF && err != nil { + seelog.Warnf("Invalid format for \"ECS_LOG_OPTS\", expected a json object with string key value. error: %v", err) } + return logOptions } // InstanceConfigDirectory returns the location on disk for custom instance configuration diff --git a/ecs-init/config/common_test.go b/ecs-init/config/common_test.go index 807fed5e..10ee63c6 100644 --- a/ecs-init/config/common_test.go +++ b/ecs-init/config/common_test.go @@ -16,6 +16,7 @@ package config import ( "fmt" "os" + "reflect" "testing" ) @@ -84,38 +85,76 @@ func TestGetAgentPartitionBucketRegion(t *testing.T) { func TestAgentDockerLogDriverConfiguration(t *testing.T) { resetEnv := func() { + os.Unsetenv(agentLogDriverEnvVar) + os.Unsetenv(agentLogOptionsEnvVar) os.Unsetenv(dockerJSONLogMaxFilesEnvVar) os.Unsetenv(dockerJSONLogMaxSizeEnvVar) } resetEnv() testcases := []struct { - name string - - envFiles string - envSize string - - expectedFiles string - expectedSize string + name string + envDriver string + envOpts string + envFiles string + envSize string + expectedDriver string + expectedOpts map[string]string }{ { - name: "defaults", - - envFiles: "", - envSize: "", - - expectedFiles: dockerJSONLogMaxFiles, - expectedSize: dockerJSONLogMaxSize, + name: "all defaults", + envDriver: "", + envOpts: "", + envFiles: "", + envSize: "", + expectedDriver: "json-file", + expectedOpts: map[string]string{"max-size": dockerJSONLogMaxSize, "max-file": dockerJSONLogMaxFiles}, }, { - name: "environment set", - - envFiles: "1", - envSize: "1m", - - expectedFiles: "1", - expectedSize: "1m", + name: "opts default", + envDriver: "awslogs", + envOpts: "", + envFiles: "1", + envSize: "1m", + expectedDriver: "awslogs", + expectedOpts: nil, + }, + { + name: "override empty json opts outside of config", + envDriver: "json-file", + envOpts: "", + envFiles: "1", + envSize: "1m", + expectedDriver: "json-file", + expectedOpts: map[string]string{"max-size": "1m", "max-file": "1"}, + }, + { + name: "json log options take precedence", + envDriver: "json-file", + envOpts: "{\"max-size\":\"17m\",\"max-file\":\"5\"}", + envFiles: "1", + envSize: "1m", + expectedDriver: "json-file", + expectedOpts: map[string]string{"max-size": "17m", "max-file": "5"}, + }, + { + name: "malformed opts", + envDriver: "splunk", + envOpts: "{\"loggingOptions\"}", + envFiles: "", + envSize: "", + expectedDriver: "splunk", + expectedOpts: nil, + }, + { + name: "invalid driver", + envDriver: "invalidDriver", + envOpts: "{\"max-size\":\"17m\",\"max-file\":\"5\"}", + envFiles: "", + envSize: "", + expectedDriver: "json-file", + expectedOpts: map[string]string{"max-size": dockerJSONLogMaxSize, "max-file": dockerJSONLogMaxFiles}, }, } @@ -123,15 +162,17 @@ func TestAgentDockerLogDriverConfiguration(t *testing.T) { t.Run(test.name, func(t *testing.T) { resetEnv() + os.Setenv(agentLogDriverEnvVar, test.envDriver) + os.Setenv(agentLogOptionsEnvVar, test.envOpts) os.Setenv(dockerJSONLogMaxFilesEnvVar, test.envFiles) os.Setenv(dockerJSONLogMaxSizeEnvVar, test.envSize) result := AgentDockerLogDriverConfiguration() - if actual := result.Config["max-size"]; actual != test.expectedSize { - t.Errorf("Configured max-size %q is not the expected %q", actual, test.expectedSize) + if actual := result.Type; actual != test.expectedDriver { + t.Errorf("Configured log driver %q is not the expected %q", actual, test.expectedDriver) } - if actual := result.Config["max-file"]; actual != test.expectedFiles { - t.Errorf("Configured max-files %q is not the expected %q", actual, test.expectedFiles) + if actual := result.Config; !reflect.DeepEqual(actual, test.expectedOpts) { + t.Errorf("Configured log options %v is not the expected %v", actual, test.expectedOpts) } }) }