AWSlambda: Let create_function
reference ECR via ImageUri (#5688)
This commit is contained in:
parent
00c0a66d1c
commit
3d95ac0978
@ -28,9 +28,11 @@ from moto.core.exceptions import RESTError
|
||||
from moto.core.utils import unix_time_millis
|
||||
from moto.iam.models import iam_backends
|
||||
from moto.iam.exceptions import IAMNotFoundException
|
||||
from moto.ecr.exceptions import ImageNotFoundException
|
||||
from moto.logs.models import logs_backends
|
||||
from moto.moto_api._internal import mock_random as random
|
||||
from moto.s3.models import s3_backends, FakeKey
|
||||
from moto.ecr.models import ecr_backends
|
||||
from moto.s3.exceptions import MissingBucket, MissingKey
|
||||
from moto import settings
|
||||
from .exceptions import (
|
||||
@ -481,10 +483,29 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
||||
self.code_size = 0
|
||||
self.code_sha_256 = ""
|
||||
elif "ImageUri" in self.code:
|
||||
self.code_sha_256 = hashlib.sha256(
|
||||
self.code["ImageUri"].encode("utf-8")
|
||||
).hexdigest()
|
||||
self.code_size = 0
|
||||
if settings.lambda_stub_ecr():
|
||||
self.code_sha_256 = hashlib.sha256(
|
||||
self.code["ImageUri"].encode("utf-8")
|
||||
).hexdigest()
|
||||
self.code_size = 0
|
||||
else:
|
||||
uri, tag = self.code["ImageUri"].split(":")
|
||||
repo_name = uri.split("/")[-1]
|
||||
image_id = {"imageTag": tag}
|
||||
ecr_backend = ecr_backends[self.account_id][self.region]
|
||||
registry_id = ecr_backend.describe_registry()["registryId"]
|
||||
images = ecr_backend.batch_get_image(
|
||||
repository_name=repo_name, image_ids=[image_id]
|
||||
)["images"]
|
||||
|
||||
if len(images) == 0:
|
||||
raise ImageNotFoundException(image_id, repo_name, registry_id) # type: ignore
|
||||
else:
|
||||
manifest = json.loads(images[0]["imageManifest"])
|
||||
self.code_sha_256 = images[0]["imageId"]["imageDigest"].replace(
|
||||
"sha256:", ""
|
||||
)
|
||||
self.code_size = manifest["config"]["size"]
|
||||
|
||||
self.function_arn = make_function_arn(
|
||||
self.region, self.account_id, self.function_name
|
||||
|
@ -249,9 +249,11 @@ class Image(BaseObject):
|
||||
self.last_scan = None
|
||||
|
||||
def _create_digest(self):
|
||||
image_contents = f"docker_image{int(random.random() * 10**6)}"
|
||||
image_manifest = json.loads(self.image_manifest)
|
||||
layer_digests = [layer["digest"] for layer in image_manifest["layers"]]
|
||||
self.image_digest = (
|
||||
"sha256:" + hashlib.sha256(image_contents.encode("utf-8")).hexdigest()
|
||||
"sha256:"
|
||||
+ hashlib.sha256("".join(layer_digests).encode("utf-8")).hexdigest()
|
||||
)
|
||||
|
||||
def get_image_digest(self):
|
||||
|
@ -69,6 +69,13 @@ def allow_unknown_region() -> bool:
|
||||
return os.environ.get("MOTO_ALLOW_NONEXISTENT_REGION", "false").lower() == "true"
|
||||
|
||||
|
||||
def lambda_stub_ecr() -> bool:
|
||||
# Whether to stub or mock ecr backend when deploying image based lambdas.
|
||||
# True => don't requiring image presence in moto ecr backend for `create_function`.
|
||||
# False => require image presence in moto ecr backend for `create_function`
|
||||
return os.environ.get("MOTO_LAMBDA_STUB_ECR", "TRUE").lower() != "false"
|
||||
|
||||
|
||||
def moto_server_port() -> str:
|
||||
return os.environ.get("MOTO_PORT") or "5000"
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
from unittest import SkipTest
|
||||
import botocore.client
|
||||
import boto3
|
||||
import hashlib
|
||||
@ -7,7 +10,8 @@ import pytest
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from freezegun import freeze_time
|
||||
from moto import mock_lambda, mock_s3
|
||||
from tests.test_ecr.test_ecr_helpers import _create_image_manifest
|
||||
from moto import mock_lambda, mock_s3, mock_ecr, settings
|
||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
||||
from uuid import uuid4
|
||||
from .utilities import (
|
||||
@ -205,11 +209,29 @@ def test_create_function__with_tracingmode(tracing_mode):
|
||||
result.should.have.key("TracingConfig").should.equal({"Mode": output})
|
||||
|
||||
|
||||
@pytest.fixture(name="with_ecr_mock")
|
||||
def ecr_repo_fixture():
|
||||
with mock_ecr():
|
||||
os.environ["MOTO_LAMBDA_STUB_ECR"] = "FALSE"
|
||||
repo_name = "testlambdaecr"
|
||||
ecr_client = ecr_client = boto3.client("ecr", "us-east-1")
|
||||
ecr_client.create_repository(repositoryName=repo_name)
|
||||
ecr_client.put_image(
|
||||
repositoryName=repo_name,
|
||||
imageManifest=json.dumps(_create_image_manifest()),
|
||||
imageTag="latest",
|
||||
)
|
||||
yield
|
||||
ecr_client.delete_repository(repositoryName=repo_name, force=True)
|
||||
os.environ["MOTO_LAMBDA_STUB_ECR"] = "TRUE"
|
||||
|
||||
|
||||
@mock_lambda
|
||||
def test_create_function_from_image():
|
||||
def test_create_function_from_stubbed_ecr():
|
||||
lambda_client = boto3.client("lambda", "us-east-1")
|
||||
fn_name = str(uuid4())[0:6]
|
||||
image_uri = "111122223333.dkr.ecr.us-east-1.amazonaws.com/testlambda:latest"
|
||||
|
||||
dic = {
|
||||
"FunctionName": fn_name,
|
||||
"Role": get_role_name(),
|
||||
@ -217,6 +239,7 @@ def test_create_function_from_image():
|
||||
"PackageType": "Image",
|
||||
"Timeout": 100,
|
||||
}
|
||||
|
||||
resp = lambda_client.create_function(**dic)
|
||||
|
||||
resp.should.have.key("FunctionName").equals(fn_name)
|
||||
@ -236,6 +259,79 @@ def test_create_function_from_image():
|
||||
code.should.have.key("ResolvedImageUri").equals(resolved_image_uri)
|
||||
|
||||
|
||||
@mock_lambda
|
||||
def test_create_function_from_mocked_ecr_image(
|
||||
with_ecr_mock,
|
||||
): # pylint: disable=unused-argument
|
||||
if settings.TEST_SERVER_MODE:
|
||||
raise SkipTest(
|
||||
"Envars not easily set in server mode, feature off by default, skipping..."
|
||||
)
|
||||
|
||||
lambda_client = boto3.client("lambda", "us-east-1")
|
||||
fn_name = str(uuid4())[0:6]
|
||||
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:latest"
|
||||
|
||||
dic = {
|
||||
"FunctionName": fn_name,
|
||||
"Role": get_role_name(),
|
||||
"Code": {"ImageUri": image_uri},
|
||||
"PackageType": "Image",
|
||||
"Timeout": 100,
|
||||
}
|
||||
resp = lambda_client.create_function(**dic)
|
||||
|
||||
resp.should.have.key("FunctionName").equals(fn_name)
|
||||
resp.should.have.key("CodeSize").greater_than(0)
|
||||
resp.should.have.key("CodeSha256")
|
||||
resp.should.have.key("PackageType").equals("Image")
|
||||
|
||||
result = lambda_client.get_function(FunctionName=fn_name)
|
||||
result.should.have.key("Configuration")
|
||||
config = result["Configuration"]
|
||||
config.should.have.key("CodeSha256").equals(resp["CodeSha256"])
|
||||
config.should.have.key("CodeSize").equals(resp["CodeSize"])
|
||||
result.should.have.key("Code")
|
||||
code = result["Code"]
|
||||
code.should.have.key("RepositoryType").equals("ECR")
|
||||
code.should.have.key("ImageUri").equals(image_uri)
|
||||
image_uri_without_tag = image_uri.split(":")[0]
|
||||
resolved_image_uri = f"{image_uri_without_tag}@sha256:{config['CodeSha256']}"
|
||||
code.should.have.key("ResolvedImageUri").equals(resolved_image_uri)
|
||||
|
||||
|
||||
@mock_lambda
|
||||
def test_create_function_from_mocked_ecr_missing_image(
|
||||
with_ecr_mock,
|
||||
): # pylint: disable=unused-argument
|
||||
if settings.TEST_SERVER_MODE:
|
||||
raise SkipTest(
|
||||
"Envars not easily set in server mode, feature off by default, skipping..."
|
||||
)
|
||||
|
||||
lambda_client = boto3.client("lambda", "us-east-1")
|
||||
|
||||
fn_name = str(uuid4())[0:6]
|
||||
image_uri = f"{ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/testlambdaecr:dne"
|
||||
|
||||
dic = {
|
||||
"FunctionName": fn_name,
|
||||
"Role": get_role_name(),
|
||||
"Code": {"ImageUri": image_uri},
|
||||
"PackageType": "Image",
|
||||
"Timeout": 100,
|
||||
}
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
lambda_client.create_function(**dic)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ImageNotFoundException")
|
||||
err["Message"].should.equal(
|
||||
"The image with imageId {'imageTag': 'dne'} does not exist within the repository with name 'testlambdaecr' in the registry with id '123456789012'"
|
||||
)
|
||||
|
||||
|
||||
@mock_lambda
|
||||
@mock_s3
|
||||
@freeze_time("2015-01-01 00:00:00")
|
||||
|
@ -1,11 +1,9 @@
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
import os
|
||||
from random import random
|
||||
|
||||
import re
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
@ -19,41 +17,7 @@ from unittest import SkipTest
|
||||
|
||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
||||
|
||||
|
||||
def _create_image_digest(contents=None):
|
||||
if not contents:
|
||||
contents = f"docker_image{int(random() * 10**6)}"
|
||||
return "sha256:" + hashlib.sha256(contents.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _create_image_manifest():
|
||||
return {
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": 7023,
|
||||
"digest": _create_image_digest("config"),
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 32654,
|
||||
"digest": _create_image_digest("layer1"),
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 16724,
|
||||
"digest": _create_image_digest("layer2"),
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": 73109,
|
||||
# randomize image digest
|
||||
"digest": _create_image_digest(),
|
||||
},
|
||||
],
|
||||
}
|
||||
from tests.test_ecr.test_ecr_helpers import _create_image_manifest
|
||||
|
||||
|
||||
@mock_ecr
|
||||
@ -1298,7 +1262,9 @@ def test_delete_batch_image_with_multiple_images():
|
||||
# Populate mock repo with images
|
||||
for i in range(10):
|
||||
client.put_image(
|
||||
repositoryName=repo_name, imageManifest=f"manifest{i}", imageTag=f"tag{i}"
|
||||
repositoryName=repo_name,
|
||||
imageManifest=json.dumps(_create_image_manifest()),
|
||||
imageTag=f"tag{i}",
|
||||
)
|
||||
|
||||
# Pull down image digests for each image in the mock repo
|
||||
|
40
tests/test_ecr/test_ecr_helpers.py
Normal file
40
tests/test_ecr/test_ecr_helpers.py
Normal file
@ -0,0 +1,40 @@
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
|
||||
def _generate_random_sha():
|
||||
return hashlib.sha256(f"{random.randint(0,100)}".encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _create_image_layers(n):
|
||||
layers = []
|
||||
for _ in range(n):
|
||||
layers.append(
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"size": random.randint(100, 1000),
|
||||
"digest": f"sha256:{_generate_random_sha()}",
|
||||
}
|
||||
)
|
||||
return layers
|
||||
|
||||
|
||||
def _create_image_digest(layers):
|
||||
layer_digests = "".join([layer["digest"] for layer in layers])
|
||||
return hashlib.sha256(f"{layer_digests}".encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _create_image_manifest(image_digest=None):
|
||||
layers = _create_image_layers(5)
|
||||
if image_digest is None:
|
||||
image_digest = _create_image_digest(layers)
|
||||
return {
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||
"size": sum([layer["size"] for layer in layers]),
|
||||
"digest": image_digest,
|
||||
},
|
||||
"layers": layers,
|
||||
}
|
Loading…
Reference in New Issue
Block a user