def unlink_account(self, linked_account_id): """ Unlink a New Relic Cloud integrations account """ res = self.query( """ mutation ($accountId: Int!, $accounts: CloudUnlinkCloudAccountsInput!) { cloudUnLinkAccount (accountId: $accountId, accounts: $accounts) { unlinkedAccounts { id name } errors { type message } } } """, accountId=self.account_id, accounts={"linkedAccountId": linked_account_id}, ) if "errors" in res: failure( "Error while unlinking account with New Relic:\n%s" % "\n".join([e["message"] for e in res["errors"] if "message" in e]) ) return res
def link_account(self, role_arn, account_name): """ create a linked account (cloud integrations account) in the New Relic account """ res = self.query( """ mutation ($accountId: Int!, $accounts: CloudLinkCloudAccountsInput!){ cloudLinkAccount (accountId: $accountId, accounts: $accounts) { linkedAccounts { id name } errors { message } } } """, accountId=self.account_id, accounts={"aws": {"arn": role_arn, "name": account_name}}, ) try: return res["cloudLinkAccount"]["linkedAccounts"][0] except (IndexError, KeyError): if "errors" in res: failure( "Error while linking account with New Relic:\n%s" % "\n".join([e["message"] for e in res["errors"] if "message" in e]) ) return None
def uninstall(ctx, **kwargs): """Uninstall New Relic AWS Lambda Layers""" input = LayerUninstall(session=None, verbose=ctx.obj["VERBOSE"], **kwargs) input = input._replace(session=boto3.Session( profile_name=input.aws_profile, region_name=input.aws_region)) if input.aws_permissions_check: permissions.ensure_layer_uninstall_permissions(input) functions = get_aliased_functions(input) with ThreadPoolExecutor() as executor: futures = [ executor.submit(layers.uninstall, input, function) for function in functions ] uninstall_success = all(future.result() for future in as_completed(futures)) if uninstall_success: done("Uninstall Complete") else: failure("Uninstall Incomplete. See messages above for details.", exit=True)
def disable_integration(self, linked_account_id, provider_slug, service_slug): """ Disable monitoring of a Cloud provider service (integration) """ res = self.query( """ mutation ($accountId: Int!, $integrations: CloudIntegrationsInput!) { cloudDisableIntegration ( accountId: $accountId, integrations: $integrations ) { disabledIntegrations { id accountId name } errors { type message } } } """, accountId=self.account_id, integrations={ provider_slug: {service_slug: [{"linkedAccountId": linked_account_id}]} }, ) if "errors" in res: failure( "Error while disabling integration with New Relic:\n%s" % "\n".join([e["message"] for e in res["errors"] if "message" in e]) ) return res
def install_log_ingestion(session, nr_license_key, enable_logs=False): """ Installs the New Relic AWS Lambda log ingestion function and role. Returns True for success and False for failure. """ function = get_function(session, "newrelic-log-ingestion") if function is None: stack_status = check_for_ingest_stack(session) if stack_status is None: click.echo( "Setting up 'newrelic-log-ingestion' function in region: %s" % session.region_name) try: create_log_ingestion_function(session, nr_license_key, enable_logs) except Exception as e: failure( "Failed to create 'newrelic-log-ingestion' function: %s" % e) return False else: failure( "CloudFormation Stack NewRelicLogIngestion exists (status: %s), but " "newrelic-log-ingestion Lambda function does not.\n" "Please manually delete the stack and re-run this command." % stack_status) return False else: success( "The 'newrelic-log-ingestion' function already exists in region %s, " "skipping" % session.region_name) return True
def install(**kwargs): """Install New Relic AWS Lambda Log Subscriptions""" input = SubscriptionInstall(session=None, **kwargs) input = input._replace(session=boto3.Session( profile_name=input.aws_profile, region_name=input.aws_region)) if input.aws_permissions_check: permissions.ensure_subscription_install_permissions(input) functions = get_aliased_functions(input) with ThreadPoolExecutor() as executor: futures = [ executor.submit(subscriptions.create_log_subscription, input, function) for function in functions ] install_success = all(future.result() for future in as_completed(futures)) if install_success: done("Install Complete") else: failure("Install Incomplete. See messages above for details.", exit=True)
def layer_selection(available_layers, runtime, architecture): if len(available_layers) == 1: return available_layers[0]["LatestMatchingVersion"]["LayerVersionArn"] layer_options = [ layer["LatestMatchingVersion"]["LayerVersionArn"] for layer in available_layers ] if sys.stdout.isatty(): output = "\n".join([ "Discovered multiple layers for runtime %s (%s):" % (runtime, architecture), "", ] + ["%d: %s" % (i, layer) for i, layer in enumerate(layer_options)] + ["", "Select a layer"]) while True: value = click.prompt(output, default=0, type=int) try: selected_layer = layer_options[value] success("Layer %s selected" % selected_layer) return selected_layer except IndexError: failure("Invalid layer selection") else: raise click.UsageError( "Discovered multiple layers for runtime %s (%s):\n%s\n" "Pass --layer-arn to specify a layer ARN" % (runtime, architecture, "\n".join(layer_options)))
def update(**kwargs): """UpdateNew Relic AWS Lambda Integration""" input = IntegrationUpdate(session=None, **kwargs) input = input._replace( session=boto3.Session( profile_name=input.aws_profile, region_name=input.aws_region ) ) if input.aws_permissions_check: permissions.ensure_integration_install_permissions(input) update_success = True click.echo("Updating newrelic-log-ingestion Lambda function in AWS account") res = integrations.update_log_ingestion(input) update_success = res and update_success if input.enable_license_key_secret: update_success = update_success and integrations.auto_install_license_key(input) else: integrations.remove_license_key(input) if update_success: done("Update Complete") else: failure("Update Incomplete. See messages above for details.", exit=True)
def uninstall(input, function_arn): assert isinstance(input, LayerUninstall) client = input.session.client("lambda") config = get_function(input.session, function_arn) if not config: failure("Could not find function: %s" % function_arn) return False update_kwargs = _remove_new_relic(input, config) if not update_kwargs: return False try: res = client.update_function_configuration(**update_kwargs) except botocore.exceptions.ClientError as e: failure("Failed to update configuration for '%s': %s" % (config["Configuration"]["FunctionArn"], e)) return False else: policy_arn = _get_license_key_policy_arn(input.session) if policy_arn: _detach_license_key_policy(input.session, config["Configuration"]["Role"], policy_arn) if input.verbose: click.echo(json.dumps(res, indent=2)) success("Successfully uninstalled layer on %s" % function_arn) return True
def install( ctx, nr_account_id, aws_profile, aws_region, aws_permissions_check, functions, excludes, layer_arn, upgrade, ): """Install New Relic AWS Lambda Layers""" session = boto3.Session(profile_name=aws_profile, region_name=aws_region) if aws_permissions_check: permissions.ensure_lambda_install_permissions(session) functions = get_aliased_functions(session, functions, excludes) install_success = True for function in functions: res = layers.install(session, function, layer_arn, nr_account_id, upgrade) install_success = res and install_success if res: success("Successfully installed layer on %s" % function) if ctx.obj["VERBOSE"]: click.echo(json.dumps(res, indent=2)) if install_success: done("Install Complete") else: failure("Install Incomplete. See messages above for details.", exit=True)
def create_integration_role(input): """ Creates a AWS CloudFormation stack that adds the New Relic AWSLambda Integration IAM role. This can be overridden with the `role_arn` parameter, which just checks that the role exists. """ assert isinstance(input, IntegrationInstall) if input.integration_arn is not None: role = _get_role(input.session, input.integration_arn) if role: success( "Found existing AWS IAM role '%s', using it with the New Relic Lambda " "integration" % input.integration_arn) return role failure( "Could not find AWS IAM role '%s', please verify it exists and run this " "command again" % input.integration_arn) return role_name = "NewRelicLambdaIntegrationRole_%s" % input.nr_account_id stack_name = "NewRelicLambdaIntegrationRole-%s" % input.nr_account_id role = _get_role(input.session, role_name) if role: success("New Relic AWS Lambda integration role '%s' already exists" % role_name) return role stack_status = _get_cf_stack_status(input.session, stack_name) if stack_status is None: _create_role(input) role = _get_role(input.session, role_name) success("Created role [%s] in AWS account." % role_name) return role failure( "Cannot create CloudFormation stack %s because it exists in state %s" % (stack_name, stack_status))
def create_integration_account(gql, nr_account_id, linked_account_name, role): """ Creates a New Relic Cloud integration account for the specified AWS IAM role. """ role_arn = role["Role"]["Arn"] account = gql.get_linked_account_by_name(linked_account_name) if account: success( "Cloud integrations account [%s] already exists " "in New Relic account [%d]." % (account["name"], nr_account_id) ) return account account = account = gql.link_account(role_arn, linked_account_name) if account: success( "Cloud integrations account [%s] was created in New Relic account [%s] " "with role [%s]." % (linked_account_name, nr_account_id, role_arn) ) return account failure( "Could not create Cloud integrations account [%s] in New Relic account [%s] " "with role [%s]. This may be due to a previously installed integration. " "Please contact New Relic support for assistance." % (linked_account_name, nr_account_id, role_arn) )
def install( aws_profile, aws_region, aws_permissions_check, functions, excludes, filter_pattern ): """Install New Relic AWS Lambda Log Subscriptions""" session = boto3.Session(profile_name=aws_profile, region_name=aws_region) if aws_permissions_check: permissions.ensure_subscription_install_permissions(session) functions = get_aliased_functions(session, functions, excludes) install_success = True for function in functions: result = subscriptions.create_log_subscription( session, function, filter_pattern ) install_success = result and install_success if result: success("Successfully installed log subscription on %s" % function) if install_success: done("Install Complete") else: failure("Install Incomplete. See messages above for details.", exit=True)
def create_integration_account(gql, input, role): """ Creates a New Relic Cloud integration account for the specified AWS IAM role. """ assert isinstance(gql, NewRelicGQL) assert isinstance(input, IntegrationInstall) role_arn = role["Role"]["Arn"] external_id = parse_arn(role_arn)["account"] account = gql.get_linked_account_by_external_id(external_id) if account: success("Cloud integrations account [%s] already exists " "in New Relic account [%d] with IAM role [%s]." % (account["name"], input.nr_account_id, account["authLabel"])) return account account = account = gql.link_account(role_arn, input.linked_account_name) if account: success( "Cloud integrations account [%s] was created in New Relic account [%s] " "with IAM role [%s]." % (input.linked_account_name, input.nr_account_id, role_arn)) return account failure( "Could not create Cloud integrations account [%s] in New Relic account [%s] " "with role [%s]. This may be due to a previously installed integration. " "Please contact New Relic support for assistance." % (input.linked_account_name, input.nr_account_id, role_arn))
def update( aws_profile, aws_region, aws_permissions_check, enable_logs, memory_size, timeout, role_name, ): """UpdateNew Relic AWS Lambda Integration""" session = boto3.Session(profile_name=aws_profile, region_name=aws_region) if aws_permissions_check: permissions.ensure_integration_install_permissions(session) update_success = True click.echo( "Updating newrelic-log-ingestion Lambda function in AWS account") res = integrations.update_log_ingestion(session, None, enable_logs, memory_size, timeout, role_name) update_success = res and update_success if update_success: done("Update Complete") else: failure("Update Incomplete. See messages above for details.", exit=True)
def enable_lambda_integration(gql, nr_account_id, linked_account_name): """ Enables AWS Lambda for the specified New Relic Cloud integrations account. Returns True for success and False for failure. """ account = gql.get_linked_account_by_name(linked_account_name) if account is None: failure("Could not find Cloud integrations account " "[%s] in New Relic account [%d]." % (linked_account_name, nr_account_id)) return False is_lambda_enabled = gql.is_integration_enabled(account["id"], "lambda") if is_lambda_enabled: success("The AWS Lambda integration is already enabled in " "Cloud integrations account [%s] of New Relic account [%d]." % (linked_account_name, nr_account_id)) return True try: integration = gql.enable_integration(account["id"], "aws", "lambda") except Exception: failure( "Could not enable New Relic AWS Lambda integration. Make sure your New " "Relic account is a Pro plan and try this command again.") return False else: success("Integration [id=%s, name=%s] has been enabled in Cloud " "integrations account [%s] of New Relic account [%d]." % ( integration["id"], integration["name"], linked_account_name, nr_account_id, )) return True
def install(session, function_arn, layer_arn, account_id, allow_upgrade, verbose): client = session.client("lambda") config = get_function(session, function_arn) if not config: failure("Could not find function: %s" % function_arn) return False region = session.region_name update_kwargs = _add_new_relic(config, region, layer_arn, account_id, allow_upgrade) if not update_kwargs: return False try: res = client.update_function_configuration(**update_kwargs) except botocore.exceptions.ClientError as e: failure("Failed to update configuration for '%s': %s" % (config["Configuration"]["FunctionArn"], e)) return False else: if verbose: click.echo(json.dumps(res, indent=2)) success("Successfully installed layer on %s" % function_arn) return True
def remove_subscription_filter(session, function_name): try: session.client("logs").delete_subscription_filter( logGroupName="/aws/lambda/%s" % function_name, filterName="NewRelicLogStreaming", ) except botocore.exceptions.ClientError as e: failure("Error removing log subscription filter for '%s': %s" % (function_name, e)) return False else: return True
def _detach_license_key_policy(session, role_arn, policy_arn): """Detaches the license key secret policy from the specified role""" _, role_name = role_arn.rsplit("/", 1) client = session.client("iam") try: client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn) except botocore.exceptions.ClientError as e: failure("Failed to detach %s policy to %s: %s" % (policy_arn, role_arn, e)) return False else: return True
def install( aws_profile, aws_region, aws_permissions_check, aws_role_policy, linked_account_name, nr_account_id, nr_api_key, nr_region, ): """Install New Relic AWS Lambda Integration""" session = boto3.Session(profile_name=aws_profile, region_name=aws_region) if aws_permissions_check: permissions.ensure_integration_install_permissions(session) click.echo("Validating New Relic credentials") gql_client = api.validate_gql_credentials(nr_account_id, nr_api_key, nr_region) click.echo("Retrieving integration license key") nr_license_key = api.retrieve_license_key(gql_client) click.echo("Checking for a pre-existing link between New Relic and AWS") integrations.validate_linked_account(session, gql_client, linked_account_name) click.echo( "Creating the AWS role for the New Relic AWS Lambda Integration") role = integrations.create_integration_role(session, aws_role_policy, nr_account_id) install_success = False if role: click.echo("Linking New Relic account to AWS account") api.create_integration_account(gql_client, nr_account_id, linked_account_name, role) click.echo( "Enabling Lambda integration on the link between New Relic and AWS" ) install_success = api.enable_lambda_integration( gql_client, nr_account_id, linked_account_name) click.echo( "Creating newrelic-log-ingestion Lambda function in AWS account") install_success = install_success and integrations.install_log_ingestion( session, nr_license_key) if install_success: done("Install Complete") else: failure("Install Incomplete. See messages above for details.")
def _remove_new_relic(input, config): assert isinstance(input, LayerUninstall) aws_region = input.session.region_name runtime = config["Configuration"]["Runtime"] if runtime not in utils.RUNTIME_CONFIG: failure("Unsupported Lambda runtime for '%s': %s" % (config["Configuration"]["FunctionArn"], runtime)) return True handler = config["Configuration"]["Handler"] # For java runtimes we need to remove the method name before # validating because method names are variable if "java" in runtime: handler = handler.split("::", 1)[0] + "::" # Detect non-New Relic handler and error if necessary. if not utils.is_valid_handler(runtime, handler): failure( "New Relic installation (via layers) not auto-detected for the specified " "function '%s'. Unrecognized handler in deployed function." % config["Configuration"]["FunctionArn"]) return False env_handler = (config["Configuration"].get("Environment", {}).get( "Variables", {}).get("NEW_RELIC_LAMBDA_HANDLER")) # Delete New Relic env vars config["Configuration"]["Environment"]["Variables"] = { key: value for key, value in config["Configuration"].get("Environment", {}).get( "Variables", {}).items() if key not in NEW_RELIC_ENV_VARS } # Remove New Relic layers layers = [ layer["Arn"] for layer in config["Configuration"].get("Layers") if not layer["Arn"].startswith(utils.get_arn_prefix(aws_region)) ] return { "FunctionName": config["Configuration"]["FunctionArn"], "Handler": env_handler if env_handler else config["Configuration"]["Handler"], "Environment": config["Configuration"]["Environment"], "Layers": layers, }
def remove_subscription_filter(session, function_name, filter_name): try: session.client("logs").delete_subscription_filter( logGroupName=get_log_group_name(function_name), filterName=filter_name) except botocore.exceptions.ClientError as e: failure("Error removing log subscription filter for '%s': %s" % (function_name, e)) return False else: success("Successfully uninstalled log subscription on %s" % function_name) return True
def install(input, function_arn): assert isinstance(input, LayerInstall) client = input.session.client("lambda") config = get_function(input.session, function_arn) if not config: failure("Could not find function: %s" % function_arn) return False policy_arn = _get_license_key_policy_arn(input.session) if input.enable_extension and not policy_arn and not input.nr_api_key: raise click.UsageError( "In order to use `--enable-extension`, you must first run " "`newrelic-lambda integrations install` with the " "`--enable-license-key-secret` flag. This uses AWS Secrets Manager " "to securely store your New Relic license key in your AWS account. " "If you are unable to use AWS Secrets Manager, re-run this command with " "`--nr-api-key` argument with your New Relic API key to set your license " "key in a NEW_RELIC_LICENSE_KEY environment variable instead.") nr_license_key = None if not policy_arn and input.nr_api_key and input.nr_region: gql = api.validate_gql_credentials(input) nr_license_key = api.retrieve_license_key(gql) update_kwargs = _add_new_relic(input, config, nr_license_key) if not update_kwargs or not isinstance(update_kwargs, dict): return False try: res = client.update_function_configuration(**update_kwargs) except botocore.exceptions.ClientError as e: failure("Failed to update configuration for '%s': %s" % (config["Configuration"]["FunctionArn"], e)) return False else: if input.enable_extension and policy_arn: _attach_license_key_policy(input.session, config["Configuration"]["Role"], policy_arn) if input.enable_extension_function_logs: subscriptions.remove_log_subscription(input, function_arn) if input.verbose: click.echo(json.dumps(res, indent=2)) success("Successfully installed layer on %s" % function_arn) return True
def get_subscription_filters(session, function_name): """Returns all the log subscription filters for the function""" log_group_name = get_log_group_name(function_name) try: res = session.client("logs").describe_subscription_filters( logGroupName=log_group_name) except botocore.exceptions.ClientError as e: if (e.response and "ResponseMetadata" in e.response and "HTTPStatusCode" in e.response["ResponseMetadata"] and e.response["ResponseMetadata"]["HTTPStatusCode"] == 404): return [] failure("Error retrieving log subscription filters for '%s': %s" % (function_name, e)) else: return res.get("subscriptionFilters", [])
def install(ctx, **kwargs): """Install New Relic AWS Lambda Layers""" input = LayerInstall(session=None, verbose=ctx.obj["VERBOSE"], **kwargs) input = input._replace(session=boto3.Session( profile_name=input.aws_profile, region_name=input.aws_region)) if input.aws_permissions_check: permissions.ensure_layer_install_permissions(input) functions = get_aliased_functions(input) with ThreadPoolExecutor() as executor: futures = [ executor.submit(layers.install, input, function) for function in functions ] install_success = all(future.result() for future in as_completed(futures)) if install_success: done("Install Complete") if ctx.obj["VERBOSE"]: click.echo( "\nNext step. Configure the CloudWatch subscription filter for your " "Lambda functions with the below command:\n") command = [ "$", "newrelic-lambda", "subscriptions", "install", "--function", "all", ] if input.aws_profile: command.append("--aws-profile %s" % input.aws_profile) if input.aws_region: command.append("--aws-region %s" % input.aws_region) click.echo(" ".join(command)) click.echo( "\nIf you used `--enable-logs` for the `newrelic-lambda integrations " "install` command earlier, run this command instead:\n") command.append('--filter-pattern ""') click.echo(" ".join(command)) else: failure("Install Incomplete. See messages above for details.", exit=True)
def create_subscription_filter( session, function_name, destination_arn, filter_pattern=DEFAULT_FILTER_PATTERN ): try: session.client("logs").put_subscription_filter( logGroupName=get_log_group_name(function_name), filterName="NewRelicLogStreaming", filterPattern=filter_pattern, destinationArn=destination_arn, ) except botocore.exceptions.ClientError as e: failure( "Error creating log subscription filter for '%s': %s" % (function_name, e) ) return False else: return True
def enable_integration(self, linked_account_id, provider_slug, service_slug): """ enable monitoring of a Cloud provider service (integration) """ res = self.query( """ mutation ($accountId: Int!, $integrations: CloudIntegrationsInput!) { cloudConfigureIntegration ( accountId: $accountId, integrations: $integrations ) { integrations { id name service { id name } } errors { linkedAccountId message } } } """, accountId=self.account_id, integrations={ provider_slug: { service_slug: [{ "linkedAccountId": linked_account_id }] } }, ) try: return res["cloudConfigureIntegration"]["integrations"][0] except (IndexError, KeyError): if "errors" in res: failure( "Error while enabling integration with New Relic:\n%s" % "\n".join([ e["message"] for e in res["errors"] if "message" in e ])) return None
def create_log_subscription(input, function_name): assert isinstance(input, SubscriptionInstall) destination = get_function(input.session, "newrelic-log-ingestion") if destination is None: failure( "Could not find 'newrelic-log-ingestion' function. Is the New Relic AWS " "integration installed?" ) return False destination_arn = destination["Configuration"]["FunctionArn"] subscription_filters = _get_subscription_filters(input.session, function_name) if subscription_filters is None: return False newrelic_filters = [ filter for filter in subscription_filters if "NewRelicLogStreaming" in filter["filterName"] ] if len(subscription_filters) > len(newrelic_filters): warning( "WARNING: Found a log subscription filter that was not installed by New " "Relic. This may prevent the New Relic log subscription filter from being " "installed. If you know you don't need this log subscription filter, you " "should first remove it and rerun this command. If your organization " "requires this log subscription filter, please contact New Relic at " "[email protected] for assistance with getting the AWS log " "subscription filter limit increased." ) if not newrelic_filters: click.echo("Adding New Relic log subscription to '%s'" % function_name) return _create_subscription_filter( input.session, function_name, destination_arn, input.filter_pattern ) else: click.echo( "Found log subscription for '%s', verifying configuration" % function_name ) newrelic_filter = newrelic_filters[0] if newrelic_filter["filterPattern"] != input.filter_pattern: return _remove_subscription_filter( input.session, function_name, newrelic_filter["filterName"] ) and _create_subscription_filter( input.session, function_name, destination_arn, input.filter_pattern ) return True
def auto_install_license_key(input): """ If the LK secret is missing, create it, picking up the LK value from the ingest lambda's configuration. """ assert isinstance(input, (IntegrationInstall, IntegrationUpdate)) lk_stack_status = _get_cf_stack_status(input.session, LICENSE_KEY_STACK_NAME) if lk_stack_status is None: click.echo("Creating the managed secret for the New Relic License Key") lk = get_log_ingestion_license_key(input.session) if lk is None: failure( "Could not create license key secret; failed to fetch license key " "value from ingest lambda") return False return install_license_key(input, nr_license_key=lk) return True
def _create_subscription_filter( session, function_name, destination_arn, filter_pattern ): try: session.client("logs").put_subscription_filter( logGroupName=_get_log_group_name(function_name), filterName="NewRelicLogStreaming", filterPattern=filter_pattern, destinationArn=destination_arn, ) except botocore.exceptions.ClientError as e: failure( "Error creating log subscription filter for '%s': %s" % (function_name, e) ) return False else: success("Successfully installed log subscription on %s" % function_name) return True