11import base64
2+ import hashlib
23import logging
34import os
5+ import subprocess
46import uuid
57from datetime import datetime
68from pathlib import Path
1012
1113from colorama import Fore , Style
1214
15+ from feast import flags_helper
1316from feast .constants import (
1417 AWS_LAMBDA_FEATURE_SERVER_IMAGE ,
1518 AWS_LAMBDA_FEATURE_SERVER_REPOSITORY ,
@@ -88,18 +91,18 @@ def update_infra(
8891 )
8992
9093 ecr_client = boto3 .client ("ecr" )
94+ docker_image_version = _get_docker_image_version ()
9195 repository_uri = self ._create_or_get_repository_uri (ecr_client )
92- version = _get_version_for_aws ()
9396 # Only download & upload the docker image if it doesn't already exist in ECR
9497 if not ecr_client .batch_get_image (
9598 repositoryName = AWS_LAMBDA_FEATURE_SERVER_REPOSITORY ,
96- imageIds = [{"imageTag" : version }],
99+ imageIds = [{"imageTag" : docker_image_version }],
97100 ).get ("images" ):
98101 image_uri = self ._upload_docker_image (
99- ecr_client , repository_uri , version
102+ ecr_client , repository_uri , docker_image_version
100103 )
101104 else :
102- image_uri = f"{ repository_uri } :{ version } "
105+ image_uri = f"{ repository_uri } :{ docker_image_version } "
103106
104107 self ._deploy_feature_server (project , image_uri )
105108
@@ -154,11 +157,10 @@ def _deploy_feature_server(self, project: str, image_uri: str):
154157 # feature views, feature services, and other definitions does not update lambda).
155158 _logger .info (" Updating AWS Lambda..." )
156159
157- lambda_client .update_function_configuration (
158- FunctionName = resource_name ,
159- Environment = {
160- "Variables" : {FEATURE_STORE_YAML_ENV_NAME : config_base64 }
161- },
160+ aws_utils .update_lambda_function_environment (
161+ lambda_client ,
162+ resource_name ,
163+ {"Variables" : {FEATURE_STORE_YAML_ENV_NAME : config_base64 }},
162164 )
163165
164166 api = aws_utils .get_first_api_gateway (api_gateway_client , resource_name )
@@ -235,7 +237,7 @@ def get_feature_server_endpoint(self) -> Optional[str]:
235237 return f"https://{ api_id } .execute-api.{ region } .amazonaws.com"
236238
237239 def _upload_docker_image (
238- self , ecr_client , repository_uri : str , version : str
240+ self , ecr_client , repository_uri : str , docker_image_version : str
239241 ) -> str :
240242 """
241243 Pulls the AWS Lambda docker image from Dockerhub and uploads it to AWS ECR.
@@ -258,12 +260,11 @@ def _upload_docker_image(
258260
259261 raise DockerDaemonNotRunning ()
260262
263+ dockerhub_image = f"{ AWS_LAMBDA_FEATURE_SERVER_IMAGE } :{ docker_image_version } "
261264 _logger .info (
262- f"Pulling remote image { Style .BRIGHT + Fore .GREEN } { AWS_LAMBDA_FEATURE_SERVER_IMAGE } { Style .RESET_ALL } "
265+ f"Pulling remote image { Style .BRIGHT + Fore .GREEN } { dockerhub_image } { Style .RESET_ALL } "
263266 )
264- for line in docker_client .api .pull (
265- AWS_LAMBDA_FEATURE_SERVER_IMAGE , stream = True , decode = True
266- ):
267+ for line in docker_client .api .pull (dockerhub_image , stream = True , decode = True ):
267268 _logger .debug (f" { line } " )
268269
269270 auth_token = ecr_client .get_authorization_token ()["authorizationData" ][0 ][
@@ -280,14 +281,14 @@ def _upload_docker_image(
280281 )
281282 _logger .debug (f" { login_status } " )
282283
283- image = docker_client .images .get (AWS_LAMBDA_FEATURE_SERVER_IMAGE )
284- image_remote_name = f"{ repository_uri } :{ version } "
284+ image = docker_client .images .get (dockerhub_image )
285+ image_remote_name = f"{ repository_uri } :{ docker_image_version } "
285286 _logger .info (
286287 f"Pushing local image to remote { Style .BRIGHT + Fore .GREEN } { image_remote_name } { Style .RESET_ALL } "
287288 )
288289 image .tag (image_remote_name )
289290 for line in docker_client .api .push (
290- repository_uri , tag = version , stream = True , decode = True
291+ repository_uri , tag = docker_image_version , stream = True , decode = True
291292 ):
292293 _logger .debug (f" { line } " )
293294
@@ -310,21 +311,53 @@ def _create_or_get_repository_uri(self, ecr_client):
310311
311312def _get_lambda_name (project : str ):
312313 lambda_prefix = AWS_LAMBDA_FEATURE_SERVER_REPOSITORY
313- lambda_suffix = f"{ project } -{ _get_version_for_aws ()} "
314+ lambda_suffix = f"{ project } -{ _get_docker_image_version ()} "
314315 # AWS Lambda name can't have the length greater than 64 bytes.
315- # This usually occurs during integration tests or when feast is
316- # installed in editable mode (pip install -e), where feast version is long
316+ # This usually occurs during integration tests where feast version is long
317317 if len (lambda_prefix ) + len (lambda_suffix ) >= 63 :
318- lambda_suffix = base64 . b64encode (lambda_suffix .encode ()).decode ()[: 40 ]
318+ lambda_suffix = hashlib . md5 (lambda_suffix .encode ()).hexdigest ()
319319 return f"{ lambda_prefix } -{ lambda_suffix } "
320320
321321
322- def _get_version_for_aws ():
323- """Returns Feast version with certain characters replaced.
322+ def _get_docker_image_version () -> str :
323+ """Returns a version for the feature server Docker image.
324+
325+ For public Feast releases this equals to the Feast SDK version modified by replacing "." with "_".
326+ For example, Feast SDK version "0.14.1" would correspond to Docker image version "0_14_1".
327+
328+ For integration tests this equals to the git commit hash of HEAD. This is necessary,
329+ because integration tests need to use images built from the same commit hash.
330+
331+ During development (when Feast is installed in editable mode) this equals to the Feast SDK version
332+ modified by removing the "dev..." suffix and replacing "." with "_". For example, Feast SDK version
333+ "0.14.1.dev41+g1cbfa225.d20211103" would correspond to Docker image version "0_14_1". This way,
334+ Feast SDK will use an already existing Docker image built during the previous public release.
324335
325- This allows the version to be included in names for AWS resources.
326336 """
327- return get_version ().replace ("." , "_" ).replace ("+" , "_" )
337+ if flags_helper .is_test ():
338+ # Note: this should be in sync with https://github.com/feast-dev/feast/blob/6fbe01b6e9a444dc77ec3328a54376f4d9387664/.github/workflows/pr_integration_tests.yml#L41
339+ return (
340+ subprocess .check_output (
341+ ["git" , "rev-parse" , "HEAD" ], cwd = Path (__file__ ).resolve ().parent
342+ )
343+ .decode ()
344+ .strip ()
345+ )
346+ else :
347+ version = get_version ()
348+ if "dev" in version :
349+ version = version [: version .find ("dev" ) - 1 ].replace ("." , "_" )
350+ _logger .warning (
351+ "You are trying to use AWS Lambda feature server while Feast is in a development mode. "
352+ f"Feast will use a docker image version { version } derived from Feast SDK "
353+ f"version { get_version ()} . If you want to update the Feast SDK version, make "
354+ "sure to first fetch all new release tags from Github and then reinstall the library:\n "
355+ "> git fetch --all --tags\n "
356+ "> pip install -e sdk/python"
357+ )
358+ else :
359+ version = version .replace ("." , "_" )
360+ return version
328361
329362
330363class S3RegistryStore (RegistryStore ):
0 commit comments