github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/boskos/janitor/janitor.py (about) 1 #!/usr/bin/env python 2 3 # Copyright 2016 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Clean up resources from gcp projects. """ 18 19 import argparse 20 import collections 21 import datetime 22 import json 23 import subprocess 24 import sys 25 26 27 # A resource that need to be cleared. 28 Resource = collections.namedtuple('Resource', 'name group condition managed') 29 DEMOLISH_ORDER = [ 30 # Beware of insertion order 31 Resource('instances', None, 'zone', None), 32 Resource('addresses', None, 'region', None), 33 Resource('disks', None, 'zone', None), 34 Resource('firewall-rules', None, None, None), 35 Resource('routes', None, None, None), 36 Resource('forwarding-rules', None, 'region', None), 37 Resource('target-http-proxies', None, None, None), 38 Resource('target-https-proxies', None, None, None), 39 Resource('url-maps', None, None, None), 40 Resource('backend-services', None, 'region', None), 41 Resource('target-pools', None, 'region', None), 42 Resource('health-checks', None, None, None), 43 Resource('http-health-checks', None, None, None), 44 Resource('instance-groups', None, 'zone', 'Yes'), 45 Resource('instance-groups', None, 'zone', 'No'), 46 Resource('instance-templates', None, None, None), 47 Resource('networks', 'subnets', 'region', None), 48 Resource('networks', None, '', None), 49 ] 50 51 52 def collect(project, age, resource, filt): 53 """ Collect a list of resources for each condition (zone or region). 54 55 Args: 56 project: The name of a gcp project. 57 age: Time cutoff from the creation of a resource. 58 resource: Definition of a type of gcloud resource. 59 filt: Filter clause for gcloud list command. 60 Returns: 61 A dict of condition : list of gcloud resource object. 62 Raises: 63 ValueError if json result from gcloud is invalid. 64 """ 65 66 col = collections.defaultdict(list) 67 68 cmd = ['gcloud', 'compute', '-q', resource.name] 69 if resource.group: 70 cmd.append(resource.group) 71 cmd.extend([ 72 'list', 73 '--format=json(name,creationTimestamp.date(tz=UTC),zone,region,isManaged)', 74 '--filter=%s' % filt, 75 '--project=%s' % project]) 76 print '%r' % cmd 77 78 for item in json.loads(subprocess.check_output(cmd)): 79 print '%r' % item 80 81 if 'name' not in item or 'creationTimestamp' not in item: 82 raise ValueError('%r' % item) 83 84 if resource.condition and resource.condition in item: 85 colname = item[resource.condition] 86 else: 87 colname = '' 88 89 if resource.managed: 90 if 'isManaged' not in item: 91 raise ValueError(resource.name, resource.managed) 92 else: 93 if resource.managed != item['isManaged']: 94 continue 95 96 # Unify datetime to use utc timezone. 97 created = datetime.datetime.strptime(item['creationTimestamp'], '%Y-%m-%dT%H:%M:%S') 98 print ('Found %r(%r), %r in %r, created time = %r' % 99 (resource.name, resource.group, item['name'], colname, item['creationTimestamp'])) 100 if created < age: 101 print ('Added to janitor list: %r(%r), %r' % 102 (resource.name, resource.group, item['name'])) 103 col[colname].append(item['name']) 104 return col 105 106 107 def clear_resources(project, cols, resource): 108 """Clear a collection of resource, from collect func above. 109 110 Args: 111 project: The name of a gcp project. 112 cols: A dict of collection of resource. 113 resource: Definition of a type of gcloud resource. 114 Returns: 115 0 if no error 116 1 if deletion command fails 117 """ 118 err = 0 119 for col, items in cols.items(): 120 if ARGS.dryrun: 121 print ('Resource type %r(%r) to be deleted: %r' % 122 (resource.name, resource.group, list(items))) 123 continue 124 125 manage_key = {'Yes':'managed', 'No':'unmanaged'} 126 127 # construct the customized gcloud commend 128 base = ['gcloud', 'compute', '-q', resource.name] 129 if resource.group: 130 base.append(resource.group) 131 if resource.managed: 132 base.append(manage_key[resource.managed]) 133 base.append('delete') 134 base.append('--project=%s' % project) 135 136 if resource.condition: 137 if col: 138 base.append('--%s=%s' % (resource.condition, col)) 139 else: 140 base.append('--global') 141 142 print 'Call %r' % base 143 try: 144 subprocess.check_call(base + list(items)) 145 except subprocess.CalledProcessError as exc: 146 err = 1 147 print >>sys.stderr, 'Error try to delete resources: %r' % exc 148 return err 149 150 151 def main(project, days, hours, filt): 152 """ Clean up resources from a gcp project based on it's creation time 153 154 Args: 155 project: The name of a gcp project. 156 days/hours: days/hours of maximum lifetime of a gcp resource. 157 filt: Resource instance filters when query. 158 Returns: 159 0 if no error 160 1 if list or delete command fails 161 """ 162 163 print '[=== Start Janitor on project %r ===]' % project 164 err = 0 165 age = datetime.datetime.utcnow() - datetime.timedelta(days=days, hours=hours) 166 for res in DEMOLISH_ORDER: 167 print 'Try to search for %r with condition %r' % (res.name, res.condition) 168 try: 169 col = collect(project, age, res, filt) 170 if col: 171 err |= clear_resources(project, col, res) 172 except ValueError: 173 err |= 1 # keep clean the other resource 174 print >>sys.stderr, 'Fail to list resource %r from project %r' % (res.name, project) 175 176 print '[=== Finish Janitor on project %r with status %r ===]' % (project, err) 177 sys.exit(err) 178 179 180 if __name__ == '__main__': 181 PARSER = argparse.ArgumentParser( 182 description='Clean up resources from an expired project') 183 PARSER.add_argument('--project', help='Project to clean', required=True) 184 PARSER.add_argument( 185 '--days', type=int, 186 help='Clean items more than --days old (added to --hours)') 187 PARSER.add_argument( 188 '--hours', type=float, 189 help='Clean items more than --hours old (added to --days)') 190 PARSER.add_argument( 191 '--filter', 192 default='NOT tags.items:do-not-delete AND NOT name ~ ^default', 193 help='Filter down to these instances') 194 PARSER.add_argument( 195 '--dryrun', 196 default=False, 197 action='store_true', 198 help='list but not delete resources') 199 ARGS = PARSER.parse_args() 200 201 # We want to allow --days=0 and --hours=0, so check against None instead. 202 if ARGS.days is None and ARGS.hours is None: 203 print >>sys.stderr, 'must specify --days and/or --hours' 204 sys.exit(1) 205 206 main(ARGS.project, ARGS.days or 0, ARGS.hours or 0, ARGS.filter)