Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion samcli/commands/package/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
kms_key_id_option,
metadata_option,
no_progressbar_option,
resolve_image_repos_option,
resolve_s3_option,
s3_bucket_option,
s3_prefix_option,
Expand Down Expand Up @@ -86,13 +87,14 @@ def resources_and_properties_help_string():
@use_json_option
@force_upload_option
@resolve_s3_option
@resolve_image_repos_option
@metadata_option
@signing_profiles_option
@no_progressbar_option
@common_options
@aws_creds_options
@save_params_option
@image_repository_validation(support_resolve_image_repos=False)
@image_repository_validation(support_resolve_image_repos=True)
@pass_context
@track_command
@check_newer_version
Expand All @@ -115,6 +117,7 @@ def cli(
metadata,
signing_profiles,
resolve_s3,
resolve_image_repos,
save_params,
config_file,
config_env,
Expand All @@ -140,6 +143,7 @@ def cli(
ctx.region,
ctx.profile,
resolve_s3,
resolve_image_repos,
) # pragma: no cover


Expand All @@ -159,17 +163,22 @@ def do_cli(
region,
profile,
resolve_s3,
resolve_image_repos,
):
"""
Implementation of the ``cli`` method
"""

from samcli.commands.package.exceptions import PackageResolveS3AndS3NotSetError
from samcli.commands.package.package_context import PackageContext

if resolve_s3:
s3_bucket = manage_stack(profile=profile, region=region)
print_managed_s3_bucket_info(s3_bucket)

if resolve_image_repos and not s3_bucket:
raise PackageResolveS3AndS3NotSetError()

with PackageContext(
template_file=template_file,
s3_bucket=s3_bucket,
Expand All @@ -185,5 +194,6 @@ def do_cli(
region=region,
profile=profile,
signing_profiles=signing_profiles,
resolve_image_repos=resolve_image_repos,
) as package_context:
package_context.run()
1 change: 1 addition & 0 deletions samcli/commands/package/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

INFRASTRUCTURE_OPTION_NAMES: List[str] = [
"s3_prefix",
"resolve_image_repos",
"image_repository",
"image_repositories",
"kms_key_id",
Expand Down
11 changes: 11 additions & 0 deletions samcli/commands/package/package_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import click

from samcli.commands.package.exceptions import PackageFailedError
from samcli.lib.bootstrap.companion_stack.companion_stack_manager import sync_ecr_stack
from samcli.lib.intrinsic_resolver.intrinsics_symbol_table import IntrinsicsSymbolTable
from samcli.lib.package.artifact_exporter import Template
from samcli.lib.package.code_signer import CodeSigner
Expand Down Expand Up @@ -71,6 +72,7 @@ def __init__(
parameter_overrides=None,
on_deploy=False,
signing_profiles=None,
resolve_image_repos=False,
):
self.template_file = template_file
self.s3_bucket = s3_bucket
Expand All @@ -89,6 +91,7 @@ def __init__(
self.code_signer = None
self.signing_profiles = signing_profiles
self.parameter_overrides = parameter_overrides
self.resolve_image_repos = resolve_image_repos
self._global_parameter_overrides = {IntrinsicsSymbolTable.AWS_REGION: region} if region else {}

def __enter__(self):
Expand All @@ -101,6 +104,14 @@ def run(self):
"""
Execute packaging based on the argument provided by customers and samconfig.toml.
"""
if self.resolve_image_repos:
template_basename = os.path.splitext(os.path.basename(self.template_file))[0]
stack_name = f"sam-app-{template_basename}"

self.image_repositories = sync_ecr_stack(
self.template_file, stack_name, self.region, self.s3_bucket, self.s3_prefix, self.image_repositories
)

stacks, _ = SamLocalStackProvider.get_stacks(
self.template_file,
global_parameter_overrides=self._global_parameter_overrides,
Expand Down
7 changes: 6 additions & 1 deletion schema/samcli.json
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@
"properties": {
"parameters": {
"title": "Parameters for the package command",
"description": "Available parameters for the package command:\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* output_template_file:\nThe path to the file where the command writes the output AWS CloudFormation template. If you don't specify a path, the command writes the template to the standard output.\n* s3_bucket:\nAWS S3 bucket where artifacts referenced in the template are uploaded.\n* image_repository:\nAWS ECR repository URI where artifacts referenced in the template are uploaded.\n* image_repositories:\nMapping of Function Logical ID to AWS ECR Repository URI.\n\nExample: Function_Logical_ID=ECR_Repo_Uri\nThis option can be specified multiple times.\n* s3_prefix:\nPrefix name that is added to the artifact's name when it is uploaded to the AWS S3 bucket.\n* kms_key_id:\nThe ID of an AWS KMS key that is used to encrypt artifacts that are at rest in the AWS S3 bucket.\n* use_json:\nIndicates whether to use JSON as the format for the output AWS CloudFormation template. YAML is used by default.\n* force_upload:\nIndicates whether to override existing files in the S3 bucket. Specify this flag to upload artifacts even if they match existing artifacts in the S3 bucket.\n* resolve_s3:\nAutomatically resolve AWS S3 bucket for non-guided deployments. Enabling this option will also create a managed default AWS S3 bucket for you. If one does not provide a --s3-bucket value, the managed bucket will be used. Do not use --guided with this option.\n* metadata:\nMap of metadata to attach to ALL the artifacts that are referenced in the template.\n* signing_profiles:\nA string that contains Code Sign configuration parameters as FunctionOrLayerNameToSign=SigningProfileName:SigningProfileOwner Since signing profile owner is optional, it could also be written as FunctionOrLayerNameToSign=SigningProfileName\n* no_progressbar:\nDoes not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
"description": "Available parameters for the package command:\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* output_template_file:\nThe path to the file where the command writes the output AWS CloudFormation template. If you don't specify a path, the command writes the template to the standard output.\n* s3_bucket:\nAWS S3 bucket where artifacts referenced in the template are uploaded.\n* image_repository:\nAWS ECR repository URI where artifacts referenced in the template are uploaded.\n* image_repositories:\nMapping of Function Logical ID to AWS ECR Repository URI.\n\nExample: Function_Logical_ID=ECR_Repo_Uri\nThis option can be specified multiple times.\n* s3_prefix:\nPrefix name that is added to the artifact's name when it is uploaded to the AWS S3 bucket.\n* kms_key_id:\nThe ID of an AWS KMS key that is used to encrypt artifacts that are at rest in the AWS S3 bucket.\n* use_json:\nIndicates whether to use JSON as the format for the output AWS CloudFormation template. YAML is used by default.\n* force_upload:\nIndicates whether to override existing files in the S3 bucket. Specify this flag to upload artifacts even if they match existing artifacts in the S3 bucket.\n* resolve_s3:\nAutomatically resolve AWS S3 bucket for non-guided deployments. Enabling this option will also create a managed default AWS S3 bucket for you. If one does not provide a --s3-bucket value, the managed bucket will be used. Do not use --guided with this option.\n* resolve_image_repos:\nAutomatically create and delete ECR repositories for image-based functions in non-guided deployments. A companion stack containing ECR repos for each function will be deployed along with the template stack. Automatically created image repositories will be deleted if the corresponding functions are removed.\n* metadata:\nMap of metadata to attach to ALL the artifacts that are referenced in the template.\n* signing_profiles:\nA string that contains Code Sign configuration parameters as FunctionOrLayerNameToSign=SigningProfileName:SigningProfileOwner Since signing profile owner is optional, it could also be written as FunctionOrLayerNameToSign=SigningProfileName\n* no_progressbar:\nDoes not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
"type": "object",
"properties": {
"template_file": {
Expand Down Expand Up @@ -1175,6 +1175,11 @@
"type": "boolean",
"description": "Automatically resolve AWS S3 bucket for non-guided deployments. Enabling this option will also create a managed default AWS S3 bucket for you. If one does not provide a --s3-bucket value, the managed bucket will be used. Do not use --guided with this option."
},
"resolve_image_repos": {
"title": "resolve_image_repos",
"type": "boolean",
"description": "Automatically create and delete ECR repositories for image-based functions in non-guided deployments. A companion stack containing ECR repos for each function will be deployed along with the template stack. Automatically created image repositories will be deleted if the corresponding functions are removed."
},
"metadata": {
"title": "metadata",
"type": "string",
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/package/package_integ_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def get_command_list(
image_repository=None,
image_repositories=None,
resolve_s3=False,
resolve_image_repos=False,
):
command_list = [get_sam_command(), "package"]

Expand Down Expand Up @@ -130,6 +131,8 @@ def get_command_list(
command_list = command_list + ["--image-repositories", str(image_repositories)]
if resolve_s3:
command_list = command_list + ["--resolve-s3"]
if resolve_image_repos:
command_list = command_list + ["--resolve-image-repos"]
return command_list

def _method_to_stack_name(self, method_name):
Expand Down
30 changes: 30 additions & 0 deletions tests/integration/package/test_package_command_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,33 @@ def test_package_with_nonloadable_image_archive(self, template_file):

self.assertEqual(1, process.returncode)
self.assertIn("unexpected EOF", process_stderr.decode("utf-8"))

@parameterized.expand(
[
"aws-serverless-function-image.yaml",
"aws-lambda-function-image.yaml",
]
)
def test_package_template_with_resolve_image_repos(self, template_file):

template_path = self.test_data_path.joinpath(template_file)
command_list = PackageIntegBase.get_command_list(
s3_bucket=self.bucket_name,
template=template_path,
resolve_image_repos=True,
)

process = Popen(command_list, stdout=PIPE, stderr=PIPE)
try:
stdout, stderr = process.communicate(timeout=TIMEOUT)
except TimeoutExpired:
process.kill()
raise

process_stdout = stdout.strip().decode("utf-8")
process_stderr = stderr.strip().decode("utf-8")
self.assertEqual(0, process.returncode, f"Command failed. Stderr: {process_stderr}")
# Verify ECR repository URI is in the output (auto-created repository)
# The output should contain an ECR repository URI pattern
ecr_uri_pattern = r"\d+\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com/"
self.assertRegex(process_stdout, ecr_uri_pattern, "Expected ECR repository URI in packaged template")
29 changes: 29 additions & 0 deletions tests/unit/commands/package/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import patch, Mock

from samcli.commands.package.command import do_cli
from samcli.commands.package.exceptions import PackageResolveS3AndS3NotSetError


class TestPackageCliCommand(TestCase):
Expand All @@ -20,6 +21,7 @@ def setUp(self):
self.region = None
self.profile = None
self.resolve_s3 = False
self.resolve_image_repos = False
self.signing_profiles = {"MyFunction": {"profile_name": "ProfileName", "profile_owner": "Profile Owner"}}

@patch("samcli.commands.package.command.click")
Expand All @@ -43,6 +45,7 @@ def test_all_args(self, package_command_context, click_mock):
region=self.region,
profile=self.profile,
resolve_s3=self.resolve_s3,
resolve_image_repos=self.resolve_image_repos,
signing_profiles=self.signing_profiles,
)

Expand All @@ -61,6 +64,7 @@ def test_all_args(self, package_command_context, click_mock):
region=self.region,
profile=self.profile,
signing_profiles=self.signing_profiles,
resolve_image_repos=self.resolve_image_repos,
)

context_mock.run.assert_called_with()
Expand Down Expand Up @@ -89,6 +93,7 @@ def test_all_args_resolve_s3(self, mock_managed_stack, package_command_context,
region=self.region,
profile=self.profile,
resolve_s3=True,
resolve_image_repos=False,
signing_profiles=self.signing_profiles,
)

Expand All @@ -107,7 +112,31 @@ def test_all_args_resolve_s3(self, mock_managed_stack, package_command_context,
region=self.region,
profile=self.profile,
signing_profiles=self.signing_profiles,
resolve_image_repos=False,
)

context_mock.run.assert_called_with()
self.assertEqual(context_mock.run.call_count, 1)

@patch("samcli.commands.package.command.click")
@patch("samcli.commands.package.package_context.PackageContext")
def test_resolve_image_repos_without_s3_bucket_raises_error(self, package_command_context, click_mock):
with self.assertRaises(PackageResolveS3AndS3NotSetError):
do_cli(
template_file=self.template_file,
s3_bucket=None,
s3_prefix=self.s3_prefix,
image_repository=None,
image_repositories=None,
kms_key_id=self.kms_key_id,
output_template_file=self.output_template_file,
use_json=self.use_json,
force_upload=self.force_upload,
no_progressbar=self.no_progressbar,
metadata=self.metadata,
region=self.region,
profile=self.profile,
resolve_s3=False,
resolve_image_repos=True,
signing_profiles=self.signing_profiles,
)
Loading