~nskaggs/juju-ci-tools/add-assess-terms

1767.1.1 by Curtis Hovey
Added aws.py based on gce.py.
1
#!/usr/bin/python
2
3
from __future__ import print_function
4
5
from argparse import ArgumentParser
6
from datetime import (
7
    datetime,
8
    timedelta,
9
)
10
import fnmatch
11
import logging
12
import os
13
import sys
14
15
from dateutil import tz
16
17
18
__metaclass__ = type
19
20
21
PERMANENT = 'permanent'
22
OLD_MACHINE_AGE = 14
23
24
25
# This logger strictly reports the activity of this script.
26
log = logging.getLogger("aws")
27
handler = logging.StreamHandler(sys.stderr)
28
handler.setFormatter(logging.Formatter(
29
    fmt='%(asctime)s %(levelname)s %(message)s',
30
    datefmt='%Y-%m-%d %H:%M:%S'))
31
log.addHandler(handler)
32
33
34
def is_permanent(node):
35
    """Return True of the node is permanent."""
36
    # the tags keys only exists if there are tags.
37
    tags = node.extra.get('tags', {})
38
    permanent = tags.get(PERMANENT, 'false')
39
    return permanent.lower() == 'true'
40
41
42
def is_young(node, old_age):
43
    """Return True if the node is young."""
44
    now = datetime.now(tz.gettz('UTC'))
45
    young = True
46
    # The value is not guaranteed, but is always present in running instances.
47
    created = node.created_at
48
    if created:
49
        creation_time = node.created_at
50
        age = now - creation_time
51
        hours = age.total_seconds() // 3600
52
        log.debug('{} is {} old'.format(node.name, hours))
53
        ago = timedelta(hours=old_age)
54
        if age > ago:
55
            young = False
56
    return young
57
58
59
def get_client(aws_access_key, aws_secret, region):
60
    """Delay imports and activation of AWS client as needed."""
61
    import libcloud
62
    aws = libcloud.compute.providers.get_driver(
63
        libcloud.compute.types.Provider.EC2)
64
    return aws(aws_access_key, aws_secret, region=region)
65
66
67
def list_instances(client, glob='*', print_out=False, states=['running']):
68
    """Return a list of cloud Nodes.
69
70
    Use print_out=True to print a listing of nodes.
71
72
    :param client: The AWS client.
73
    :param glob: The glob to find matching resource groups to delete.
74
    :param print_out: Print the found resources to STDOUT?
75
    :return: A list of Nodes
76
    """
77
    nodes = []
78
    for node in client.list_nodes():
1767.1.2 by Curtis Hovey
Added tests for aws.
79
        if not (fnmatch.fnmatch(node.name, glob) and node.state in states):
1767.1.1 by Curtis Hovey
Added aws.py based on gce.py.
80
            log.debug('Skipping {}'.format(node.name))
81
            continue
82
        nodes.append(node)
83
    if print_out:
84
        for node in nodes:
1767.1.2 by Curtis Hovey
Added tests for aws.
85
            if node.created_at:
86
                created = node.created_at.isoformat()
87
            else:
88
                created = 'UNKNOWN'
1767.1.1 by Curtis Hovey
Added aws.py based on gce.py.
89
            region_name = client.region_name
90
            print('{}\t{}\t{}\t{}'.format(
91
                node.name, region_name, created, node.state))
92
    return nodes
93
94
95
def delete_instances(client, name_id, old_age=OLD_MACHINE_AGE, dry_run=False):
96
    """Delete a node instance.
97
1767.1.3 by Curtis Hovey
Fixed doc.
98
    :param client: The AWS client.
1767.1.1 by Curtis Hovey
Added aws.py based on gce.py.
99
    :param name_id: A glob to match the aws name or Juju instance-id.
100
    :param old_age: The minimum age to delete.
101
    :param dry_run: Do not make changes when True.
102
    """
103
    nodes = list_instances(client, glob=name_id)
104
    deleted_count = 0
105
    deletable = []
106
    for node in nodes:
107
        if is_permanent(node):
108
            log.debug('Skipping {} because it is permanent'.format(node.name))
109
            continue
110
        if is_young(node, old_age):
111
            log.debug('Skipping {} because it is young:'.format(node.name))
112
            continue
113
        deletable.append(node)
114
    if not deletable:
115
        log.warning(
116
            'The no machines match {} that are older than {}'.format(
117
                name_id, old_age))
118
    for node in deletable:
119
        node_name = node.name
120
        log.debug('Deleting {}'.format(node_name))
121
        if not dry_run:
122
            # Do not pass destroy_boot_disk=True unless the node has a special
123
            # boot disk that is not set to autodestroy.
124
            success = client.destroy_node(node)
125
            if success:
126
                log.debug('Deleted {}'.format(node_name))
127
                deleted_count += 1
128
            else:
129
                log.error('Cannot delete {}'.format(node_name))
130
    return deleted_count
131
132
133
def parse_args(argv):
134
    """Return the argument parser for this program."""
135
    parser = ArgumentParser(description='Query and manage AWS.')
136
    parser.add_argument(
137
        '-d', '--dry-run', action='store_true', default=False,
138
        help='Do not make changes.')
139
    parser.add_argument(
140
        '-v', '--verbose', action='store_const',
141
        default=logging.INFO, const=logging.DEBUG,
142
        help='Verbose test harness output.')
143
    parser.add_argument(
144
        '--aws-access-key',
145
        help=("The AWS EC2 access key."
146
              "Environment: $AWS_ACCESS_KEY."),
147
        default=os.environ.get('AWS_ACCESS_KEY'))
148
    parser.add_argument(
149
        '--aws-secret',
150
        help=("The AWS EC2 secret."
151
              "Environment: $AWS_SECRET_KEY."),
152
        default=os.environ.get('AWS_SECRET_KEY'))
1767.1.4 by Curtis Hovey
make region a positional arg.
153
    parser.add_argument('region', help="The EC2 region.")
1767.1.1 by Curtis Hovey
Added aws.py based on gce.py.
154
    subparsers = parser.add_subparsers(help='sub-command help', dest="command")
155
    ls_parser = subparsers.add_parser(
156
        'list-instances', help='List vm instances.')
157
    ls_parser.add_argument(
158
        'filter', default='*', nargs='?',
159
        help='A glob pattern to match services to.')
160
    di_parser = subparsers.add_parser(
161
        'delete-instances',
162
        help='delete old resource groups and their vm, networks, etc.')
163
    di_parser.add_argument(
164
        '-o', '--old-age', default=OLD_MACHINE_AGE, type=int,
165
        help='Set old machine age to n hours.')
166
    di_parser.add_argument(
167
        'filter',
168
        help='A glob pattern to select AWS name or juju instance-id')
169
    args = parser.parse_args(argv[1:])
170
    if not all([args.aws_access_key, args.aws_secret]):
171
        log.error("$AWS_ACCESS_KEY, $AWS_SECRET_KEY was not provided.")
172
    return args
173
174
175
def main(argv):
176
    args = parse_args(argv)
177
    log.setLevel(args.verbose)
178
    client = get_client(args.aws_access_key, args.aws_secret, args.region)
179
    try:
180
        if args.command == 'list-instances':
181
            list_instances(client, glob=args.filter, print_out=True)
182
        elif args.command == 'delete-instances':
183
            delete_instances(
184
                client, args.filter,
185
                old_age=args.old_age, dry_run=args.dry_run)
186
    except Exception as e:
187
        print(e)
188
        return 1
189
    return 0
190
191
192
if __name__ == '__main__':
193
    sys.exit(main(sys.argv))