-
Notifications
You must be signed in to change notification settings - Fork 8
/
lino.py
271 lines (222 loc) · 8.51 KB
/
lino.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
import argparse
import linode.api
import json
import os
import requests
import sys
import yaml
from distutils.util import strtobool
# for the server domain and linode datacenter settings
vars_file = 'vars/conf.yml'
# cloudflare settings
cloudflare_url = 'https://api.cloudflare.com/client/v4'
# payment_term should always be 1, artifact of bad linode-python code
payment_term = 1
# these need to be in the environment
api_key = os.environ['LINODE_API_KEY']
cloudflare_api_key = os.environ['CF_KEY']
cloudflare_email = os.environ['CF_EMAIL']
cloudflare_zone_identifier = os.environ['CF_ZONE_IDENTIFIER']
with open(vars_file, 'r') as f:
doc = yaml.load(f)
server_domain = doc['server_domain']
datacenter = doc['linode_datacenter']
# SSL key/certs for the load balancer
key_file = '../dehydrated/certs/{0}/privkey.pem'.format(server_domain)
fullchain_file = '../dehydrated/certs/{0}/fullchain.pem'.format(server_domain)
if os.path.exists(key_file):
with open(key_file, 'r') as f:
sslkey = f.read()
else:
sslkey = False
if os.path.exists(fullchain_file):
with open(fullchain_file, 'r') as f:
sslcert = f.read()
else:
sslcert = False
api = linode.api.Api(api_key)
cloudflare_headers = {'X-Auth-Email': cloudflare_email,
'X-Auth-Key': cloudflare_api_key,
'Content-Type': 'application/json'}
def nodebalancer_config(balancerid, **kwargs):
# https://www.linode.com/api/nodebalancer/nodebalancer.config.create
port = kwargs.get('port', 80)
protocol = kwargs.get('protocol', 'http')
algorithm = kwargs.get('algorithm', 'roundrobin')
check = kwargs.get('check', 'connection')
check_path = kwargs.get('check_path', '/version.txt')
stickiness = kwargs.get('stickiness', 'table')
config = {
'NodeBalancerID': balancerid,
'Port': port,
'Protocol': protocol,
'Algorithm': algorithm,
'check': check,
'check_path': check_path,
'stickiness': stickiness,
}
if protocol == 'https':
if sslcert and sslkey:
config['Port'] = 443
config['ssl_cert'] = sslcert
config['ssl_key'] = sslkey
config['cipher_suite'] = 'recommended'
else:
sys.exit('SSL Cert or SSL Key not loaded, check that {0}\n and {1} exist.'.format(key_file, fullchain_file))
return config
def main():
parser = argparse.ArgumentParser(
description="""
python lino.py listnodes (list linodes with their IPs)
python lino.py delete thunder1, thunder2, ... (deletes linodes by label)
python lino.py nodecreate thunder1, thunder2, ... (creates node balancer for labeled nodes)
python lino.py reboot thunder1, thunder2, ...(reboots linodes by label)
""", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('action', nargs='+')
args = parser.parse_args()
if args.action[0] == 'delete':
# slice off the first argument since its the command itself
delete(args.action[1:])
elif args.action[0] == 'forcedelete':
# slice off the first argument since its the command itself
delete(args.action[1:], force=True)
elif args.action[0] == 'listnodes':
listnodes()
elif args.action[0] == 'nodecreate':
nodebalancercreate(args.action[1:])
elif args.action[0] == 'reboot':
reboot(args.action[1:])
else:
sys.exit('No known action requested. Maybe you meant delete, listnodes, or nodecreate?')
def cf_get_identifier(hostname):
# Get the identifier for an A record, takes the first one listed by cf
identifier_url = "{0}/zones/{1}/dns_records".format(cloudflare_url, cloudflare_zone_identifier)
response = requests.get(identifier_url, headers=cloudflare_headers)
fullname = hostname + '.{0}'.format(server_domain)
if response:
data = json.loads(response.text)
for record in data['result']:
if record['name'] == fullname:
return record['id']
return False
else:
return False
def cf_delete_dns_record(record_id):
# Delete cloudflare A record by identifier from cf_get_identifier
delete_url = "{0}/zones/{1}/dns_records/{2}".format(cloudflare_url, cloudflare_zone_identifier, record_id)
response = requests.delete(delete_url, headers=cloudflare_headers)
if response:
data = json.loads(response.text)
if data['success']:
return True
else:
return False
else:
return False
def reboot(labels):
if not labels:
sys.exit('You must enter the labels to reboot')
vpsids = {}
toreboot = {}
for vps in api.linode_list():
vpsids[vps['LABEL']] = vps['LINODEID']
for label in labels:
for k, v in vpsids.iteritems():
if label in k:
toreboot[label] = v
for l in toreboot.values():
r = api.linode_reboot(LinodeID=l)
print r
def nodehasprivateip(linodeid):
for ip in api.linode_ip_list():
if not ip['ISPUBLIC'] and ip['LINODEID'] == linodeid:
return ip['IPADDRESS']
r = api.linode_ip_addprivate(LinodeID=linodeid)
a = api.linode_reboot(LinodeID=linodeid)
print a
return r['IPADDRESS']
def nodebalancercreate(labels):
if not labels:
sys.exit('You must enter the labels of the webheads.')
vpsids = {}
toadd = {}
webheads = {}
configs = {}
for vps in api.linode_list():
vpsids[vps['LABEL']] = vps['LINODEID']
for label in labels:
for k, v in vpsids.iteritems():
if label in k:
toadd[label] = v
for k, v in toadd.iteritems():
webheads[k] = nodehasprivateip(v)
r = api.nodebalancer_create(DatacenterID=datacenter, PaymentTerm=payment_term)
if r['NodeBalancerID']:
port = 80
a = api.nodebalancer_config_create(**nodebalancer_config(r['NodeBalancerID'], protocol='tcp'))
else:
sys.exit('No node balancer id after creation attempt, something failed majorly.')
if a['ConfigID']:
configs[a['ConfigID']] = port
else:
sys.exit('Failed trying to create HTTP config for node balancer.')
port = 443
a = api.nodebalancer_config_create(**nodebalancer_config(r['NodeBalancerID'], protocol='tcp', port=port))
if a['ConfigID']:
configs[a['ConfigID']] = port
else:
sys.exit('Failed trying to create HTTPS (SSL) config for node balancer.')
# Add the nodes to the load balancer
for l, ip in webheads.iteritems():
for cid, port in configs.iteritems():
a = api.nodebalancer_node_create(ConfigID=cid, Label=l, Address='{0}:{1}'.format(ip, port))
if not a['NodeID']:
sys.exit('Failed trying to add a node to the load balancer ID: {0}'.format(cid))
print "Node Balancer configs created:{0}".format(configs)
def listnodes():
vpslist = {}
for ips in api.linode_ip_list():
linodeid = ips['LINODEID']
label = api.linode_list(LINODEID=linodeid)[0]['LABEL']
ip = ips['IPADDRESS']
public = ips['ISPUBLIC']
if not vpslist.get(label):
vpslist[label] = {}
vpslist[label][linodeid] = []
vpslist[label][linodeid].append(dict(ISPUBLIC=public, IPADDRESS=ip))
else:
vpslist[label][linodeid].append(dict(ISPUBLIC=public, IPADDRESS=ip))
for key, value in vpslist.iteritems():
vpslist[key]
print '{0}: {1}'.format(key, value)
def delete(labels, force=False):
vpsids = {}
todelete = {} # dict label:linodeid of the nodes to be deleted
for vps in api.linode_list():
vpsids[vps['LABEL']] = vps['LINODEID']
for label in labels:
for key, value in vpsids.iteritems():
if label in key:
todelete[label] = value
if todelete:
print todelete
if force or prompt('Are you sure you want to delete the above instances?'):
for key, value in todelete.iteritems():
r = api.linode_delete(LINODEID=value, skipChecks=1)
if r:
print r
if cf_delete_dns_record(cf_get_identifier(key)):
print "{0} DNS record deleted.".format(key)
else:
print "Can't delete, no linodes matched: " + " ".join(labels[1:])
def prompt(query):
sys.stdout.write('%s [y/n]: ' % query)
val = raw_input()
try:
ret = strtobool(val)
except ValueError:
sys.stdout.write('Please answer with a y/n\n')
return prompt(query)
return ret
if __name__ == '__main__':
main()