skilleter-thingy 0.0.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- skilleter_thingy/__init__.py +0 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/aws.py +548 -0
- skilleter_thingy/borger.py +269 -0
- skilleter_thingy/colour.py +213 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/dc_curses.py +278 -0
- skilleter_thingy/dc_defaults.py +221 -0
- skilleter_thingy/dc_util.py +50 -0
- skilleter_thingy/dircolors.py +308 -0
- skilleter_thingy/diskspacecheck.py +67 -0
- skilleter_thingy/docker.py +95 -0
- skilleter_thingy/docker_purge.py +113 -0
- skilleter_thingy/ffind.py +536 -0
- skilleter_thingy/files.py +142 -0
- skilleter_thingy/ggit.py +90 -0
- skilleter_thingy/ggrep.py +154 -0
- skilleter_thingy/git.py +1368 -0
- skilleter_thingy/git2.py +1307 -0
- skilleter_thingy/git_br.py +180 -0
- skilleter_thingy/git_ca.py +142 -0
- skilleter_thingy/git_cleanup.py +287 -0
- skilleter_thingy/git_co.py +220 -0
- skilleter_thingy/git_common.py +61 -0
- skilleter_thingy/git_hold.py +154 -0
- skilleter_thingy/git_mr.py +92 -0
- skilleter_thingy/git_parent.py +77 -0
- skilleter_thingy/git_review.py +1416 -0
- skilleter_thingy/git_update.py +385 -0
- skilleter_thingy/git_wt.py +96 -0
- skilleter_thingy/gitcmp_helper.py +322 -0
- skilleter_thingy/gitlab.py +193 -0
- skilleter_thingy/gitprompt.py +274 -0
- skilleter_thingy/gl.py +174 -0
- skilleter_thingy/gphotosync.py +610 -0
- skilleter_thingy/linecount.py +155 -0
- skilleter_thingy/logger.py +112 -0
- skilleter_thingy/moviemover.py +133 -0
- skilleter_thingy/path.py +156 -0
- skilleter_thingy/photodupe.py +110 -0
- skilleter_thingy/phototidier.py +248 -0
- skilleter_thingy/popup.py +87 -0
- skilleter_thingy/process.py +112 -0
- skilleter_thingy/py_audit.py +131 -0
- skilleter_thingy/readable.py +270 -0
- skilleter_thingy/remdir.py +126 -0
- skilleter_thingy/rmdupe.py +550 -0
- skilleter_thingy/rpylint.py +91 -0
- skilleter_thingy/run.py +334 -0
- skilleter_thingy/s3_sync.py +383 -0
- skilleter_thingy/splitpics.py +99 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/sysmon.py +435 -0
- skilleter_thingy/tfm.py +920 -0
- skilleter_thingy/tfm_pane.py +595 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/tidy.py +160 -0
- skilleter_thingy/trimpath.py +84 -0
- skilleter_thingy/window_rename.py +92 -0
- skilleter_thingy/xchmod.py +125 -0
- skilleter_thingy/yamlcheck.py +89 -0
- skilleter_thingy-0.0.22.dist-info/LICENSE +619 -0
- skilleter_thingy-0.0.22.dist-info/METADATA +22 -0
- skilleter_thingy-0.0.22.dist-info/RECORD +67 -0
- skilleter_thingy-0.0.22.dist-info/WHEEL +5 -0
- skilleter_thingy-0.0.22.dist-info/entry_points.txt +43 -0
- skilleter_thingy-0.0.22.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Thingy addpath command
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2018 John Skilleter
|
|
7
|
+
|
|
8
|
+
Update a $PATH-type variable by adding or removing entries.
|
|
9
|
+
|
|
10
|
+
Intended to be used as in:
|
|
11
|
+
|
|
12
|
+
export PATH=$(addpath $PATH --add /opt/bin)
|
|
13
|
+
"""
|
|
14
|
+
################################################################################
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
import argparse
|
|
19
|
+
|
|
20
|
+
################################################################################
|
|
21
|
+
|
|
22
|
+
def pathmod(pathentries, separator, pathlist, prefix=False, suffix=False, delete=False, force=False):
|
|
23
|
+
""" Modify a path.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Only do something if the list of paths to add exists
|
|
27
|
+
|
|
28
|
+
if pathlist:
|
|
29
|
+
# Join the list path entries together then split them into individual entries
|
|
30
|
+
# Allows for a list of entries in the form ['a:b:c', 'd', 'e']
|
|
31
|
+
|
|
32
|
+
paths = separator.join(pathlist).split(separator)
|
|
33
|
+
|
|
34
|
+
# Process each entry
|
|
35
|
+
|
|
36
|
+
for entry in paths:
|
|
37
|
+
# Do nothing (except delete) if the path does not exist and we aren't forcing
|
|
38
|
+
|
|
39
|
+
if not entry or (not os.path.isdir(entry) and not (force or delete)):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# If we are removing or adding/moving an entry remove any existing entry
|
|
43
|
+
|
|
44
|
+
if (delete or prefix or suffix) and entry in pathentries:
|
|
45
|
+
pathentries.remove(entry)
|
|
46
|
+
|
|
47
|
+
# Prefix or suffix the entry
|
|
48
|
+
|
|
49
|
+
if not delete and entry not in pathentries:
|
|
50
|
+
if suffix:
|
|
51
|
+
pathentries.append(entry)
|
|
52
|
+
else:
|
|
53
|
+
pathentries.insert(0, entry)
|
|
54
|
+
|
|
55
|
+
return pathentries
|
|
56
|
+
|
|
57
|
+
################################################################################
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
""" Main function - handles command line, outputs result to stdout """
|
|
61
|
+
|
|
62
|
+
parser = argparse.ArgumentParser(description='Add or remove entries from a path list (e.g. as used by the PATH environment variable)')
|
|
63
|
+
parser.add_argument('--add', action='append', help='Add an entry to the front of the path (do nothing if it is already present in the path)')
|
|
64
|
+
parser.add_argument('--prefix', action='append', help='Add an entry to the front of the path (or move it there if it is already present)')
|
|
65
|
+
parser.add_argument('--suffix', action='append', help='Add an entry to the end of the path (or move it there if it is already present)')
|
|
66
|
+
parser.add_argument('--remove', action='append', help='Remove an entry from the path (do nothing if it is not present')
|
|
67
|
+
parser.add_argument('--force', default=False, help='Add entries even if a corresponding directory does not exist')
|
|
68
|
+
parser.add_argument('--separator', action='store', default=':', help='Override the default path separator')
|
|
69
|
+
parser.add_argument('path', nargs=1, help='The path to modify')
|
|
70
|
+
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
|
|
73
|
+
# Split the given path into component parts
|
|
74
|
+
|
|
75
|
+
pathsplit = [pathentry for pathentry in args.path[0].split(args.separator) if pathentry]
|
|
76
|
+
|
|
77
|
+
# Process the additions, suffixations, prefixanisms and deletes.
|
|
78
|
+
|
|
79
|
+
pathmod(pathsplit, args.separator, args.add)
|
|
80
|
+
pathmod(pathsplit, args.separator, args.prefix, prefix=True)
|
|
81
|
+
pathmod(pathsplit, args.separator, args.suffix, suffix=True)
|
|
82
|
+
pathmod(pathsplit, args.separator, args.remove, delete=True)
|
|
83
|
+
|
|
84
|
+
# Glue the path back together
|
|
85
|
+
|
|
86
|
+
pathjoin = args.separator.join(pathsplit)
|
|
87
|
+
|
|
88
|
+
# Output the updated path to stdout
|
|
89
|
+
|
|
90
|
+
print(pathjoin)
|
|
91
|
+
|
|
92
|
+
################################################################################
|
|
93
|
+
|
|
94
|
+
def addpath():
|
|
95
|
+
"""Entry point"""
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
main()
|
|
99
|
+
except KeyboardInterrupt:
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
except BrokenPipeError:
|
|
102
|
+
sys.exit(2)
|
|
103
|
+
|
|
104
|
+
################################################################################
|
|
105
|
+
|
|
106
|
+
if __name__ == '__main__':
|
|
107
|
+
addpath()
|
skilleter_thingy/aws.py
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Simple AWS module - provides an abstraction layer on top of the boto3 client
|
|
5
|
+
for simple operations such as reading/writing S3 buckets, sending emails
|
|
6
|
+
etc.
|
|
7
|
+
|
|
8
|
+
Currently this is a random collection of functions extracted from the S3
|
|
9
|
+
bucket ACL checking code but it is hoped it will evolve over time into
|
|
10
|
+
something more comprehensive.
|
|
11
|
+
|
|
12
|
+
Author: John Skilleter
|
|
13
|
+
"""
|
|
14
|
+
################################################################################
|
|
15
|
+
|
|
16
|
+
# System modules
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import logging
|
|
20
|
+
import json
|
|
21
|
+
|
|
22
|
+
# AWS modules
|
|
23
|
+
|
|
24
|
+
import boto3
|
|
25
|
+
import botocore
|
|
26
|
+
|
|
27
|
+
################################################################################
|
|
28
|
+
|
|
29
|
+
class GenericAWS:
|
|
30
|
+
""" Class providing generic S3 functionality from which the
|
|
31
|
+
other S3 classes are derived """
|
|
32
|
+
|
|
33
|
+
################################################################################
|
|
34
|
+
|
|
35
|
+
def __init__(self, service, session=None, profile=None, createresource=True, createclient=True, region=None):
|
|
36
|
+
""" Initialisation - just create a client handle, optionally using
|
|
37
|
+
the specified profile """
|
|
38
|
+
|
|
39
|
+
if session:
|
|
40
|
+
self.session = session
|
|
41
|
+
elif profile:
|
|
42
|
+
self.session = boto3.session.Session(profile_name=profile)
|
|
43
|
+
else:
|
|
44
|
+
self.session = boto3
|
|
45
|
+
|
|
46
|
+
args = {}
|
|
47
|
+
if region:
|
|
48
|
+
args['region_name'] = region
|
|
49
|
+
|
|
50
|
+
if createresource:
|
|
51
|
+
self.resource = self.session.resource(service, **args)
|
|
52
|
+
|
|
53
|
+
if createclient:
|
|
54
|
+
self.client = self.session.client(service, **args)
|
|
55
|
+
|
|
56
|
+
self.log = logging.getLogger('%s:%s' % (__name__, service))
|
|
57
|
+
|
|
58
|
+
################################################################################
|
|
59
|
+
|
|
60
|
+
def set_log_level(self, level):
|
|
61
|
+
""" Set logging for the module """
|
|
62
|
+
|
|
63
|
+
self.log.setLevel(level)
|
|
64
|
+
|
|
65
|
+
################################################################################
|
|
66
|
+
|
|
67
|
+
class SES(GenericAWS):
|
|
68
|
+
""" Class for AWS Simple Email Service """
|
|
69
|
+
|
|
70
|
+
################################################################################
|
|
71
|
+
|
|
72
|
+
def __init__(self, session=None, profile=None):
|
|
73
|
+
""" Initialisation """
|
|
74
|
+
|
|
75
|
+
super().__init__('ses', session, profile, createresource=False)
|
|
76
|
+
|
|
77
|
+
################################################################################
|
|
78
|
+
|
|
79
|
+
def send(self, sender, recipient, subject, body):
|
|
80
|
+
""" Send an email """
|
|
81
|
+
|
|
82
|
+
destination = \
|
|
83
|
+
{
|
|
84
|
+
'ToAddresses': [recipient]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
message = \
|
|
88
|
+
{
|
|
89
|
+
'Subject':
|
|
90
|
+
{
|
|
91
|
+
'Data': subject
|
|
92
|
+
},
|
|
93
|
+
'Body':
|
|
94
|
+
{
|
|
95
|
+
'Text':
|
|
96
|
+
{
|
|
97
|
+
'Data': body
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Attempt to send the email - just report the error and quit on failure
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
self.log.info('Sending email from %s to %s', sender, recipient)
|
|
106
|
+
|
|
107
|
+
self.client.send_email(Source=sender,
|
|
108
|
+
Destination=destination,
|
|
109
|
+
Message=message)
|
|
110
|
+
|
|
111
|
+
except (botocore.exceptions.EndpointConnectionError,
|
|
112
|
+
self.client.exceptions.MessageRejected) as err:
|
|
113
|
+
print('')
|
|
114
|
+
print('Error sending email: %sn' % err)
|
|
115
|
+
print(' Sender: %s' % sender)
|
|
116
|
+
print(' Recipient: %s' % recipient)
|
|
117
|
+
print(' Subject: %s' % subject)
|
|
118
|
+
print(' Body:')
|
|
119
|
+
|
|
120
|
+
for txt in body.split('\n'):
|
|
121
|
+
print(' %s' % txt)
|
|
122
|
+
|
|
123
|
+
print('')
|
|
124
|
+
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
################################################################################
|
|
128
|
+
|
|
129
|
+
class S3Bucket(GenericAWS):
|
|
130
|
+
""" Class providing access to S3 buckets """
|
|
131
|
+
|
|
132
|
+
################################################################################
|
|
133
|
+
|
|
134
|
+
def __init__(self, session=None, profile=None):
|
|
135
|
+
""" Initialisation - just create a client handle, optionally using
|
|
136
|
+
the specified profile """
|
|
137
|
+
|
|
138
|
+
super().__init__('s3', session, profile)
|
|
139
|
+
|
|
140
|
+
################################################################################
|
|
141
|
+
|
|
142
|
+
def read(self, bucket, key):
|
|
143
|
+
""" Read the specified data from the specified bucket.
|
|
144
|
+
Returns the data, or raises a boto3 exception on error. """
|
|
145
|
+
|
|
146
|
+
self.log.info('Get object from key %s in bucket %s', key, bucket)
|
|
147
|
+
|
|
148
|
+
response = self.client.get_object(Bucket=bucket, Key=key)
|
|
149
|
+
|
|
150
|
+
self.log.debug('Get object: %s', response)
|
|
151
|
+
|
|
152
|
+
return response['Body'].read()
|
|
153
|
+
|
|
154
|
+
################################################################################
|
|
155
|
+
|
|
156
|
+
def write(self, bucket, key, data):
|
|
157
|
+
""" Write data into the specified key of the specified bucket
|
|
158
|
+
Raises a boto3 exception on error. """
|
|
159
|
+
|
|
160
|
+
self.log.info('Writing %d bytes of data to key %s in bucket %s', len(data), key, bucket)
|
|
161
|
+
|
|
162
|
+
self.client.put_object(Bucket=bucket, Key=key, Body=data)
|
|
163
|
+
|
|
164
|
+
################################################################################
|
|
165
|
+
|
|
166
|
+
def get_tags(self, bucket):
|
|
167
|
+
""" Read the tags from a bucket. Returns a dictionary of tags, which will be
|
|
168
|
+
empty if the bucket has no tags. """
|
|
169
|
+
|
|
170
|
+
self.log.info('Reading tags from bucket %s', bucket)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
tags = self.client.get_bucket_tagging(Bucket=bucket)['TagSet']
|
|
174
|
+
|
|
175
|
+
except botocore.exceptions.ClientError as err:
|
|
176
|
+
# Any exception except NoSuchTagSet gets raised.
|
|
177
|
+
# NoSuchTagSet simply means that the bucket has no tags
|
|
178
|
+
# and isn't really an error, as such, so we just return
|
|
179
|
+
# an empty dictionary.
|
|
180
|
+
|
|
181
|
+
if err.response['Error']['Code'] != 'NoSuchTagSet':
|
|
182
|
+
self.log.error('Error reading tags for bucket %s: %s', bucket, err.response['Error']['Code'])
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
tags = {}
|
|
186
|
+
|
|
187
|
+
self.log.info('Tags: %s', tags)
|
|
188
|
+
|
|
189
|
+
return tags
|
|
190
|
+
|
|
191
|
+
################################################################################
|
|
192
|
+
|
|
193
|
+
def get_location(self, bucket):
|
|
194
|
+
""" Return the location of a bucket - note that the Boto3 returns the location
|
|
195
|
+
set to none, rather than us-east-1 if you query a bucket located in that
|
|
196
|
+
region. """
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
location = self.client.get_bucket_location(Bucket=bucket)['LocationConstraint']
|
|
200
|
+
except botocore.exceptions.ClientError as err:
|
|
201
|
+
self.log.info('Error getting location of bucket %s: %s', bucket, err.response['Error']['Code'])
|
|
202
|
+
raise
|
|
203
|
+
|
|
204
|
+
if not location:
|
|
205
|
+
location = 'us-east-1'
|
|
206
|
+
|
|
207
|
+
return location
|
|
208
|
+
|
|
209
|
+
################################################################################
|
|
210
|
+
|
|
211
|
+
def get_acl(self, bucket):
|
|
212
|
+
""" Return the bucket ACLs """
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
return self.client.get_bucket_acl(Bucket=bucket)['Grants']
|
|
216
|
+
except botocore.exceptions.ClientError as err:
|
|
217
|
+
self.log.error('Error getting ACL for bucket %s: %s', bucket, err.response['Error']['Code'])
|
|
218
|
+
raise
|
|
219
|
+
|
|
220
|
+
################################################################################
|
|
221
|
+
|
|
222
|
+
def get_policy(self, bucket):
|
|
223
|
+
""" Return bucket policy information as a list of policy dictionaries
|
|
224
|
+
Returns an empty list if the bucket has no policies (similar to
|
|
225
|
+
get_tags() above). """
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
policy_data = self.client.get_bucket_policy(Bucket=bucket)
|
|
229
|
+
|
|
230
|
+
except botocore.exceptions.ClientError as err:
|
|
231
|
+
# Any exception which *isn't* a no-policy exception gets raised
|
|
232
|
+
# to the caller, the no-policy one causes the function to return
|
|
233
|
+
# an empty policy list.
|
|
234
|
+
|
|
235
|
+
if err.response['Error']['Code'] != 'NoSuchBucketPolicy':
|
|
236
|
+
self.log.error('Error reading policy for bucket %s: %s', bucket, err.response['Error']['Code'])
|
|
237
|
+
raise
|
|
238
|
+
|
|
239
|
+
return []
|
|
240
|
+
|
|
241
|
+
return json.loads(policy_data['Policy'])
|
|
242
|
+
|
|
243
|
+
################################################################################
|
|
244
|
+
|
|
245
|
+
def get_buckets(self):
|
|
246
|
+
""" Return a list of all the available buckets """
|
|
247
|
+
|
|
248
|
+
return [bucket.name for bucket in self.resource.buckets.all()]
|
|
249
|
+
|
|
250
|
+
################################################################################
|
|
251
|
+
|
|
252
|
+
def get_website(self, bucket):
|
|
253
|
+
""" Return the web site configuration for the bucket, or None if the bucket
|
|
254
|
+
is not configured for hosting """
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
web = self.client.get_bucket_website(Bucket=bucket)
|
|
258
|
+
|
|
259
|
+
except botocore.exceptions.ClientError as err:
|
|
260
|
+
if err.response['Error']['Code'] != 'NoSuchWebsiteConfiguration':
|
|
261
|
+
print('>>>%s' % err.response)
|
|
262
|
+
raise
|
|
263
|
+
|
|
264
|
+
web = None
|
|
265
|
+
|
|
266
|
+
return web
|
|
267
|
+
|
|
268
|
+
################################################################################
|
|
269
|
+
|
|
270
|
+
def get_objects(self, bucket, max_objects=None):
|
|
271
|
+
""" Yield a list of the details of the objects in a bucket, stopping after
|
|
272
|
+
returning max_objects (if specified). """
|
|
273
|
+
|
|
274
|
+
paginator = self.client.get_paginator('list_objects_v2')
|
|
275
|
+
|
|
276
|
+
pagesize = min(25, max_objects) if max_objects else 25
|
|
277
|
+
|
|
278
|
+
objects = paginator.paginate(Bucket=bucket, PaginationConfig={'PageSize': pagesize})
|
|
279
|
+
|
|
280
|
+
count = 0
|
|
281
|
+
|
|
282
|
+
for data in objects:
|
|
283
|
+
if 'Contents' in data:
|
|
284
|
+
for obj in data['Contents']:
|
|
285
|
+
|
|
286
|
+
if max_objects:
|
|
287
|
+
count += 1
|
|
288
|
+
if count > max_objects:
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
yield obj
|
|
292
|
+
|
|
293
|
+
################################################################################
|
|
294
|
+
|
|
295
|
+
def get_object_acl(self, bucket, obj):
|
|
296
|
+
""" Return the ACL data for an object in a bucket """
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
return self.client.get_object_acl(Bucket=bucket, Key=obj)['Grants']
|
|
300
|
+
except botocore.exceptions.ClientError as err:
|
|
301
|
+
self.log.error('Error getting ACL for object %s in bucket %s: %s', obj, bucket, err.response['Error']['Code'])
|
|
302
|
+
raise
|
|
303
|
+
|
|
304
|
+
################################################################################
|
|
305
|
+
|
|
306
|
+
def get_lifecycle(self, bucket):
|
|
307
|
+
""" Return the bucket lifecycle data """
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
lifecycle = self.client.get_bucket_lifecycle_configuration(Bucket=bucket)
|
|
311
|
+
except botocore.exceptions.ClientError as err:
|
|
312
|
+
if err.response['Error']['Code'] == 'NoSuchLifecycleConfiguration':
|
|
313
|
+
lifecycle = None
|
|
314
|
+
else:
|
|
315
|
+
raise
|
|
316
|
+
|
|
317
|
+
return lifecycle
|
|
318
|
+
|
|
319
|
+
################################################################################
|
|
320
|
+
|
|
321
|
+
class STS(GenericAWS):
|
|
322
|
+
""" Class providing access to STS functionality """
|
|
323
|
+
|
|
324
|
+
def __init__(self, session=None, profile=None):
|
|
325
|
+
""" Initialise the STS client (there is no STS resource in boto3) """
|
|
326
|
+
|
|
327
|
+
super().__init__('sts', session, profile, createresource=False)
|
|
328
|
+
|
|
329
|
+
################################################################################
|
|
330
|
+
|
|
331
|
+
def account(self):
|
|
332
|
+
""" Return the name of the current AWS account """
|
|
333
|
+
|
|
334
|
+
return self.client.get_caller_identity()['Account']
|
|
335
|
+
|
|
336
|
+
################################################################################
|
|
337
|
+
|
|
338
|
+
class IAM(GenericAWS):
|
|
339
|
+
""" Class providing access to IAM """
|
|
340
|
+
|
|
341
|
+
def __init__(self, session=None, profile=None):
|
|
342
|
+
""" Initialise the IAM client/resource """
|
|
343
|
+
|
|
344
|
+
super().__init__('iam', session, profile, createresource=False)
|
|
345
|
+
|
|
346
|
+
def role_exists(self, role):
|
|
347
|
+
""" Return True if the role exists """
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
self.client.get_role(RoleName=role)
|
|
351
|
+
except botocore.exceptions.ClientError as err:
|
|
352
|
+
if err.response['Error']['Code'] != 'NoSuchEntity':
|
|
353
|
+
raise
|
|
354
|
+
|
|
355
|
+
return False
|
|
356
|
+
else:
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
def create_role(self, role, description, policy):
|
|
360
|
+
""" Create a new role """
|
|
361
|
+
|
|
362
|
+
response = self.client.create_role(RoleName=role, Description=description, AssumeRolePolicyDocument=policy)
|
|
363
|
+
|
|
364
|
+
return response
|
|
365
|
+
|
|
366
|
+
def put_role_policy(self, role, policy_name, policy):
|
|
367
|
+
""" Update the policy in an existing role """
|
|
368
|
+
|
|
369
|
+
response = self.client.put_role_policy(RoleName=role, PolicyName=policy_name, PolicyDocument=policy)
|
|
370
|
+
|
|
371
|
+
return response
|
|
372
|
+
|
|
373
|
+
def get_role_policy(self, role, policy_name):
|
|
374
|
+
""" Read the policy in an existing role """
|
|
375
|
+
|
|
376
|
+
response = self.client.get_role_policy(RoleName=role, PolicyName=policy_name)
|
|
377
|
+
|
|
378
|
+
return response
|
|
379
|
+
|
|
380
|
+
################################################################################
|
|
381
|
+
|
|
382
|
+
class Events(GenericAWS):
|
|
383
|
+
""" Class providing access to CloudWatch Events """
|
|
384
|
+
|
|
385
|
+
def __init__(self, session=None, profile=None):
|
|
386
|
+
""" Initialise the CloudWatch Events client/resource """
|
|
387
|
+
|
|
388
|
+
super().__init__('events', session, profile, createresource=False)
|
|
389
|
+
|
|
390
|
+
def put_rule(self, name, schedule):
|
|
391
|
+
""" Create or update the specified rule """
|
|
392
|
+
|
|
393
|
+
self.client.put_rule(Name=name, ScheduleExpression=schedule)
|
|
394
|
+
|
|
395
|
+
def put_target(self, name, targets):
|
|
396
|
+
""" Add the specified target(s) to the specified rule, or update
|
|
397
|
+
existing targets """
|
|
398
|
+
|
|
399
|
+
self.client.put_targets(Rule=name, Targets=targets)
|
|
400
|
+
|
|
401
|
+
################################################################################
|
|
402
|
+
|
|
403
|
+
DEFAULT_LAMBDA_TIMEOUT = 3
|
|
404
|
+
DEFAULT_LAMBDA_MEMORY = 128
|
|
405
|
+
DEFAULT_LAMBDA_HANDLER = 'main.main'
|
|
406
|
+
DEFAULT_LAMBDA_RUNTIME = 'python3.6'
|
|
407
|
+
|
|
408
|
+
class Lambda(GenericAWS):
|
|
409
|
+
""" Class providing access to Lambda functions """
|
|
410
|
+
|
|
411
|
+
def __init__(self, session=None, profile=None, region=None):
|
|
412
|
+
""" Initialise the Lambda client/resource """
|
|
413
|
+
|
|
414
|
+
super().__init__('lambda', session, profile, createresource=False, region=region)
|
|
415
|
+
|
|
416
|
+
def exists(self, name):
|
|
417
|
+
""" Return True if the specified Lambda function exists """
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
self.client.get_function(FunctionName=name)
|
|
421
|
+
except botocore.exceptions.ClientError as err:
|
|
422
|
+
if err.response['Error']['Code'] != 'ResourceNotFoundException':
|
|
423
|
+
raise
|
|
424
|
+
return False
|
|
425
|
+
else:
|
|
426
|
+
return True
|
|
427
|
+
|
|
428
|
+
def update_function(self, name, zipfile):
|
|
429
|
+
""" Update the specified Lambda function given a zip file """
|
|
430
|
+
|
|
431
|
+
with open(zipfile, 'rb') as zipper:
|
|
432
|
+
zipdata = zipper.read()
|
|
433
|
+
|
|
434
|
+
response = self.client.update_function_code(FunctionName=name, ZipFile=zipdata)
|
|
435
|
+
|
|
436
|
+
return response
|
|
437
|
+
|
|
438
|
+
def update_function_configuration(self, name,
|
|
439
|
+
handler=None,
|
|
440
|
+
environment=None):
|
|
441
|
+
""" Update the handler associated with a Lambda fucntion """
|
|
442
|
+
|
|
443
|
+
update_args = {}
|
|
444
|
+
update_args['FunctionName'] = name
|
|
445
|
+
|
|
446
|
+
if handler:
|
|
447
|
+
update_args['Handler'] = handler
|
|
448
|
+
|
|
449
|
+
if environment:
|
|
450
|
+
update_args['Environment'] = {'Variables': environment}
|
|
451
|
+
|
|
452
|
+
self.client.update_function_configuration(**update_args)
|
|
453
|
+
|
|
454
|
+
def create_function(self, name, role, description, zipfile,
|
|
455
|
+
runtime=DEFAULT_LAMBDA_RUNTIME,
|
|
456
|
+
handler=DEFAULT_LAMBDA_HANDLER,
|
|
457
|
+
timeout=DEFAULT_LAMBDA_TIMEOUT,
|
|
458
|
+
memory=DEFAULT_LAMBDA_MEMORY):
|
|
459
|
+
""" Create the specified Lambda function given a zip file"""
|
|
460
|
+
|
|
461
|
+
with open(zipfile, 'rb') as zipper:
|
|
462
|
+
zipdata = zipper.read()
|
|
463
|
+
|
|
464
|
+
response = self.client.create_function(FunctionName=name,
|
|
465
|
+
Runtime=runtime,
|
|
466
|
+
Role=role,
|
|
467
|
+
Handler=handler,
|
|
468
|
+
Code={'ZipFile': zipdata},
|
|
469
|
+
Description=description,
|
|
470
|
+
Timeout=timeout,
|
|
471
|
+
MemorySize=memory)
|
|
472
|
+
|
|
473
|
+
return response
|
|
474
|
+
|
|
475
|
+
################################################################################
|
|
476
|
+
|
|
477
|
+
def get_session(profile_name=None):
|
|
478
|
+
""" Wrapper for boto3.session.Session """
|
|
479
|
+
|
|
480
|
+
return boto3.session.Session(profile_name=profile_name)
|
|
481
|
+
|
|
482
|
+
################################################################################
|
|
483
|
+
|
|
484
|
+
def set_stream_logger(name='botocore', level=10, format_string=None):
|
|
485
|
+
""" Wrapper for boto3.set_stream_logger """
|
|
486
|
+
|
|
487
|
+
return boto3.set_stream_logger(name=name, level=level, format_string=format_string)
|
|
488
|
+
|
|
489
|
+
################################################################################
|
|
490
|
+
|
|
491
|
+
def set_default_region(region):
|
|
492
|
+
""" Set the default region for the module """
|
|
493
|
+
|
|
494
|
+
boto3.setup_default_session(region_name=region)
|
|
495
|
+
|
|
496
|
+
################################################################################
|
|
497
|
+
|
|
498
|
+
def get_regions(servicename):
|
|
499
|
+
""" Generate a list of regions where a service is supported """
|
|
500
|
+
|
|
501
|
+
return boto3.session.Session().get_available_regions(servicename)
|
|
502
|
+
|
|
503
|
+
################################################################################
|
|
504
|
+
|
|
505
|
+
def get_resources():
|
|
506
|
+
""" Devious method of getting the list of clients supported by boto3
|
|
507
|
+
Note that I'm somewhat embarrassed by this, but can't find a better
|
|
508
|
+
way of doing it! """
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
boto3.resource('XXX-NOT-A-RESOURCE-XXX')
|
|
512
|
+
except boto3.exceptions.ResourceNotExistsError as err:
|
|
513
|
+
return str(err).replace(' - ', '').split('\n')[2:-1]
|
|
514
|
+
|
|
515
|
+
################################################################################
|
|
516
|
+
|
|
517
|
+
def get_clients():
|
|
518
|
+
""" Similarly devious way of getting the list of supported clients.
|
|
519
|
+
This is equally as yukky as the get_resources() function. """
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
boto3.client('XXX-NOT-A-CLIENT-XXX')
|
|
523
|
+
except botocore.exceptions.UnknownServiceError as err:
|
|
524
|
+
return repr(err)[:-3].split(': ')[2].split(', ')
|
|
525
|
+
|
|
526
|
+
################################################################################
|
|
527
|
+
|
|
528
|
+
def test_function():
|
|
529
|
+
""" Module test function """
|
|
530
|
+
|
|
531
|
+
# Test STS
|
|
532
|
+
|
|
533
|
+
sts_client = STS()
|
|
534
|
+
|
|
535
|
+
print('Current account: %s' % sts_client.account())
|
|
536
|
+
|
|
537
|
+
# TODO: Test SES
|
|
538
|
+
# TODO: Test S3
|
|
539
|
+
# TODO: Test IAM
|
|
540
|
+
# TODO: Test Cloudwatch Events
|
|
541
|
+
# TODO: Test Lambda
|
|
542
|
+
# TODO: Test set_default_region(), get_session(), get_resources(), get_regions(), get_clients()
|
|
543
|
+
|
|
544
|
+
################################################################################
|
|
545
|
+
# Test code
|
|
546
|
+
|
|
547
|
+
if __name__ == '__main__':
|
|
548
|
+
test_function()
|