Parameters: JenkinsUser: Type: String Description: 'Jenkins user name' JenkinsAPIToken: Type: String NoEcho: true Description: 'Jenkins user api token' JenkinsUrl: Type: String Description: 'Jenkins url' Resources: JenkinsAPIUserTokenSecret: Type: AWS::SecretsManager::Secret Properties: Description: 'Secret to store jenkins username and personal access token' Name: 'JenkinsAPIUserTokenSecret' SecretString: !Sub - '{ "${username}": "${apitoken}" }' - username: !Ref JenkinsUser apitoken: !Ref JenkinsAPIToken LambdaJenkinsTrigger: Type: 'AWS::Lambda::Function' Properties: Code: ZipFile: | import json import boto3 import base64 import urllib3 import os from botocore.exceptions import ClientError def get_secret(): secret_name = os.environ['JenkinsAPIUserTokenSecret'] region_name = os.environ['AWS_REGION'] # Create a Secrets Manager client session = boto3.session.Session() client = session.client( service_name='secretsmanager', region_name=region_name ) # In this sample we only handle the specific exceptions for the 'GetSecretValue' API. # See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html # We rethrow the exception by default. try: get_secret_value_response = client.get_secret_value( SecretId=secret_name ) except ClientError as e: if e.response['Error']['Code'] == 'DecryptionFailureException': # Secrets Manager can't decrypt the protected secret text using the provided KMS key. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InternalServiceErrorException': # An error occurred on the server side. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidParameterException': # You provided an invalid value for a parameter. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidRequestException': # You provided a parameter value that is not valid for the current state of the resource. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'ResourceNotFoundException': # We can't find the resource that you asked for. # Deal with the exception here, and/or rethrow at your discretion. raise e else: # Decrypts secret using the associated KMS CMK. # Depending on whether the secret is a string or binary, one of these fields will be populated. if 'SecretString' in get_secret_value_response: return get_secret_value_response['SecretString'] else: return base64.b64decode(get_secret_value_response['SecretBinary']) def get_secret_value(secert_string): # secert json is of format {'key' : 'value'} secert_json = json.loads(secert_string) # Get values return list(secert_json.values())[0] def lambda_handler(event, context): http = urllib3.PoolManager() user_api_token = json.loads(get_secret()) url = get_jenkins_job_url(event) user = list(user_api_token.keys())[0] api_token = user_api_token[user] print('Jenkins remote trigger url: ' + url) headers = urllib3.make_headers(basic_auth = user + ':' + api_token) r = http.request('GET', url, headers=headers) print(r.data) if r.status > 299: raise Exception('Remote trigger of jenkins failed with status ' + str(r.status)) return { 'statusCode': r.status, 'body': r.data } def get_jenkins_job_url(event): jenkins_url = os.environ['JenkinsUrl'] jenkins_model_deploy_pipeline_name = 'sagemaker-' + event['detail']['ModelPackageGroupName'] + '-modeldeploy' jenkins_job_url = jenkins_url + '/job/' + jenkins_model_deploy_pipeline_name + '/' job_token = 'token=token' cause = 'cause=Model+Package+version+' + event['detail']['ModelPackageArn'] + '+apporval+status+changed+to+' + event['detail']['ModelApprovalStatus'] jenkins_remote_url_params = job_token + '&' + cause return jenkins_job_url + 'buildWithParameters?' + jenkins_remote_url_params Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.8 Environment: Variables: JenkinsAPIUserTokenSecret: !Ref JenkinsAPIUserTokenSecret JenkinsUrl: !Ref JenkinsUrl Timeout: '5' JenkinsTriggerRule: Type: 'AWS::Events::Rule' Properties: Description: "Rule to trigger a deployment when SageMaker Model registry is updated with a new model package. For example, a new model package is registered with Registry" EventPattern: source: - "aws.sagemaker" detail-type: - "SageMaker Model Package State Change" detail: ModelApprovalStatus: - Approved - Rejected State: ENABLED Targets: - Arn: !GetAtt - LambdaJenkinsTrigger - Arn Id: LambdaJenkinsTrigger PermissionForEventsToInvokeLambdaJenkinsTrigger: Type: 'AWS::Lambda::Permission' Properties: Action: lambda:InvokeFunction FunctionName: { Ref: LambdaJenkinsTrigger } Principal: events.amazonaws.com LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:* Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: arn:aws:secretsmanager:*:*:*