awscli-local icon indicating copy to clipboard operation
awscli-local copied to clipboard

:whale: :tada: dockerized version of awscli-local

Open marcellodesales opened this issue 8 months ago • 4 comments

🎉 New Version

  • [x] Add support for dockerized execution using Alpine linux
  • [x] Image should be reusable by anyone in need of building AWS services

🏗️ Build

  • I have pushed a version of the image to my personal docker registry repo.
$ docker compose build && docker compose push
[+] Building 4.8s (17/17) FINISHED                                                                                                                                   docker-container:kind_khayyam
 => [awslocal internal] load build definition from Dockerfile                                                                                                                                 0.0s
 => => transferring dockerfile: 998B                                                                                                                                                          0.0s
 => WARN: InvalidDefaultArgInFrom: Default value for ARG ${BUILDER_IMAGE} results in empty or invalid base image name (line 3)                                                                0.0s
 => WARN: InvalidDefaultArgInFrom: Default value for ARG ${BUILDER_IMAGE} results in empty or invalid base image name (line 16)                                                               0.0s
 => [awslocal internal] load metadata for docker.io/library/python:3.13.2-alpine3.21                                                                                                          3.8s
 => [awslocal auth] library/python:pull token for registry-1.docker.io                                                                                                                        0.0s
 => [awslocal internal] load .dockerignore                                                                                                                                                    0.0s
 => => transferring context: 2B                                                                                                                                                               0.0s
 => [awslocal builder 1/6] FROM docker.io/library/python:3.13.2-alpine3.21@sha256:323a717dc4a010fee21e3f1aac738ee10bb485de4e7593ce242b36ee48d6b352                                            0.0s
 => => resolve docker.io/library/python:3.13.2-alpine3.21@sha256:323a717dc4a010fee21e3f1aac738ee10bb485de4e7593ce242b36ee48d6b352                                                             0.0s
 => [awslocal internal] load build context                                                                                                                                                    0.0s
 => => transferring context: 9.57kB                                                                                                                                                           0.0s
 => CACHED [awslocal service 2/5] RUN apk update && apk add aws-cli==2.22.10-r0 bash zip curl                                                                                                 0.0s
 => CACHED [awslocal service 3/5] WORKDIR /app/site-packages                                                                                                                                  0.0s
 => CACHED [awslocal builder 2/6] WORKDIR /usr/src/app                                                                                                                                        0.0s
 => CACHED [awslocal builder 3/6] RUN python3 -m venv /venv                                                                                                                                   0.0s
 => CACHED [awslocal builder 4/6] RUN pip install --upgrade pip                                                                                                                               0.0s
 => CACHED [awslocal builder 5/6] COPY requirements.txt requirements.txt                                                                                                                      0.0s
 => CACHED [awslocal builder 6/6] RUN pip install --no-cache-dir -r requirements.txt                                                                                                          0.0s
 => CACHED [awslocal service 4/5] COPY --from=builder /venv /venv                                                                                                                             0.0s
 => [awslocal service 5/5] COPY bin/awslocal-docker /venv/bin/awslocal                                                                                                                        0.1s
 => [awslocal] exporting to docker image format                                                                                                                                               0.8s
 => => exporting layers                                                                                                                                                                       0.0s
 => => exporting manifest sha256:d9c4ae2efb3cb07909e31189afdf2c449558d56e31494fa5e302ca1e65a5478a                                                                                             0.0s
 => => exporting config sha256:c1802bab7ea93cdafe600f4603e84c67b1e248fb95c91dcf6149c286e98f438e                                                                                               0.0s
 => => sending tarball                                                                                                                                                                        0.7s
 => [awslocal] importing to docker                                                                                                                                                            0.0s
 => => loading layer 8aff40f8e9b8 3.55kB / 3.55kB                                                                                                                                             0.0s
