// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package manageddaemon import ( "fmt" "time" dockercontainer "github.com/docker/docker/api/types/container" ) const ( imageTarPath = "/var/lib/ecs/deps/daemons" imageTagDefault = "latest" defaultAgentCommunicationPathHostRoot = "/var/run/ecs" defaultApplicationLogPathHostRoot = "/var/log/ecs" defaultAgentCommunicationMount = "agentCommunicationMount" defaultApplicationLogMount = "applicationLogMount" ) type ManagedDaemon struct { imageName string imageRef string imageTag string healthCheckTest []string healthCheckInterval time.Duration healthCheckTimeout time.Duration healthCheckRetries int // Daemons require an agent <-> daemon mount // identified by the volume name `agentCommunicationMount` // the SourceVolumeHostPath will always be overridden to // /var/run/ecs/ agentCommunicationMount *MountPoint // Daemons require an application log mount // identified by the volume name `applicationLogMount` // the SourceVolumeHostPath will always be overridden to // /var/log/ecs/ applicationLogMount *MountPoint mountPoints []*MountPoint environment map[string]string loadedDaemonImageRef string } // A valid managed daemon will require // healthcheck and mount points to be added func NewManagedDaemon( imageName string, imageRef string, imageTag string, ) *ManagedDaemon { if imageTag == "" { imageTag = imageTagDefault } // health check retries 0 is valid // we'll override this default to -1 newManagedDaemon := &ManagedDaemon{ imageName: imageName, imageRef: imageRef, imageTag: imageTag, healthCheckRetries: -1, } return newManagedDaemon } // ImportAll function will parse/validate all managed daemon definitions // defined in /var/lib/ecs/deps/daemons and will return an array // of valid ManagedDeamon objects func ImportAll() []*ManagedDaemon { // TODO parse taskdef json files in /deps/daemons // TODO validate that each daemon has a corresponding image tar ebsManagedDaemon := NewManagedDaemon("ebs-csi-driver", "public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver", "v1.20.0") // TODO add healthcheck // TODO add mount points return []*ManagedDaemon{ebsManagedDaemon} } 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) GetImageName() string { return md.imageName } func (md *ManagedDaemon) GetImageRef() string { return md.imageRef } func (md *ManagedDaemon) GetImageTag() string { return md.imageTag } func (md *ManagedDaemon) GetImageCanonicalRef() string { return (fmt.Sprintf("%s:%s", md.imageRef, md.imageTag)) } func (md *ManagedDaemon) GetImageTarPath() string { return (fmt.Sprintf("%s/%s", imageTarPath, md.imageName)) } func (md *ManagedDaemon) GetAgentCommunicationMount() *MountPoint { return md.agentCommunicationMount } func (md *ManagedDaemon) GetApplicationLogMount() *MountPoint { return md.applicationLogMount } // returns list of mountpoints without the // agentCommunicationMount and applicationLogMount func (md *ManagedDaemon) GetFilteredMountPoints() []*MountPoint { filteredMounts := make([]*MountPoint, len(md.mountPoints)) copy(filteredMounts, md.mountPoints) return filteredMounts } // returns list of mountpoints which (re)integrates // agentCommunicationMount and applicationLogMount // these will always include host mount file overrides func (md *ManagedDaemon) GetMountPoints() []*MountPoint { allMounts := make([]*MountPoint, len(md.mountPoints)) copy(allMounts, md.mountPoints) allMounts = append(allMounts, md.agentCommunicationMount) allMounts = append(allMounts, md.applicationLogMount) return allMounts } func (md *ManagedDaemon) GetEnvironment() map[string]string { return md.environment } func (md *ManagedDaemon) GetLoadedDaemonImageRef() string { return md.loadedDaemonImageRef } // filter mount points for agentCommunicationMount // set required mounts // and override host paths in favor of agent defaults // when a duplicate SourceVolumeID is given, the last Mount wins func (md *ManagedDaemon) SetMountPoints(mountPoints []*MountPoint) error { var mountPointMap = make(map[string]*MountPoint) for _, mp := range mountPoints { if mp.SourceVolumeID == defaultAgentCommunicationMount { mp.SourceVolumeHostPath = fmt.Sprintf("%s/%s/", defaultAgentCommunicationPathHostRoot, md.imageName) md.agentCommunicationMount = mp } else if mp.SourceVolumeID == defaultApplicationLogMount { mp.SourceVolumeHostPath = fmt.Sprintf("%s/%s/", defaultApplicationLogPathHostRoot, md.imageName) md.applicationLogMount = mp } else { mountPointMap[mp.SourceVolumeID] = mp } } mountResult := []*MountPoint{} for _, mp := range mountPointMap { mountResult = append(mountResult, mp) } md.mountPoints = mountResult return nil } // Used to set or to update the agentCommunicationMount func (md *ManagedDaemon) SetAgentCommunicationMount(mp *MountPoint) error { if mp.SourceVolumeID == defaultAgentCommunicationMount { mp.SourceVolumeHostPath = fmt.Sprintf("%s/%s/", defaultAgentCommunicationPathHostRoot, md.imageName) md.agentCommunicationMount = mp return nil } else { return fmt.Errorf("AgentCommunicationMount %s must have a SourceVolumeID of %s", mp.SourceVolumeID, defaultAgentCommunicationMount) } } // Used to set or to update the applicationLogMount func (md *ManagedDaemon) SetApplicationLogMount(mp *MountPoint) error { if mp.SourceVolumeID == defaultApplicationLogMount { mp.SourceVolumeHostPath = fmt.Sprintf("%s/%s/", defaultApplicationLogPathHostRoot, md.imageName) md.applicationLogMount = mp return nil } else { return fmt.Errorf("ApplicationLogMount %s must have a SourceVolumeID of %s", mp.SourceVolumeID, defaultApplicationLogMount) } } func (md *ManagedDaemon) SetEnvironment(environment map[string]string) { md.environment = make(map[string]string) for key, val := range environment { md.environment[key] = val } } func (md *ManagedDaemon) SetLoadedDaemonImageRef(loadedImageRef string) { md.loadedDaemonImageRef = loadedImageRef } // 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 // MountPoint.SourceVolume is found func (md *ManagedDaemon) AddMountPoint(mp *MountPoint) error { mountIndex := md.GetMountPointIndex(mp) if mountIndex != -1 { return fmt.Errorf("MountPoint already exists at index %d", mountIndex) } md.mountPoints = append(md.mountPoints, mp) return nil } // UpdateMountPoint will update by // MountPoint.SourceVolume which is unique to the task // and will throw an error if the MountPoint.SourceVolume // is not found func (md *ManagedDaemon) UpdateMountPointBySourceVolume(mp *MountPoint) error { mountIndex := md.GetMountPointIndex(mp) if mountIndex == -1 { return fmt.Errorf("MountPoint %s not found; will not update", mp.SourceVolume) } md.mountPoints[mountIndex] = mp return nil } // UpdateMountPoint will delete by // MountPoint.SourceVolume which is unique to the task // and will throw an error if the MountPoint.SourceVolume // is not found func (md *ManagedDaemon) DeleteMountPoint(mp *MountPoint) error { mountIndex := md.GetMountPointIndex(mp) if mountIndex == -1 { return fmt.Errorf("MountPoint %s not found; will not delete", mp.SourceVolume) } md.mountPoints = append(md.mountPoints[:mountIndex], md.mountPoints[mountIndex+1:]...) return nil } // GetMountPointIndex will return index of a mountpoint or -1 // search by the unique MountPoint.SourceVolume field func (md *ManagedDaemon) GetMountPointIndex(mp *MountPoint) int { sourceVolume := mp.SourceVolume for i, mount := range md.mountPoints { if mount.SourceVolume == sourceVolume { return i } } return -1 } // AddEnvVar will add by envKey // and will throw an error if an existing // envKey is found func (md *ManagedDaemon) AddEnvVar(envKey string, envVal string) error { _, exists := md.environment[envKey] if !exists { md.environment[envKey] = envVal return nil } return fmt.Errorf("EnvKey: %s already exists; will not add EnvVal: %s", envKey, envVal) } // Updates environment varable by evnKey // and will throw an error if the envKey // is not found func (md *ManagedDaemon) UpdateEnvVar(envKey string, envVal string) error { _, ok := md.environment[envKey] if !ok { return fmt.Errorf("EnvKey: %s not found; will not update EnvVal: %s", envKey, envVal) } md.environment[envKey] = envVal return nil } // Deletes environment variable by envKey // and will throw an error if the envKey // is not found func (md *ManagedDaemon) DeleteEnvVar(envKey string) error { _, ok := md.environment[envKey] if !ok { return fmt.Errorf("EnvKey: %s not found; will not delete", envKey) } delete(md.environment, envKey) return nil } // Generates a DockerHealthConfig object from the // ManagedDaeemon Health Check fields func (md *ManagedDaemon) GetDockerHealthConfig() *dockercontainer.HealthConfig { return &dockercontainer.HealthConfig{ Test: md.healthCheckTest, Interval: md.healthCheckInterval, Timeout: md.healthCheckTimeout, Retries: md.healthCheckRetries, } } // Validates that all required fields are present and valid func (md *ManagedDaemon) IsValidManagedDaemon() bool { isValid := true isValid = isValid && (md.agentCommunicationMount != nil) isValid = isValid && (md.applicationLogMount != nil) isValid = isValid && (len(md.healthCheckTest) != 0) isValid = isValid && (md.healthCheckRetries != -1) return isValid }