diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go index 4ec7f5174b..dae8ce3784 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go @@ -80,7 +80,7 @@ func NewDockerKurtosisBackend( } } -func (backend *DockerKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) { +func (backend *DockerKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { return backend.dockerManager.FetchImage(ctx, image, downloadMode) } diff --git a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go index 35c91884d1..f8bbb7be5b 100644 --- a/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go +++ b/container-engine-lib/lib/backend_impls/docker/docker_manager/docker_manager.go @@ -524,7 +524,7 @@ func (manager *DockerManager) CreateAndStartContainer( dockerImage = dockerImage + dockerTagSeparatorChar + dockerDefaultTag } - _, err := manager.FetchImage(ctx, dockerImage, args.imageDownloadMode) + _, _, err := manager.FetchImage(ctx, dockerImage, args.imageDownloadMode) if err != nil { return "", nil, stacktrace.Propagate(err, "An error occurred fetching image '%v'", dockerImage) } @@ -1247,7 +1247,7 @@ func (manager *DockerManager) FetchLatestImage(ctx context.Context, dockerImage return nil } -func (manager *DockerManager) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) { +func (manager *DockerManager) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { var err error var pulledFromRemote bool = true logrus.Infof("Fetching image '%s' is running in '%s' mode", image, downloadMode) @@ -1258,14 +1258,19 @@ func (manager *DockerManager) FetchImage(ctx context.Context, image string, down case image_download_mode.ImageDownloadMode_Missing: pulledFromRemote, err = manager.FetchImageIfMissing(ctx, image) default: - return false, stacktrace.NewError("Undefined image pulling mode: '%v'", image_fetching) + return false, "", stacktrace.NewError("Undefined image pulling mode: '%v'", image_fetching) } if err != nil { - return false, stacktrace.Propagate(err, "An error occurred fetching image '%v'", image) + return false, "", stacktrace.Propagate(err, "An error occurred fetching image '%v'", image) } - return pulledFromRemote, nil + imageArchitecture, err := manager.getImagePlatform(ctx, image) + if err != nil { + return false, "", stacktrace.Propagate(err, "An error occurred while fetching the architecture of the image") + } + + return pulledFromRemote, imageArchitecture, nil } func (manager *DockerManager) CreateContainerExec(context context.Context, containerId string, cmd []string) (*types.HijackedResponse, error) { @@ -1404,6 +1409,15 @@ func (manager *DockerManager) getNetworksByFilterArgs(ctx context.Context, args return networks, nil } +func (manager *DockerManager) getImagePlatform(ctx context.Context, imageName string) (string, error) { + imageInspect, _, err := manager.dockerClient.ImageInspectWithRaw(ctx, imageName) + if err != nil { + return "", stacktrace.Propagate(err, "an error occurred while running image inspect on image '%v'", imageName) + } + + return imageInspect.Architecture, nil +} + /* Creates a Docker-Container-To-Host Port mapping, defining how a Container's JSON RPC and service-specific ports are mapped to the host ports. diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go index 4ba34562f7..21f839f42f 100644 --- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go @@ -119,9 +119,9 @@ func NewKubernetesKurtosisBackend( } } -func (backend *KubernetesKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) { +func (backend *KubernetesKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { logrus.Warnf("FetchImage isn't implemented for Kubernetes yet") - return false, nil + return false, "", nil } func (backend *KubernetesKurtosisBackend) PruneUnusedImages(ctx context.Context) ([]string, error) { diff --git a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go index 5260a911d0..a41bb4b2c6 100644 --- a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go @@ -27,12 +27,12 @@ func NewMetricsReportingKurtosisBackend(underlying backend_interface.KurtosisBac return &MetricsReportingKurtosisBackend{underlying: underlying} } -func (backend *MetricsReportingKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) { - pulledFromRemote, err := backend.underlying.FetchImage(ctx, image, downloadMode) +func (backend *MetricsReportingKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { + pulledFromRemote, architecture, err := backend.underlying.FetchImage(ctx, image, downloadMode) if err != nil { - return false, stacktrace.Propagate(err, "An error occurred pulling image '%v'", image) + return false, "", stacktrace.Propagate(err, "An error occurred pulling image '%v'", image) } - return pulledFromRemote, nil + return pulledFromRemote, architecture, nil } func (backend *MetricsReportingKurtosisBackend) PruneUnusedImages(ctx context.Context) ([]string, error) { diff --git a/container-engine-lib/lib/backend_interface/kurtosis_backend.go b/container-engine-lib/lib/backend_interface/kurtosis_backend.go index b858d3afd9..ab6fc98e47 100644 --- a/container-engine-lib/lib/backend_interface/kurtosis_backend.go +++ b/container-engine-lib/lib/backend_interface/kurtosis_backend.go @@ -31,7 +31,8 @@ type KurtosisBackend interface { // FetchImage always attempts to retrieve the latest image. // If retrieving the latest [dockerImage] fails, the local image will be used. // Returns True is it was retrieved from cloud or False if it's a local image - FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) + // Returns a string that represents the architecture of the image + FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) PruneUnusedImages(ctx context.Context) ([]string, error) diff --git a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go index 28ab090d1a..29fd97ae02 100644 --- a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go +++ b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.23.1. DO NOT EDIT. +// Code generated by mockery v2.22.1. DO NOT EDIT. package backend_interface @@ -803,12 +803,13 @@ func (_c *MockKurtosisBackend_DumpKurtosis_Call) RunAndReturn(run func(context.C } // FetchImage provides a mock function with given fields: ctx, image, downloadMode -func (_m *MockKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, error) { +func (_m *MockKurtosisBackend) FetchImage(ctx context.Context, image string, downloadMode image_download_mode.ImageDownloadMode) (bool, string, error) { ret := _m.Called(ctx, image, downloadMode) var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, image_download_mode.ImageDownloadMode) (bool, error)); ok { + var r1 string + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, image_download_mode.ImageDownloadMode) (bool, string, error)); ok { return rf(ctx, image, downloadMode) } if rf, ok := ret.Get(0).(func(context.Context, string, image_download_mode.ImageDownloadMode) bool); ok { @@ -817,13 +818,19 @@ func (_m *MockKurtosisBackend) FetchImage(ctx context.Context, image string, dow r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(context.Context, string, image_download_mode.ImageDownloadMode) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, image_download_mode.ImageDownloadMode) string); ok { r1 = rf(ctx, image, downloadMode) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(string) } - return r0, r1 + if rf, ok := ret.Get(2).(func(context.Context, string, image_download_mode.ImageDownloadMode) error); ok { + r2 = rf(ctx, image, downloadMode) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } // MockKurtosisBackend_FetchImage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FetchImage' @@ -846,12 +853,12 @@ func (_c *MockKurtosisBackend_FetchImage_Call) Run(run func(ctx context.Context, return _c } -func (_c *MockKurtosisBackend_FetchImage_Call) Return(_a0 bool, _a1 error) *MockKurtosisBackend_FetchImage_Call { - _c.Call.Return(_a0, _a1) +func (_c *MockKurtosisBackend_FetchImage_Call) Return(_a0 bool, _a1 string, _a2 error) *MockKurtosisBackend_FetchImage_Call { + _c.Call.Return(_a0, _a1, _a2) return _c } -func (_c *MockKurtosisBackend_FetchImage_Call) RunAndReturn(run func(context.Context, string, image_download_mode.ImageDownloadMode) (bool, error)) *MockKurtosisBackend_FetchImage_Call { +func (_c *MockKurtosisBackend_FetchImage_Call) RunAndReturn(run func(context.Context, string, image_download_mode.ImageDownloadMode) (bool, string, error)) *MockKurtosisBackend_FetchImage_Call { _c.Call.Return(run) return _c } @@ -2152,12 +2159,13 @@ func (_c *MockKurtosisBackend_UpdateEnclave_Call) RunAndReturn(run func(context. return _c } -// NewMockKurtosisBackend creates a new instance of MockKurtosisBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockKurtosisBackend(t interface { +type mockConstructorTestingTNewMockKurtosisBackend interface { mock.TestingT Cleanup(func()) -}) *MockKurtosisBackend { +} + +// NewMockKurtosisBackend creates a new instance of MockKurtosisBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockKurtosisBackend(t mockConstructorTestingTNewMockKurtosisBackend) *MockKurtosisBackend { mock := &MockKurtosisBackend{} mock.Mock.Test(t) diff --git a/core/server/api_container/server/startosis_engine/startosis_validator.go b/core/server/api_container/server/startosis_engine/startosis_validator.go index 7d15d0b4df..cfaadf52d5 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator.go @@ -3,6 +3,7 @@ package startosis_engine import ( "context" "fmt" + "runtime" "strings" "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" @@ -26,7 +27,11 @@ const ( containerDownloadedImagesMsgFromLocal = "locally cached" containerDownloadedImagesMsgFromRemote = "remotely downloaded" containerDownloadedImagesMsgLineFormat = "> %s - %s" - linebreak = "\n" + + containerImageArchWarningHeaderFormat = "Container images with different architecture than expected(%s):" + containerImageArchitectureMsgLineFormat = "> %s - %s" + + linebreak = "\n" ) type StartosisValidator struct { @@ -208,6 +213,7 @@ func sendContainerImageSummaryInfoMsg( } imageLines := []string{} + imagesWithIncorrectArchLines := []string{} for image, validatedImage := range imageSuccessfullyValidated { pulledFromStr := containerDownloadedImagesMsgFromLocal @@ -215,6 +221,13 @@ func sendContainerImageSummaryInfoMsg( pulledFromStr = containerDownloadedImagesMsgFromRemote } + architecture := validatedImage.GetArchitecture() + + if architecture != runtime.GOARCH { + imageWithIncorrectArchLine := fmt.Sprintf(containerImageArchitectureMsgLineFormat, image, architecture) + imagesWithIncorrectArchLines = append(imagesWithIncorrectArchLines, imageWithIncorrectArchLine) + } + imageLine := fmt.Sprintf(containerDownloadedImagesMsgLineFormat, image, pulledFromStr) imageLines = append(imageLines, imageLine) } @@ -225,6 +238,14 @@ func sendContainerImageSummaryInfoMsg( msg := strings.Join(msgLines, linebreak) starlarkRunResponseLineStream <- binding_constructors.NewStarlarkRunResponseLineFromInfoMsg(msg) + + if len(imagesWithIncorrectArchLines) > 0 { + imageWarningHeader := fmt.Sprintf(containerImageArchWarningHeaderFormat, runtime.GOARCH) + imagesWithArchMsgLines := []string{imageWarningHeader} + imagesWithArchMsgLines = append(imagesWithArchMsgLines, imagesWithIncorrectArchLines...) + imagesWithDiffArchWarningMessage := strings.Join(imagesWithArchMsgLines, linebreak) + starlarkRunResponseLineStream <- binding_constructors.NewStarlarkRunResponseLineFromWarning(imagesWithDiffArchWarningMessage) + } } func updateProgressWithDownloadInfo(starlarkRunResponseLineStream chan<- *kurtosis_core_rpc_api_bindings.StarlarkRunResponseLine, imageCurrentlyInProgress []string, numberOfImageValidated uint32, totalNumberOfImagesToValidate uint32) { diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/docker_images_validator.go b/core/server/api_container/server/startosis_engine/startosis_validator/docker_images_validator.go index 7b19bcf0fe..9776146f9c 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/docker_images_validator.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/docker_images_validator.go @@ -54,16 +54,17 @@ func (validator *DockerImagesValidator) Validate(ctx context.Context, environmen func fetchImageFromBackend(ctx context.Context, wg *sync.WaitGroup, imageCurrentlyDownloading chan bool, backend *backend_interface.KurtosisBackend, imageName string, imageDownloadMode image_download_mode.ImageDownloadMode, pullErrors chan<- error, imageDownloadStarted chan<- string, imageDownloadFinished chan<- *ValidatedImage) { logrus.Debugf("Requesting the download of image: '%s'", imageName) var imagePulledFromRemote bool + var imageArch string defer wg.Done() imageCurrentlyDownloading <- true imageDownloadStarted <- imageName defer func() { <-imageCurrentlyDownloading - imageDownloadFinished <- NewValidatedImage(imageName, imagePulledFromRemote) + imageDownloadFinished <- NewValidatedImage(imageName, imagePulledFromRemote, imageArch) }() logrus.Debugf("Starting the download of image: '%s'", imageName) - imagePulledFromRemote, err := (*backend).FetchImage(ctx, imageName, imageDownloadMode) + imagePulledFromRemote, imageArch, err := (*backend).FetchImage(ctx, imageName, imageDownloadMode) if err != nil { logrus.Warnf("Container image '%s' download failed. Error was: '%s'", imageName, err.Error()) pullErrors <- startosis_errors.WrapWithValidationError(err, "Failed fetching the required image '%v'.", imageName) diff --git a/core/server/api_container/server/startosis_engine/startosis_validator/validated_image.go b/core/server/api_container/server/startosis_engine/startosis_validator/validated_image.go index 6548f65018..f1766a4a6b 100644 --- a/core/server/api_container/server/startosis_engine/startosis_validator/validated_image.go +++ b/core/server/api_container/server/startosis_engine/startosis_validator/validated_image.go @@ -3,10 +3,11 @@ package startosis_validator type ValidatedImage struct { name string pulledFromRemote bool + architecture string } -func NewValidatedImage(name string, pulledFromRemote bool) *ValidatedImage { - return &ValidatedImage{name: name, pulledFromRemote: pulledFromRemote} +func NewValidatedImage(name string, pulledFromRemote bool, architecture string) *ValidatedImage { + return &ValidatedImage{name: name, pulledFromRemote: pulledFromRemote, architecture: architecture} } func (v *ValidatedImage) GetName() string { @@ -16,3 +17,7 @@ func (v *ValidatedImage) GetName() string { func (v *ValidatedImage) GetPulledFromRemote() bool { return v.pulledFromRemote } + +func (v *ValidatedImage) GetArchitecture() string { + return v.architecture +}