github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/packaging/ci/lambda/GitPullS3/lambda_function.py (about) 1 # Copyright 2016 Amazon Web Services, Inc. or its affiliates. All Rights Reserved. 2 # This file is licensed to you under the AWS Customer Agreement (the "License"). 3 # You may not use this file except in compliance with the License. 4 # A copy of the License is located at http://aws.amazon.com/agreement/ . 5 # This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. 6 # See the License for the specific language governing permissions and limitations under the License. 7 # 8 # mjmac 2016-12-07: Change exclude_git to False. Add code to embed source 9 # metadata in archive object's metadata in order to 10 # enable its use by subsequent pipeline stages. 11 12 from pygit2 import Keypair,credentials,discover_repository,Repository,clone_repository,RemoteCallbacks 13 from boto3 import client 14 import os,stat 15 import shutil 16 from zipfile import ZipFile 17 from ipaddress import ip_network, ip_address 18 import json 19 import logging 20 import hmac 21 import hashlib 22 23 ### If true the function will not include .git folder in the zip 24 exclude_git=False 25 26 ### If true the function will delete all files at the end of each invocation, useful if you run into storage space constraints, but will slow down invocations as each invoke will need to checkout the entire repo 27 cleanup=False 28 29 key='enc_key' 30 31 logger = logging.getLogger() 32 logger.setLevel(logging.INFO) 33 logger.handlers[0].setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')) 34 logging.getLogger('boto3').setLevel(logging.ERROR) 35 logging.getLogger('botocore').setLevel(logging.ERROR) 36 37 s3 = client('s3') 38 kms = client('kms') 39 40 def write_key(filename,contents): 41 logger.info('Writing keys to /tmp/...') 42 mode = stat.S_IRUSR | stat.S_IWUSR 43 umask_original = os.umask(0) 44 try: 45 handle = os.fdopen(os.open(filename, os.O_WRONLY | os.O_CREAT, mode), 'w') 46 finally: 47 os.umask(umask_original) 48 handle.write(contents+'\n') 49 handle.close() 50 51 def get_keys(keybucket,pubkey,update=False): 52 if not os.path.isfile('/tmp/id_rsa') or not os.path.isfile('/tmp/id_rsa.pub') or update: 53 logger.info('Keys not found on Lambda container, fetching from S3...') 54 enckey = s3.get_object(Bucket=keybucket,Key=key)['Body'].read() 55 privkey = kms.decrypt(CiphertextBlob=enckey)['Plaintext'] 56 write_key('/tmp/id_rsa',privkey) 57 write_key('/tmp/id_rsa.pub',pubkey) 58 return Keypair('git','/tmp/id_rsa.pub','/tmp/id_rsa','') 59 60 def init_remote(repo, name, url): 61 remote = repo.remotes.create(name, url, '+refs/*:refs/*') 62 return remote 63 64 def create_repo(repo_path, remote_url, creds): 65 if os.path.exists(repo_path): 66 logger.info('Cleaning up repo path...') 67 shutil.rmtree(repo_path) 68 repo = clone_repository(remote_url, repo_path, callbacks=creds ) 69 70 return repo 71 72 def pull_repo(repo, remote_url, creds): 73 remote_exists = False 74 for r in repo.remotes: 75 if r.url == remote_url: 76 remote_exists = True 77 remote = r 78 if not remote_exists: 79 remote = repo.create_remote('origin',remote_url) 80 logger.info('Fetching and merging changes...') 81 remote.fetch(callbacks=creds) 82 remote_master_id = repo.lookup_reference('refs/remotes/origin/master').target 83 repo.checkout_tree(repo.get(remote_master_id)) 84 master_ref = repo.lookup_reference('refs/heads/master') 85 master_ref.set_target(remote_master_id) 86 repo.head.set_target(remote_master_id) 87 return repo 88 89 def zip_repo(repo_path,repo_name): 90 logger.info('Creating zipfile...') 91 zf = ZipFile('/tmp/'+repo_name.replace('/','_')+'.zip','w') 92 for dirname, subdirs, files in os.walk(repo_path): 93 if exclude_git: 94 try: 95 subdirs.remove('.git') 96 except ValueError: 97 pass 98 zdirname = dirname[len(repo_path)+1:] 99 zf.write(dirname,zdirname) 100 for filename in files: 101 zf.write(os.path.join(dirname, filename),os.path.join(zdirname, filename)) 102 zf.close() 103 return '/tmp/'+repo_name.replace('/','_')+'.zip' 104 105 def push_s3(filename,repo_name,outputbucket,tags={}): 106 s3key='%s/%s' % (repo_name,filename.replace('/tmp/','')) 107 logger.info('pushing zip to s3://%s/%s' % (outputbucket,s3key)) 108 data=open(filename,'rb') 109 s3.put_object(Bucket=outputbucket,Body=data,Key=s3key,Metadata=tags) 110 logger.info('Completed S3 upload...') 111 112 def lambda_handler(event,context): 113 print json.dumps(event) 114 keybucket=event['context']['key-bucket'] 115 outputbucket=event['context']['output-bucket'] 116 pubkey=event['context']['public-key'] 117 ### Source IP ranges to allow requests from, if the IP is in one of these the request will not be chacked for an api key 118 ipranges=[] 119 for i in event['context']['allowed-ips'].split(','): 120 ipranges.append(ip_network(u'%s' % i)) 121 ### APIKeys, it is recommended to use a different API key for each repo that uses this function 122 apikeys=event['context']['api-secrets'].split(',') 123 ip = ip_address(event['context']['source-ip']) 124 secure=False 125 for net in ipranges: 126 if ip in net: 127 secure=True 128 if 'X-Gitlab-Token' in event['params']['header'].keys(): 129 if event['params']['header']['X-Gitlab-Token'] in apikeys: 130 secure=True 131 if 'X-Git-Token' in event['params']['header'].keys(): 132 if event['params']['header']['X-Git-Token'] in apikeys: 133 secure=True 134 if 'X-Gitlab-Token' in event['params']['header'].keys(): 135 if event['params']['header']['X-Gitlab-Token'] in apikeys: 136 secure=True 137 if 'X-Hub-Signature' in event['params']['header'].keys(): 138 for k in apikeys: 139 if hmac.new(str(k),str(event['context']['raw-body']),hashlib.sha1).hexdigest() == str(event['params']['header']['X-Hub-Signature'].replace('sha1=','')): 140 secure=True 141 if not secure: 142 logger.error('Source IP %s is not allowed' % event['context']['source-ip']) 143 raise Exception('Source IP %s is not allowed' % event['context']['source-ip']) 144 try: 145 repo_name = event['body-json']['project']['path_with_namespace'] 146 except: 147 repo_name = event['body-json']['repository']['full_name'] 148 try: 149 remote_url = event['body-json']['project']['git_ssh_url'] 150 except: 151 try: 152 remote_url = 'git@'+event['body-json']['repository']['links']['html']['href'].replace('https://','').replace('/',':',1)+'.git' 153 except: 154 remote_url = event['body-json']['repository']['ssh_url'] 155 repo_path = '/tmp/%s' % repo_name 156 creds = RemoteCallbacks( credentials=get_keys(keybucket,pubkey), ) 157 try: 158 repository_path = discover_repository(repo_path) 159 repo = Repository(repository_path) 160 logger.info('found existing repo, using that...') 161 except: 162 logger.info('creating new repo for %s in %s' % (remote_url, repo_path)) 163 repo = create_repo(repo_path, remote_url, creds) 164 pull_repo(repo,remote_url,creds) 165 zipfile = zip_repo(repo_path, repo_name) 166 s3_tags = dict( 167 source_revision = event['body-json']['head_commit']['id'], 168 source_html_url = event['body-json']['repository']['html_url'], 169 source_ref = event['body-json']['ref'] 170 ) 171 push_s3(zipfile,repo_name,outputbucket,s3_tags) 172 if cleanup: 173 logger.info('Cleanup Lambda container...') 174 shutil.rmtree(repo_path) 175 shutil.rm(zipfile) 176 shutil.rm('/tmp/id_rsa') 177 shutil.rm('/tmp/id_rsa.pub') 178 return 'Successfully updated %s' % repo_name