// 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 image import ( "encoding/json" "fmt" "strings" "sync" "time" "github.com/aws/amazon-ecs-agent/ecs-agent/logger" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" ) type Image struct { ImageID string Names []string Size int64 } func (image *Image) String() string { return fmt.Sprintf("ImageID: %s; Names: %s", image.ImageID, strings.Join(image.Names, ", ")) } // ImageState represents a docker image // and its state information such as containers associated with it type ImageState struct { // Image is the image corresponding to this ImageState. Image *Image // Containers are the containers that use this image. Containers []*apicontainer.Container `json:"-"` // PulledAt is the time when this image was pulled. PulledAt time.Time // LastUsedAt is the time when this image was used last time. LastUsedAt time.Time // PullSucceeded defines whether this image has been pulled successfully before, // this should be set to true when one of the pull image call succeeds. PullSucceeded bool lock sync.RWMutex } // UpdateContainerReference updates container reference in image state func (imageState *ImageState) UpdateContainerReference(container *apicontainer.Container) { imageState.lock.Lock() defer imageState.lock.Unlock() logger.Info("Updating container reference in image state", logger.Fields{ field.Container: container.Name, field.Image: container.Image, "state": imageState.Image.ImageID, }) imageState.Containers = append(imageState.Containers, container) } // AddImageName adds image name to image state func (imageState *ImageState) AddImageName(imageName string) { imageState.lock.Lock() defer imageState.lock.Unlock() if !imageState.HasImageName(imageName) { logger.Info("Adding image to state", logger.Fields{ field.Image: imageName, "state": imageState.Image.ImageID, }) imageState.Image.Names = append(imageState.Image.Names, imageName) } } // GetImageID returns id of image func (imageState *ImageState) GetImageID() string { imageState.lock.RLock() defer imageState.lock.RUnlock() return imageState.Image.ImageID } // GetImageNamesCount returns number of image names func (imageState *ImageState) GetImageNamesCount() int { imageState.lock.RLock() defer imageState.lock.RUnlock() return len(imageState.Image.Names) } // HasNoAssociatedContainers returns true if image has no associated containers, false otherwise func (imageState *ImageState) HasNoAssociatedContainers() bool { return len(imageState.Containers) == 0 } // UpdateImageState updates image name and container reference in image state func (imageState *ImageState) UpdateImageState(container *apicontainer.Container) { imageState.AddImageName(container.Image) imageState.UpdateContainerReference(container) } // RemoveImageName removes image name from image state func (imageState *ImageState) RemoveImageName(containerImageName string) bool { imageState.lock.Lock() defer imageState.lock.Unlock() for i, imageName := range imageState.Image.Names { if imageName == containerImageName { imageState.Image.Names = append(imageState.Image.Names[:i], imageState.Image.Names[i+1:]...) return true } } return false } // HasImageName returns true if image state contains the containerImageName func (imageState *ImageState) HasImageName(containerImageName string) bool { for _, imageName := range imageState.Image.Names { if imageName == containerImageName { return true } } return false } // RemoveContainerReference removes container reference from image state func (imageState *ImageState) RemoveContainerReference(container *apicontainer.Container) error { // Get the image state write lock for updating container reference imageState.lock.Lock() defer imageState.lock.Unlock() for i := range imageState.Containers { if imageState.Containers[i].Name == container.Name { // Container reference found; hence remove it logger.Info("Removing container reference from image state", logger.Fields{ field.Container: container.Name, field.Image: container.Image, "state": imageState.Image.ImageID, }) imageState.Containers = append(imageState.Containers[:i], imageState.Containers[i+1:]...) // Update the last used time for the image imageState.LastUsedAt = time.Now() return nil } } return fmt.Errorf("Container reference is not found in the image state container: %s", container.String()) } // SetPullSucceeded sets the PullSucceeded of the imageState func (imageState *ImageState) SetPullSucceeded(pullSucceeded bool) { imageState.lock.Lock() defer imageState.lock.Unlock() imageState.PullSucceeded = pullSucceeded } // GetPullSucceeded safely returns the PullSucceeded of the imageState func (imageState *ImageState) GetPullSucceeded() bool { imageState.lock.RLock() defer imageState.lock.RUnlock() return imageState.PullSucceeded } // MarshalJSON marshals image state func (imageState *ImageState) MarshalJSON() ([]byte, error) { imageState.lock.Lock() defer imageState.lock.Unlock() return json.Marshal(&struct { Image *Image PulledAt time.Time LastUsedAt time.Time PullSucceeded bool }{ Image: imageState.Image, PulledAt: imageState.PulledAt, LastUsedAt: imageState.LastUsedAt, PullSucceeded: imageState.PullSucceeded, }) } func (imageState *ImageState) String() string { image := "" if imageState.Image != nil { image = imageState.Image.String() } return fmt.Sprintf("Image: [%s] referenced by %d containers; PulledAt: %s; LastUsedAt: %s; PullSucceeded: %t", image, len(imageState.Containers), imageState.PulledAt.UTC().Format(time.RFC3339), imageState.LastUsedAt.UTC().Format(time.RFC3339), imageState.PullSucceeded) } func (imageState *ImageState) Fields() logger.Fields { imageID := "" names := []string{} size := int64(-1) if imageState.Image != nil { imageID = imageState.Image.ImageID names = imageState.Image.Names size = imageState.Image.Size } return logger.Fields{ field.ImageID: imageID, field.ImageNames: names, field.ImageSizeBytes: size, "referencedBy": len(imageState.Containers), field.ImagePulledAt: imageState.PulledAt.UTC().Format(time.RFC3339), field.ImageLastUsedAt: imageState.LastUsedAt.UTC().Format(time.RFC3339), field.ImagePullSucceeded: imageState.PullSucceeded, } }