[+] Pushing 8/8
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 8aff40f8e9b8 Pushed                                                                                                                         6.1s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 8fae2133b79d Layer already exists                                                                                                           2.9s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 56c23dc9a76d Layer already exists                                                                                                           2.8s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: da53f50a8a75 Layer already exists                                                                                                           2.9s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 336e6a290569 Layer already exists                                                                                                           2.8s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 53d9c097c68d Layer already exists                                                                                                           4.6s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 052b772c7a04 Layer already exists                                                                                                           4.6s 
 ✔ Pushing marcellodesales/awscli-local:0.22.0.7: 08000c18d16d Layer already exists                                                                                                           4.6s 
(.venv) 

🏃 Running

  • Entrypoint already executes awslocal
$ docker compose run awslocal

usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help

aws: error: the following arguments are required: command

✅ Testing

  • Created a lambda function as a hello world
  • Start localstack with a network bridged

🔧 Localstack Deployment

services:

  localstack:
    container_name: localstack
    image: dockerhub.docker.artifactory.viasat.com/localstack/localstack:4.2.0
    ports:
      - "4566:4566"            # LocalStack main endpoint
      - "4571:4571"            # Internal communication
    user: root
    environment:
      - SERVICES=lambda,s3,cloudwatch,iam,events
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
      - HOST_TMP_FOLDER=/tmp/localstack
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/var/lib/localstack
      - ./s3-hello-world-lambda-function:/lambda-functions
    networks:
      - local-aws

networks:
  local-aws:
    driver: bridge
    name: local-aws

🔧 AWS Lambda Deploy example

  • Locally create a deploy and run compose service that connects to localstack through the bridge network
  • Create a .env properties with the required inputs
LAMBDA_LOCAL_DIR=s3-hello-world-lambda-function
LAMBDA_FUNCTION_NAME=s3-hello-world
LAMBDA_TRIGGER_NAME=daily-lambda-trigger
LAMBDA_HANDLER_FUNCTION=lambda_function.handler
LAMBDA_PYTHON_VERSION=python3.11
LOCALSTACK_HOST=localstack:4566
  • Then, use the deploy
networks:

  #####
  ##### First, docker compose up -d is required to get the local aws network created
  ##### See the healthcheck section below
  #####
  local-aws:
    external: true

services:

  s3-hello-world:
    image: dockerhub.docker.artifactory.viasat.com/marcellodesales/awscli-local:0.22.0.7
    working_dir: /viasat/platform/vionix/aws
    volumes:
      - ${CURRENT_DIR:-.}:/viasat/platform/vionix/aws
    networks:
      - local-aws
#    env_file: ./s3-hello-world-lambda-function/local.env
    environment:
      LAMBDA_LOCAL_DIR: ${LAMBDA_LOCAL_DIR}
      LAMBDA_FUNCTION_NAME: ${LAMBDA_FUNCTION_NAME}
      LAMBDA_TRIGGER_NAME: ${LAMBDA_TRIGGER_NAME}
      LAMBDA_HANDLER_FUNCTION: ${LAMBDA_HANDLER_FUNCTION}
      LAMBDA_PYTHON_VERSION: ${LAMBDA_PYTHON_VERSION}
      LOCALSTACK_HOST: ${LOCALSTACK_HOST}
    entrypoint: ["/bin/bash"]
    command:
      - -c
      - |
        env
        echo "Checking if localstack is up and running... curl -I http://localstack:4566/_localstack/health"
        curl -I http://localstack:4566/_localstack/health || exit 1

        echo "Create AWS Role from policies/lambda-role-policy.json"
        awslocal iam create-role \
            --role-name lambda-executor \
            --assume-role-policy-document file://policies/lambda-role-policy.json

        echo "Create the lambda zip file ${LAMBDA_LOCAL_DIR}/function.zip"
        cd ${LAMBDA_LOCAL_DIR}
        zip -r function.zip .

        set -x
        awslocal lambda delete-function --function-name ${LAMBDA_FUNCTION_NAME} 
        
        echo "Creating the lambda function from ${LAMBDA_LOCAL_DIR}/function.zip"
        awslocal lambda create-function \
            --function-name ${LAMBDA_FUNCTION_NAME} \
            --runtime ${LAMBDA_PYTHON_VERSION} \
            --role arn:aws:iam::000000000000:role/lambda-executor \
            --handler ${LAMBDA_HANDLER_FUNCTION} \
            --zip-file fileb://function.zip

        echo "Creating a cloudwatch event trigger ${LAMBDA_TRIGGER_NAME} schedule ${LAMBDA_SCHEDULE_EXPRESSION}"
        # Optional: Create a CloudWatch event trigger
        awslocal events put-rule \
            --name "${LAMBDA_TRIGGER_NAME}" \
            --schedule-expression "rate(1 day)"
        
        echo "Add an event target for the function for the CloudWatch example"
        awslocal events put-targets \
            --rule "${LAMBDA_TRIGGER_NAME}" \
            --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:000000000000:function:${LAMBDA_FUNCTION_NAME}"

        echo "Invoke Lambda function locally"
        awslocal lambda invoke \
            --function-name ${LAMBDA_FUNCTION_NAME} \
            --payload '{"key": "value"}' \
            output.json
        echo "Show output"
        cat output.json
  • Then, execute the docker-compose-deploy to verify
