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)