def do_cli( ctx, no_interactive, location, runtime, dependency_manager, output_dir, name, app_template, no_input, extra_context, auto_clone=True, ): from samcli.commands.init.init_generator import do_generate from samcli.commands.init.init_templates import InitTemplates from samcli.commands.init.interactive_init_flow import do_interactive # check for mutually exclusive parameters if location and app_template: msg = """ You must not provide both the --location and --app-template parameters. You can run 'sam init' without any options for an interactive initialization flow, or you can provide one of the following required parameter combinations: --name and --runtime and --app-template and --dependency-manager --location """ raise UserException(msg) # check for required parameters if location or (name and runtime and dependency_manager and app_template): # need to turn app_template into a location before we generate if app_template: templates = InitTemplates(no_interactive, auto_clone) location = templates.location_from_app_template( runtime, dependency_manager, app_template) no_input = True default_context = {"project_name": name, "runtime": runtime} if extra_context is None: extra_context = default_context else: extra_context = _merge_extra_context(default_context, extra_context) if not output_dir: output_dir = "." do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context) elif no_interactive: error_msg = """ ERROR: Missing required parameters, with --no-interactive set. Must provide one of the following required parameter combinations: --name and --runtime and --dependency-manager and --app-template --location You can also re-run without the --no-interactive flag to be prompted for required values. """ raise UserException(error_msg) else: # proceed to interactive state machine, which will call do_generate do_interactive(location, runtime, dependency_manager, output_dir, name, app_template, no_input)
def do_cli( ctx, function_identifier, template, event, no_event, env_vars, debug_port, # pylint: disable=R0914 debug_args, debugger_path, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ LOG.debug("local invoke command is called") if no_event and event != STDIN_FILE_NAME: # Do not know what the user wants. no_event and event both passed in. raise UserException( "no_event and event cannot be used together. Please provide only one." ) if no_event: event_data = "{}" else: event_data = _get_event(event) # Pass all inputs to setup necessary context to invoke function locally. # Handler exception raised by the processor for invalid args and print errors try: with InvokeContext(template_file=template, function_identifier=function_identifier, env_vars_file=env_vars, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, aws_profile=profile, debug_port=debug_port, debug_args=debug_args, debugger_path=debugger_path, aws_region=region) as context: # Invoke the function context.local_lambda_runner.invoke(context.function_name, event=event_data, stdout=context.stdout, stderr=context.stderr) except FunctionNotFound: raise UserException( "Function {} not found in template".format(function_identifier)) except InvalidSamDocumentException as ex: raise UserException(str(ex))
def _check_sanity_of_stack(stack): tags = stack.get("Tags", None) outputs = stack.get("Outputs", None) # For some edge cases, stack could be in invalid state # Check if stack information contains the Tags and Outputs as we expected if tags is None or outputs is None: stack_state = stack.get("StackName", None) msg = ( f"Stack {SAM_CLI_STACK_NAME} is missing Tags and/or Outputs information and therefore not in a " f"healthy state (Current state:{stack_state}). Failing as the stack was likely not created " f"by the AWS SAM CLI") raise UserException(msg) # Sanity check for non-none stack? Sanity check for tag? try: sam_cli_tag = next(t for t in tags if t["Key"] == "ManagedStackSource") if not sam_cli_tag["Value"] == "AwsSamCli": msg = ( "Stack " + SAM_CLI_STACK_NAME + " ManagedStackSource tag shows " + sam_cli_tag["Value"] + " which does not match the AWS SAM CLI generated tag value of AwsSamCli. " "Failing as the stack was likely not created by the AWS SAM CLI." ) raise UserException(msg) except StopIteration as ex: msg = ( "Stack " + SAM_CLI_STACK_NAME + " exists, but the ManagedStackSource tag is missing. " "Failing as the stack was likely not created by the AWS SAM CLI.") raise UserException(msg) from ex
def test_must_record_wrapped_user_exception(self, ContextMock): ContextMock.get_current_context.return_value = self.context_mock expected_exception = UserException("Something went wrong", wrapped_from="CustomException") expected_exception.exit_code = 1235 def real_fn(): raise expected_exception with self.assertRaises(UserException) as context: track_command(real_fn)() self.assertEqual( context.exception, expected_exception, "Must re-raise the original exception object " "without modification", ) expected_attrs = _ignore_common_attributes({ "exitReason": "CustomException", "exitCode": 1235 }) args, _ = self.telemetry_instance.emit.call_args_list[0] metric = args[0] assert metric.get_metric_name() == "commandRun" self.assertGreaterEqual(metric.get_data().items(), expected_attrs.items())
def do_cli(ctx, template, semantic_version): """Publish the application based on command line inputs.""" try: template_data = get_template_data(template) except ValueError as ex: click.secho("Publish Failed", fg="red") raise UserException(str(ex)) # Override SemanticVersion in template metadata when provided in command input if semantic_version and SERVERLESS_REPO_APPLICATION in template_data.get( METADATA, {}): template_data.get(METADATA).get( SERVERLESS_REPO_APPLICATION)[SEMANTIC_VERSION] = semantic_version try: publish_output = publish_application(template_data) click.secho("Publish Succeeded", fg="green") click.secho(_gen_success_message(publish_output)) except InvalidS3UriError: click.secho("Publish Failed", fg="red") raise UserException( "Your SAM template contains invalid S3 URIs. Please make sure that you have uploaded application " "artifacts to S3 by packaging the template. See more details in {}" .format(SAM_PACKAGE_DOC)) except ServerlessRepoError as ex: click.secho("Publish Failed", fg="red") LOG.debug("Failed to publish application to serverlessrepo", exc_info=True) error_msg = "{}\nPlease follow the instructions in {}".format( str(ex), SAM_PUBLISH_DOC) raise UserException(error_msg) application_id = publish_output.get("application_id") _print_console_link(ctx.region, application_id)
def do_cli(ctx, host, port, static_dir, template, env_vars, debug_port, debug_args, # pylint: disable=R0914 debugger_path, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ LOG.debug("local start-api command is called") # Pass all inputs to setup necessary context to invoke function locally. # Handler exception raised by the processor for invalid args and print errors try: with InvokeContext(template_file=template, function_identifier=None, # Don't scope to one particular function env_vars_file=env_vars, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, aws_profile=profile, debug_port=debug_port, debug_args=debug_args, debugger_path=debugger_path, aws_region=region) as invoke_context: service = LocalApiService(lambda_invoke_context=invoke_context, port=port, host=host, static_dir=static_dir) service.start() except NoApisDefined: raise UserException("Template does not have any APIs connected to Lambda functions") except InvalidSamDocumentException as ex: raise UserException(str(ex))
def _create_or_get_stack(cloudformation_client): try: stack = None try: ds_resp = cloudformation_client.describe_stacks( StackName=SAM_CLI_STACK_NAME) stacks = ds_resp["Stacks"] stack = stacks[0] click.echo( "\n\tLooking for resources needed for deployment: Found!") except ClientError: click.echo( "\n\tLooking for resources needed for deployment: Not found.") stack = _create_stack( cloudformation_client ) # exceptions are not captured from subcommands # Sanity check for non-none stack? Sanity check for tag? tags = stack["Tags"] try: sam_cli_tag = next(t for t in tags if t["Key"] == "ManagedStackSource") if not sam_cli_tag["Value"] == "AwsSamCli": msg = ( "Stack " + SAM_CLI_STACK_NAME + " ManagedStackSource tag shows " + sam_cli_tag["Value"] + " which does not match the AWS SAM CLI generated tag value of AwsSamCli. " "Failing as the stack was likely not created by the AWS SAM CLI." ) raise UserException(msg) except StopIteration: msg = ( "Stack " + SAM_CLI_STACK_NAME + " exists, but the ManagedStackSource tag is missing. " "Failing as the stack was likely not created by the AWS SAM CLI." ) raise UserException(msg) outputs = stack["Outputs"] try: bucket_name = next( o for o in outputs if o["OutputKey"] == "SourceBucket")["OutputValue"] except StopIteration: msg = ( "Stack " + SAM_CLI_STACK_NAME + " exists, but is missing the managed source bucket key. " "Failing as this stack was likely not created by the AWS SAM CLI." ) raise UserException(msg) # This bucket name is what we would write to a config file return bucket_name except (ClientError, BotoCoreError) as ex: LOG.debug("Failed to create managed resources", exc_info=ex) raise ManagedStackError(str(ex))
def location_from_app_template(self, runtime, dependency_manager, app_template): options = self.init_options(runtime, dependency_manager) try: template = next(item for item in options if self._check_app_template(item, app_template)) if template.get("init_location") is not None: return template["init_location"] if template.get("directory") is not None: return os.path.join(self.repo_path, template["directory"]) raise UserException("Invalid template. This should not be possible, please raise an issue.") except StopIteration: msg = "Can't find application template " + app_template + " - check valid values in interactive init." raise UserException(msg)
def prompt_for_location(self, runtime, dependency_manager): options = self.init_options(runtime, dependency_manager) if len(options) == 1: template_md = options[0] else: choices = list(map(str, range(1, len(options) + 1))) choice_num = 1 click.echo("\nAWS quick start application templates:") for o in options: if o.get("displayName") is not None: msg = "\t" + str(choice_num) + " - " + o.get("displayName") click.echo(msg) else: msg = ("\t" + str(choice_num) + " - Default Template for runtime " + runtime + " with dependency manager " + dependency_manager) click.echo(msg) choice_num = choice_num + 1 choice = click.prompt("Template selection", type=click.Choice(choices), show_choices=False) template_md = options[int(choice) - 1] # zero index if template_md.get("init_location") is not None: return (template_md["init_location"], "hello-world") if template_md.get("directory") is not None: return (os.path.join(self.repo_path, template_md["directory"]), template_md["appTemplate"]) raise UserException( "Invalid template. This should not be possible, please raise an issue." )
def do_cli(args, template_file, stack_name): args = args + ("--stack-name", stack_name) try: execute_command("deploy", args, template_file=template_file) except OSError as ex: raise UserException(str(ex))
def do_cli(args, template_file, s3_bucket): args = args + ('--s3-bucket', s3_bucket) try: execute_command("package", args, template_file) except OSError as ex: raise UserException(str(ex))
def do_cli(ctx, template): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ from samtranslator.translator.managed_policy_translator import ManagedPolicyLoader from samcli.commands.exceptions import UserException from samcli.commands.local.cli_common.user_exceptions import InvalidSamTemplateException from .lib.exceptions import InvalidSamDocumentException from .lib.sam_template_validator import SamTemplateValidator sam_template = _read_sam_file(template) iam_client = boto3.client("iam") validator = SamTemplateValidator(sam_template, ManagedPolicyLoader(iam_client)) try: validator.is_valid() except InvalidSamDocumentException as e: click.secho( "Template provided at '{}' was invalid SAM Template.".format( template), bg="red") raise InvalidSamTemplateException(str(e)) except NoCredentialsError as e: raise UserException( "AWS Credentials are required. Please configure your credentials.", wrapped_from=e.__class__.__name__) click.secho("{} is a valid SAM Template".format(template), fg="green")
def _parse_time(time_str, property_name): """ Parse the time from the given string, convert to UTC, and return the datetime object Parameters ---------- time_str : str The time to parse property_name : str Name of the property where this time came from. Used in the exception raised if time is not parseable Returns ------- datetime.datetime Parsed datetime object Raises ------ samcli.commands.exceptions.UserException If the string cannot be parsed as a timestamp """ if not time_str: return None parsed = parse_date(time_str) if not parsed: raise UserException( "Unable to parse the time provided by '{}'".format( property_name)) return to_utc(parsed)
def prompt_for_location(self, runtime, dependency_manager): options = self.init_options(runtime, dependency_manager) choices = map(str, range(1, len(options) + 1)) choice_num = 1 for o in options: if o.get("displayName") is not None: msg = str(choice_num) + " - " + o.get("displayName") click.echo(msg) else: msg = (str(choice_num) + " - Default Template for runtime " + runtime + " with dependency manager " + dependency_manager) click.echo(msg) choice_num = choice_num + 1 choice = click.prompt("Template Selection", type=click.Choice(choices), show_choices=False) template_md = options[int(choice) - 1] # zero index if template_md.get("init_location") is not None: return template_md["init_location"] if template_md.get("directory") is not None: return os.path.join(self.repo_path, template_md["directory"]) raise UserException( "Invalid template. This should not be possible, please raise an issue." )
def do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context): try: generate_project(location, runtime, dependency_manager, output_dir, name, no_input, extra_context) except (GenerateProjectFailedError, ArbitraryProjectDownloadFailed) as e: raise UserException(str(e))
def _merge_extra_context(default_context, extra_context): try: extra_context_dict = json.loads(extra_context) except JSONDecodeError: raise UserException( "Parse error reading the --extra-content parameter. The value of this parameter must be valid JSON." ) return {**extra_context_dict, **default_context}
def do_cli( template, # pylint: disable=too-many-locals base_dir, build_dir, clean, use_container, manifest_path, docker_network, skip_pull_image, parameter_overrides): """ Implementation of the ``cli`` method """ LOG.debug("'build' command is called") if use_container: LOG.info("Starting Build inside a container") with BuildContext(template, base_dir, build_dir, clean=clean, manifest_path=manifest_path, use_container=use_container, parameter_overrides=parameter_overrides, docker_network=docker_network, skip_pull_image=skip_pull_image) as ctx: builder = ApplicationBuilder( ctx.function_provider, ctx.build_dir, ctx.base_dir, manifest_path_override=ctx.manifest_path_override, container_manager=ctx.container_manager) try: artifacts = builder.build() modified_template = builder.update_template( ctx.template_dict, ctx.original_template_path, artifacts) move_template(ctx.original_template_path, ctx.output_template_path, modified_template) click.secho("\nBuild Succeeded", fg="green") msg = gen_success_msg( os.path.relpath(ctx.build_dir), os.path.relpath(ctx.output_template_path), os.path.abspath( ctx.build_dir) == os.path.abspath(DEFAULT_BUILD_DIR)) click.secho(msg, fg="yellow") except (UnsupportedRuntimeException, BuildError, UnsupportedBuilderLibraryVersionError) as ex: click.secho("Build Failed", fg="red") raise UserException(str(ex))
def do_cli(ctx, location, runtime, dependency_manager, output_dir, name, no_input): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ from samcli.commands.exceptions import UserException from samcli.local.init import generate_project from samcli.local.init.exceptions import GenerateProjectFailedError LOG.debug("Init command") click.secho("[+] Initializing project structure...", fg="green") no_build_msg = """ Project generated: {output_dir}/{name} Steps you can take next within the project folder =================================================== [*] Invoke Function: sam local invoke HelloWorldFunction --event event.json [*] Start API Gateway locally: sam local start-api """.format( output_dir=output_dir, name=name ) build_msg = """ Project generated: {output_dir}/{name} Steps you can take next within the project folder =================================================== [*] Install dependencies [*] Invoke Function: sam local invoke HelloWorldFunction --event event.json [*] Start API Gateway locally: sam local start-api """.format( output_dir=output_dir, name=name ) no_build_step_required = ( "python", "python3.7", "python3.6", "python2.7", "nodejs", "nodejs4.3", "nodejs6.10", "nodejs8.10", "nodejs10.x", "ruby2.5", ) next_step_msg = no_build_msg if runtime in no_build_step_required else build_msg try: generate_project(location, runtime, dependency_manager, output_dir, name, no_input) if not location: click.secho(next_step_msg, bold=True) click.secho("Read {name}/README.md for further instructions\n".format(name=name), bold=True) click.secho("[*] Project initialization is now complete", fg="green") except GenerateProjectFailedError as e: raise UserException(str(e))
def _init_options_from_bundle(self, runtime, dependency_manager): for mapping in list(itertools.chain(*(RUNTIME_DEP_TEMPLATE_MAPPING.values()))): if runtime in mapping["runtimes"] or any([r.startswith(runtime) for r in mapping["runtimes"]]): if not dependency_manager or dependency_manager == mapping["dependency_manager"]: mapping["appTemplate"] = "hello-world" # when bundled, use this default template name return [mapping] msg = "Lambda Runtime {} and dependency manager {} does not have an available initialization template.".format( runtime, dependency_manager ) raise UserException(msg)
def test_must_record_user_exception(self, ContextMock): ContextMock.get_current_context.return_value = self.context_mock expected_exception = UserException("Something went wrong") expected_exception.exit_code = 1235 def real_fn(): raise expected_exception with self.assertRaises(UserException) as context: track_command(real_fn)() self.assertEquals(context.exception, expected_exception, "Must re-raise the original exception object " "without modification") expected_attrs = _cmd_run_attrs({ "exitReason": "UserException", "exitCode": 1235 }) self.telemetry_instance.emit.assert_has_calls([ call("commandRun", expected_attrs) ])
def do_cli(ctx, template): """Publish the application based on command line inputs.""" try: template_data = get_template_data(template) except ValueError as ex: click.secho("Publish Failed", fg='red') raise UserException(str(ex)) try: publish_output = publish_application(template_data) click.secho("Publish Succeeded", fg="green") click.secho(_gen_success_message(publish_output), fg="yellow") except ServerlessRepoError as ex: click.secho("Publish Failed", fg='red') raise UserException(str(ex)) except ClientError as ex: click.secho("Publish Failed", fg='red') raise _wrap_s3_uri_exception(ex) application_id = publish_output.get('application_id') _print_console_link(ctx.region, application_id)
def _get_resource_id_from_stack(cfn_client, stack_name, logical_id): """ Given the LogicalID of a resource, call AWS CloudFormation to get physical ID of the resource within the specified stack. Parameters ---------- cfn_client : boto3.session.Session.client CloudFormation client provided by AWS SDK stack_name : str Name of the stack to query logical_id : str LogicalId of the resource Returns ------- str Physical ID of the resource Raises ------ samcli.commands.exceptions.UserException If the stack or resource does not exist """ LOG.debug( "Getting resource's PhysicalId from AWS CloudFormation stack. StackName=%s, LogicalId=%s", stack_name, logical_id, ) try: response = cfn_client.describe_stack_resource( StackName=stack_name, LogicalResourceId=logical_id) LOG.debug("Response from AWS CloudFormation %s", response) return response["StackResourceDetail"]["PhysicalResourceId"] except botocore.exceptions.ClientError as ex: LOG.debug( "Unable to fetch resource name from CloudFormation Stack: " "StackName=%s, ResourceLogicalId=%s, Response=%s", stack_name, logical_id, ex.response, ) # The exception message already has a well formatted error message that we can surface to user raise UserException( str(ex), wrapped_from=ex.response["Error"]["Code"]) from ex
def do_cli(ctx, function_identifier, template, event, env_vars, debug_port, debug_args, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ LOG.debug("local invoke command is called") event_data = _get_event(event) # Pass all inputs to setup necessary context to invoke function locally. # Handler exception raised by the processor for invalid args and print errors try: with InvokeContext(template_file=template, function_identifier=function_identifier, env_vars_file=env_vars, debug_port=debug_port, debug_args=debug_args, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, aws_profile=profile) as context: # Invoke the function context.local_lambda_runner.invoke(context.function_name, event=event_data, stdout=context.stdout, stderr=context.stderr) except FunctionNotFound: raise UserException( "Function {} not found in template".format(function_identifier)) except InvalidSamDocumentException as ex: raise UserException(str(ex))
def manage_stack(profile, region): outputs = manage_cloudformation_stack(profile=None, region=region, stack_name=SAM_CLI_STACK_NAME, template_body=_get_stack_template()) try: bucket_name = next(o for o in outputs if o["OutputKey"] == "SourceBucket")["OutputValue"] except StopIteration as ex: msg = ( "Stack " + SAM_CLI_STACK_NAME + " exists, but is missing the managed source bucket key. " "Failing as this stack was likely not created by the AWS SAM CLI.") raise UserException(msg) from ex # This bucket name is what we would write to a config file return bucket_name
def do_generate(location, runtime, dependency_manager, output_dir, name, no_input, extra_context): no_build_msg = """ Project generated: {output_dir}/{name} Steps you can take next within the project folder =================================================== [*] Invoke Function: sam local invoke HelloWorldFunction --event event.json [*] Start API Gateway locally: sam local start-api """.format( output_dir=output_dir, name=name ) build_msg = """ Project generated: {output_dir}/{name} Steps you can take next within the project folder =================================================== [*] Install dependencies [*] Invoke Function: sam local invoke HelloWorldFunction --event event.json [*] Start API Gateway locally: sam local start-api """.format( output_dir=output_dir, name=name ) no_build_step_required = ( "python", "python3.7", "python3.6", "python2.7", "nodejs", "nodejs4.3", "nodejs6.10", "nodejs8.10", "nodejs10.x", "ruby2.5", ) next_step_msg = no_build_msg if runtime in no_build_step_required else build_msg try: generate_project(location, runtime, dependency_manager, output_dir, name, no_input, extra_context) if not location: click.secho(next_step_msg, bold=True) click.secho("Read {name}/README.md for further instructions\n".format(name=name), bold=True) click.secho("[*] Project initialization is now complete", fg="green") except GenerateProjectFailedError as e: raise UserException(str(e))
def do_cli(ctx, location, runtime, output_dir, name, no_input): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ LOG.debug("Init command") click.secho("[+] Initializing project structure...", fg="green") try: generate_project(location, runtime, output_dir, name, no_input) # Custom templates can implement their own visual cues so let's not repeat the message if not location: click.secho( "[SUCCESS] - Read {name}/README.md for further instructions on how to proceed" .format(name=name), bold=True) click.secho("[*] Project initialization is now complete", fg="green") except GenerateProjectFailedError as e: raise UserException(str(e))
def __enter__(self): try: self._template_dict = get_template_data(self._template_file) except ValueError as ex: raise UserException(str(ex)) self._function_provider = SamFunctionProvider(self._template_dict, self._parameter_overrides) if not self._base_dir: # Base directory, if not provided, is the directory containing the template self._base_dir = str(pathlib.Path(self._template_file).resolve().parent) self._build_dir = self._setup_build_dir(self._build_dir, self._clean) if self._use_container: self._container_manager = ContainerManager(docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image) return self
def _get_cookiecutter_template_context(name, runtime, extra_context): default_context = {} extra_context_dict = {} if runtime is not None: default_context["runtime"] = runtime if name is not None: default_context["project_name"] = name if extra_context is not None: try: extra_context_dict = json.loads(extra_context) except JSONDecodeError: raise UserException( "Parse error reading the --extra-context parameter. The value of this parameter must be valid JSON." ) return {**extra_context_dict, **default_context}
def do_cli(ctx, template): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ sam_template = _read_sam_file(template) iam_client = boto3.client('iam') validator = SamTemplateValidator(sam_template, ManagedPolicyLoader(iam_client)) try: validator.is_valid() except InvalidSamDocumentException as e: click.secho("Template provided at '{}' was invalid SAM Template.".format(template), bg='red') raise InvalidSamTemplateException(str(e)) except NoCredentialsError as e: raise UserException("AWS Credentials are required. Please configure your credentials.") click.secho("{} is a valid SAM Template".format(template), fg='green')
def run_interactive_flows(self) -> Dict: """ prompt the user a series of questions' flows and gather the answers to create the cookiecutter context. The questions are identified by keys. If multiple questions, whether within the same flow or across different flows, have the same key, the last question will override the others and we will get only the answer of this question. Raises: UserException(ClickException) if anything went wrong. Returns: A Dictionary in the form of {question.key: answer} representing user's answers to the flows' questions """ try: context: Dict[str, Any] = {} for flow in self._interactive_flows: context = flow.run(context) return context except Exception as e: raise UserException(str(e), wrapped_from=e.__class__.__name__) from e