$ docker compose -f docker-compose-deploy.yaml run s3-hello-world
WARN[0000] The "LAMBDA_SCHEDULE_EXPRESSION" variable is not set. Defaulting to a blank string. 
WARN[0000] Found orphan containers ([localstack vionix-platform-aws-localstack-deploy-s3-hello-world-run-4104d6790002 vionix-platform-aws-localstack-deploy-s3-hello-world-run-5e0c801fa498 vionix-platform-aws-localstack-deploy-s3-hello-world-run-ce3ab7aa1306 vionix-platform-aws-localstack-deploy-s3-hello-world-run-0ac955708867 vionix-platform-aws-localstack-deploy-s3-hello-world-run-42c0598bb72d vionix-platform-aws-localstack-deploy-s3-hello-world-run-263653088c3d vionix-platform-aws-localstack-deploy-s3-hello-world-run-943a0e66b789 vionix-platform-aws-localstack-deploy-s3-hello-world-run-c89c8e03e05b]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up. 
[+] Running 1/1
 ✔ s3-hello-world Pulled                                                                                                                                                                      5.2s 
PYTHON_SHA256=d984bcc57cd67caab26f7def42e523b1c015bbc5dc07836cf4f0b63fa159eb56
HOSTNAME=f4de90798643
PYTHON_VERSION=3.13.2
LAMBDA_LOCAL_DIR=s3-hello-world-lambda-function
LAMBDA_TRIGGER_NAME=daily-lambda-trigger
LAMBDA_FUNCTION_NAME=s3-hello-world
PWD=/viasat/platform/vionix/aws
HOME=/root
LAMBDA_HANDLER_FUNCTION=lambda_function.handler
GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305
LAMBDA_PYTHON_VERSION=python3.11
TERM=xterm
SHLVL=1
PATH=/usr/bin:/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LOCALSTACK_HOST=localstack:4566
_=/usr/bin/env
Checking if localstack is up and running... curl -I http://localstack:4566/_localstack/health
HTTP/1.1 200 OK
Server: TwistedWeb/24.3.0
Date: Wed, 26 Mar 2025 13:50:02 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 2

Create AWS Role from policies/lambda-role-policy.json

An error occurred (EntityAlreadyExists) when calling the CreateRole operation: Role with name lambda-executor already exists.
Create the lambda zip file s3-hello-world-lambda-function/function.zip
updating: requirements.txt (stored 0%)
updating: Dockerfile (deflated 35%)
updating: lambda_function.py (deflated 41%)
updating: output.json (deflated 2%)
+ awslocal lambda delete-function --function-name s3-hello-world
+ echo 'Creating the lambda function from s3-hello-world-lambda-function/function.zip'
Creating the lambda function from s3-hello-world-lambda-function/function.zip
+ awslocal lambda create-function --function-name s3-hello-world --runtime python3.11 --role arn:aws:iam::000000000000:role/lambda-executor --handler lambda_function.handler --zip-file fileb://function.zip
{
    "FunctionName": "s3-hello-world",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:s3-hello-world",
    "Runtime": "python3.11",
    "Role": "arn:aws:iam::000000000000:role/lambda-executor",
    "Handler": "lambda_function.handler",
    "CodeSize": 1324,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2025-03-26T13:50:11.657043+0000",
    "CodeSha256": "BJ8ec5spa6q3gyk8i0Hg28f067v759u26XIzaiUKfl4=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "14d05e41-7e0d-41ce-9c6a-98988e55c20e",
    "State": "Pending",
    "StateReason": "The function is being created.",
    "StateReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ],
    "EphemeralStorage": {
        "Size": 512
    },
    "SnapStart": {
        "ApplyOn": "None",
        "OptimizationStatus": "Off"
    },
    "RuntimeVersionConfig": {
        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:8eeff65f6809a3ce81507fe733fe09b835899b99481ba22fd75b5a7338290ec1"
    },
    "LoggingConfig": {
        "LogFormat": "Text",
        "LogGroup": "/aws/lambda/s3-hello-world"
    }
}
+ echo 'Creating a cloudwatch event trigger daily-lambda-trigger schedule '
Creating a cloudwatch event trigger daily-lambda-trigger schedule 
+ awslocal events put-rule --name daily-lambda-trigger --schedule-expression 'rate(1 day)'
{
    "RuleArn": "arn:aws:events:us-east-1:000000000000:rule/daily-lambda-trigger"
}
+ echo 'Add an event target for the function for the CloudWatch example'
Add an event target for the function for the CloudWatch example
+ awslocal events put-targets --rule daily-lambda-trigger --targets Id=1,Arn=arn:aws:lambda:us-east-1:000000000000:function:s3-hello-world
{
    "FailedEntryCount": 0,
    "FailedEntries": []
}
+ echo 'Invoke Lambda function locally'
Invoke Lambda function locally
+ awslocal lambda invoke --function-name s3-hello-world --payload '{"key": "value"}' output.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
+ echo 'Show output'
Show output
+ cat output.json
{"statusCode": 200, "body": "Hello from S3 Lambda example called with {'key': 'value'}"}%                                                                                                  (.venv) 

marcellodesales avatar Mar 25 '25 23:03 marcellodesales

The other thing I would say is that it might be easier to just call awslocal inside the existing localstack image, rather than creating a new image that just containers awslocal.

mattviasat avatar Apr 01 '25 18:04 mattviasat

The other thing I would say is that it might be easier to just call awslocal inside the existing localstack image, rather than creating a new image that just containers awslocal.

Thank you for all your suggestions... I just need a smaller image to account for SBOM, security, etc... This is specific for a cloudNative platform and I don't need to pull the entire localstack image for the use cases that I have...

marcellodesales avatar Apr 02 '25 21:04 marcellodesales

Thank you for all your suggestions... I just need a smaller image to account for SBOM, security, etc... This is specific for a cloudNative platform and I don't need to pull the entire localstack image for the use cases that I have...

I think there is probably a simpler way that results in a smaller image. You could use python/python3.x image and just

python3 -m venv /venv/
/venv/bin/python3 -m pip install --no-cache-dir awscli-local
/venv/bin/awslocal help

mattviasat avatar Apr 07 '25 17:04 mattviasat

The other thing I would say is that it might be easier to just call awslocal inside the existing localstack image, rather than creating a new image that just containers awslocal.

Thank you for all your suggestions... I just need a smaller image to account for SBOM, security, etc... This is specific for a cloudNative platform and I don't need to pull the entire localstack image for the use cases that I have...

If you are running localstack in one docker container, than i believe you should already have the image stored locally. If you need to interact with an existing localstack container you can either:

  • exec into the running container to run a command. awslocal is installed in localstack/localstack and you know it will be the right version.
  • Use init hooks to create as stack when the containers comes up https://docs.localstack.cloud/references/init-hooks/#lifecycle-stages-and-hooks
  • or if you insist on running a different container, docker run -e awslocal localstack/localstack <my command here> you would need to set up the ports and allow it to talk to the original localstack/localstack container, but it should work.

mattviasat avatar May 20 '25 16:05 mattviasat