From ceb14050a3d292f01cc77962f5768721727d6257 Mon Sep 17 00:00:00 2001 From: Josh Wright Date: Fri, 1 Mar 2013 14:42:50 -0500 Subject: [PATCH 01/34] Add a License files --- LICENSE | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee3c49a --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +A Python library to perform low-level Linode API functions. + +Copyright (c) 2010 Timothy J Fontaine +Copyright (c) 2010 Josh Wright +Copyright (c) 2010 Ryan Tucker +Copyright (c) 2008 James C Sinclair + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. From 9ece49fb3000696a820e9ed403d68a4efd376ab7 Mon Sep 17 00:00:00 2001 From: Josh Wright Date: Fri, 1 Mar 2013 14:44:50 -0500 Subject: [PATCH 02/34] Add a note about the license to the Readme --- README | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README b/README index e4ea56f..32eacf9 100644 --- a/README +++ b/README @@ -26,3 +26,9 @@ Batching Batching should be used with care, once enabled all api calls are cached until Api.batchFlush() is called, however you must remember the order in which calls were made as that's the order of the list returned to you + +License +------- + +This code is provided under an MIT-style license. Please refer to the LICENSE +file in the root of the project for specifics. From fdb08846e88c6f5b4d1d27eb114fd42acfce8ea8 Mon Sep 17 00:00:00 2001 From: Tim Heckman Date: Fri, 15 Mar 2013 00:11:48 -0700 Subject: [PATCH 03/34] add support for requests module --- linode/api.py | 65 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/linode/api.py b/linode/api.py index adf93c4..01cdb50 100755 --- a/linode/api.py +++ b/linode/api.py @@ -7,6 +7,7 @@ Copyright (c) 2010 Josh Wright Copyright (c) 2010 Ryan Tucker Copyright (c) 2008 James C Sinclair +Copyright (c) 2013 Tim Heckman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -41,30 +42,48 @@ import simplejson as json try: - import VEpycurl - def vepycurl_request(url, fields, headers): - return (url, fields, headers) - - def vepycurl_open(request): - c = VEpycurl.VEpycurl(verifySSL=2) - url, fields, headers = request - nh = [ '%s: %s' % (k, v) for k,v in headers.items()] - c.perform(url, fields, nh) - return c.results() - - URLOPEN = vepycurl_open - URLREQUEST = vepycurl_request + import requests + from types import MethodType + + def requests_request(url, fields, headers): + return requests.Request(method="POST", url=url, headers=headers, data=fields) + + def requests_open(request): + r = request.prepare() + s = requests.Session() + s.verify = True + response = s.send(r) + response.read = MethodType(lambda x: x.text, response) + return response + + URLOPEN = requests_open + URLREQUEST = requests_request except: - import warnings - ssl_message = 'using urllib instead of pycurl, urllib does not verify SSL remote certificates, there is a risk of compromised communication' - warnings.warn(ssl_message, RuntimeWarning) - - def urllib_request(url, fields, headers): - fields = urllib.urlencode(fields) - return urllib2.Request(url, fields, headers) - - URLOPEN = urllib2.urlopen - URLREQUEST = urllib_request + try: + import VEpycurl + def vepycurl_request(url, fields, headers): + return (url, fields, headers) + + def vepycurl_open(request): + c = VEpycurl.VEpycurl(verifySSL=2) + url, fields, headers = request + nh = [ '%s: %s' % (k, v) for k,v in headers.items()] + c.perform(url, fields, nh) + return c.results() + + URLOPEN = vepycurl_open + URLREQUEST = vepycurl_request + except: + import warnings + ssl_message = 'using urllib instead of pycurl, urllib does not verify SSL remote certificates, there is a risk of compromised communication' + warnings.warn(ssl_message, RuntimeWarning) + + def urllib_request(url, fields, headers): + fields = urllib.urlencode(fields) + return urllib2.Request(url, fields, headers) + + URLOPEN = urllib2.urlopen + URLREQUEST = urllib_request class MissingRequiredArgument(Exception): From a565abb6940d851c27a30341409a50a2abc8aec4 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Tue, 23 Apr 2013 21:30:02 -0400 Subject: [PATCH 04/34] Fall back to float currency (meh!) with simplejson Ugly edge case. Real ugly. Happened on an Ubuntu 8.04 box. --- linode/api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/linode/api.py b/linode/api.py index 01cdb50..891bdea 100755 --- a/linode/api.py +++ b/linode/api.py @@ -38,8 +38,10 @@ try: import json + FULL_BODIED_JSON = True except: import simplejson as json + FULL_BODIED_JSON = False try: import requests @@ -263,11 +265,15 @@ def __send_request(self, request): logging.debug('Raw Response: '+response) - try: - s = json.loads(response, parse_float=Decimal) - except Exception, ex: - print(response) - raise ex + if FULL_BODIED_JSON: + try: + s = json.loads(response, parse_float=Decimal) + except Exception, ex: + print(response) + raise ex + else: + # Stuck with simplejson, which won't let us parse_float + s = json.loads(response) if isinstance(s, dict): s = LowerCaseDict(s) From f80151467d61d4f5009c77fd2854579608dd016a Mon Sep 17 00:00:00 2001 From: Dan Slimmon Date: Fri, 19 Jul 2013 21:36:14 +0000 Subject: [PATCH 05/34] Started to add Nodebalancer functionality (tested with chube) --- linode/api.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/linode/api.py b/linode/api.py index 891bdea..fc781b0 100755 --- a/linode/api.py +++ b/linode/api.py @@ -818,6 +818,42 @@ def domain_resource_update(self, request): """ pass + @__api_request(optional=['NodeBalancerID'], + returns=[{u'ADDRESS4': 'IPv4 IP address of the NodeBalancer', + u'ADDRESS6': 'IPv6 IP address of the NodeBalancer', + u'CLIENTCONNTHROTTLE': 'Allowed connections per second, per client IP', + u'HOSTNAME': 'NodeBalancer hostname', + u'LABEL': 'NodeBalancer label', + u'NODEBALANCERID': 'NodeBalancer ID', + u'STATUS': 'NodeBalancer status, as a string'}]) + def nodebalancer_list(self, request): + """List information about your NodeBalancers.""" + pass + + @__api_request(required=['NodeBalancerID'], + optional=['Label', + 'ClientConnThrottle'], + returns={u'NodeBalancerID': 'NodeBalancerID'}) + def nodebalancer_update(self, request): + """Update information about, or settings for, a Nodebalancer. + + See nodebalancer_list.__doc__ for information on parameters. + """ + pass + + @__api_request(required=['DatacenterID', 'PaymentTerm'], + returns={u'NodeBalancerID' : 'ID of the created NodeBalancer'}) + def nodebalancer_create(self, request): + """Creates a NodeBalancer.""" + pass + + @__api_request(required=['NodeBalancerID'], + returns={u'NodeBalancerID': 'Destroyed NodeBalancer ID'}) + def nodebalancer_delete(self, request): + """Immediately removes a NodeBalancer from your account and issues + a pro-rated credit back to your account, if applicable.""" + pass + @__api_request(optional=['StackScriptID'], returns=[{u'CREATE_DT': "'yyyy-mm-dd hh:mm:ss.0'", u'DEPLOYMENTSACTIVE': 'The number of Scripts that Depend on this Script', From 739c3252b65ae395b8f339f2d0e87d69def0414e Mon Sep 17 00:00:00 2001 From: Dan Slimmon Date: Sun, 21 Jul 2013 00:58:12 +0000 Subject: [PATCH 06/34] Added vim swapfiles to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0d20b64..034ed13 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +.*.sw? From fb8c924d8b8f205dacac1e76a7c06c44a884cc46 Mon Sep 17 00:00:00 2001 From: Dan Slimmon Date: Sun, 21 Jul 2013 01:48:01 +0000 Subject: [PATCH 07/34] Added Nodebalancer config methods. --- linode/api.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/linode/api.py b/linode/api.py index fc781b0..d45f730 100755 --- a/linode/api.py +++ b/linode/api.py @@ -854,6 +854,55 @@ def nodebalancer_delete(self, request): a pro-rated credit back to your account, if applicable.""" pass + @__api_request(required=['NodeBalancerID'], + optional=['ConfigID'], + returns=[{ + u'ALGORITHM': 'Balancing algorithm.', + u'CHECK': 'Type of health check to perform.', + u'CHECK_ATTEMPTS': 'Number of failed probes allowed.', + u'CHECK_BODY': 'A regex against the expected result body.', + u'CHECK_INTERVAL': 'Seconds between health check probes.', + u'CHECK_PATH': 'The path of the health check request.', + u'CHECK_TIMEOUT': 'Seconds to wait before calling a failure.', + u'CONFIGID': 'ID of this config', + u'NODEBALANCERID': 'NodeBalancer ID.', + u'PORT': 'Port to bind to on public interface.', + u'PROTOCOL': 'The protocol to be used (tcp or http).', + u'STICKINESS': 'Session persistence.'}]) + def nodebalancer_config_list(self, request): + """List information about your NodeBalancer Configs.""" + pass + + @__api_request(required=['ConfigID'], + optional=['Algorithm', 'check', 'check_attempts', 'check_body', + 'check_interval', 'check_path', 'check_timeout', + 'Port', 'Protocol', 'Stickiness'], + returns={u'ConfigID': 'The ConfigID you passed in the first place.'}) + def nodebalancer_config_update(self, request): + """Update information about, or settings for, a Nodebalancer Config. + + See nodebalancer_config_list.__doc__ for information on parameters. + """ + pass + + @__api_request(required=['NodeBalancerID'], + optional=['Algorithm', 'check', 'check_attempts', 'check_body', + 'check_interval', 'check_path', 'check_timeout', + 'Port', 'Protocol', 'Stickiness'], + returns={u'ConfigID': 'The ConfigID of the new Config.'}) + def nodebalancer_config_create(self, request): + """Create a Nodebalancer Config. + + See nodebalancer_config_list.__doc__ for information on parameters. + """ + pass + + @__api_request(required=['ConfigID'], + returns={u'ConfigID': 'Destroyed Config ID'}) + def nodebalancer_config_delete(self, request): + """Deletes a NodeBalancer's Config.""" + pass + @__api_request(optional=['StackScriptID'], returns=[{u'CREATE_DT': "'yyyy-mm-dd hh:mm:ss.0'", u'DEPLOYMENTSACTIVE': 'The number of Scripts that Depend on this Script', From c17464d3775156ceb50f4fadb0c485d5a23228fa Mon Sep 17 00:00:00 2001 From: Dan Slimmon Date: Mon, 22 Jul 2013 22:09:56 +0000 Subject: [PATCH 08/34] Added nodebalancer.node methods. --- linode/api.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/linode/api.py b/linode/api.py index d45f730..438b807 100755 --- a/linode/api.py +++ b/linode/api.py @@ -903,6 +903,46 @@ def nodebalancer_config_delete(self, request): """Deletes a NodeBalancer's Config.""" pass + @__api_request(required=['ConfigID'], + optional=['NodeID'], + returns=[{u'ADDRESS': 'Address:port combination for the node.', + u'CONFIGID': 'ConfigID of this node\'s config.', + u'LABEL': 'The backend node\'s label.', + u'MODE': 'Connection mode for this node.', + u'NODEBALANCERID': 'ID of this node\'s nodebalancer.', + u'NODEID': 'NodeID.', + u'STATUS': 'Node\'s status in the nodebalancer.', + u'WEIGHT': 'Load balancing weight.'}]) + def nodebalancer_node_list(self, request): + """List information about your NodeBalancer Nodes.""" + pass + + @__api_request(required=['NodeID'], + optional=['Label', 'Address', 'Weight', 'Mode'], + returns={u'NodeID': 'The NodeID you passed in the first place.'}) + def nodebalancer_node_update(self, request): + """Update information about, or settings for, a Nodebalancer Node. + + See nodebalancer_node_list.__doc__ for information on parameters. + """ + pass + + @__api_request(required=['ConfigID', 'Label', 'Address'], + optional=['Weight', 'Mode'], + returns={u'NodeID': 'The NodeID of the new Node.'}) + def nodebalancer_node_create(self, request): + """Create a Nodebalancer Node. + + See nodebalancer_node_list.__doc__ for information on parameters. + """ + pass + + @__api_request(required=['NodeID'], + returns={u'NodeID': 'Destroyed Node ID'}) + def nodebalancer_node_delete(self, request): + """Deletes a NodeBalancer Node.""" + pass + @__api_request(optional=['StackScriptID'], returns=[{u'CREATE_DT': "'yyyy-mm-dd hh:mm:ss.0'", u'DEPLOYMENTSACTIVE': 'The number of Scripts that Depend on this Script', From 4b1d5cc31e520f4835cb9b90e3e9c977333168e5 Mon Sep 17 00:00:00 2001 From: Magnus Appelquist Date: Thu, 15 May 2014 22:12:19 +0200 Subject: [PATCH 09/34] Added linode_ip_setrdns to set RDNS --- linode/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/linode/api.py b/linode/api.py index 891bdea..29aa083 100755 --- a/linode/api.py +++ b/linode/api.py @@ -8,6 +8,7 @@ Copyright (c) 2010 Ryan Tucker Copyright (c) 2008 James C Sinclair Copyright (c) 2013 Tim Heckman +Copyright (c) 2014 Magnus Appelquist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -628,6 +629,14 @@ def linode_ip_list(self, request): """Lists a Linode's IP addresses.""" pass + @__api_request(required=['IPAddressID','Hostname'], + returns=[{u'HOSTNAME': 'reverse.dns.name.here', + u'IPADDRESS': '192.168.100.1', + u'IPADDRESSID': 'IP address ID'}]) + def linode_ip_setrdns(self, request): + """Sets the reverse DNS name of a public Linode IP.""" + pass + @__api_request(required=['LinodeID'], optional=['pendingOnly', 'JobID'], returns=[{u'ACTION': "API action (e.g. u'linode.create')", u'DURATION': "Duration spent processing or ''", From 918cacfbf186c7376aa4ab446d64256df0addc81 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Wed, 21 May 2014 20:54:20 -0400 Subject: [PATCH 10/34] Update avail.linodeplans to add hourly price --- linode/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/linode/api.py b/linode/api.py index 2e2a1df..29f52a3 100755 --- a/linode/api.py +++ b/linode/api.py @@ -676,14 +676,18 @@ def avail_datacenters(self, request): pass @__api_request(returns=[{u'DISK': 'Maximum disk allocation (GB)', - u'LABEL': 'Name of plan', u'PLANID': 'Plan ID', - u'PRICE': 'Price (US dollars)', + u'LABEL': 'Name of plan', + u'PLANID': 'Plan ID', + u'PRICE': 'Monthly price (US dollars)', + u'HOURLY': 'Hourly price (US dollars)', u'RAM': 'Maximum memory (MB)', u'XFER': 'Allowed transfer (GB/mo)', u'AVAIL': {u'Datacenter ID': 'Quantity'}}]) def avail_linodeplans(self, request): """Returns a structure of Linode PlanIDs containing PlanIDs, and their availability in each datacenter. + + This plan is deprecated and will be removed in the future. """ pass From 0f6c4ebc807a23c935c23189d9013bfe65d6b12e Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Wed, 21 May 2014 20:54:50 -0400 Subject: [PATCH 11/34] Fix json.dumps handling of Decimal vals (Fix: #14) --- linode/shell.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/linode/shell.py b/linode/shell.py index 5df7f15..0026f05 100755 --- a/linode/shell.py +++ b/linode/shell.py @@ -42,12 +42,12 @@ class DecimalEncoder(json.JSONEncoder): """Handle Decimal types when producing JSON. - Hat tip: http://stackoverflow.com/a/1960649 + Hat tip: http://stackoverflow.com/questions/4019856/decimal-to-json """ - def _iterencode(self, o, markers=None): + def default(self, o): if isinstance(o, decimal.Decimal): - return (str(o) for o in [o]) - return super(DecimalEncoder, self)._iterencode(o, markers) + return float(o) + return json.JSONEncoder.default(self, o) class LinodeConsole(code.InteractiveConsole): def __init__(self, locals=None, filename="", From 35bbc7f892b45d8b455520e91667adaf01d52b1c Mon Sep 17 00:00:00 2001 From: James Date: Wed, 25 Jun 2014 09:51:13 +1000 Subject: [PATCH 12/34] Create README.md --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6b748d --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Linode Python Bindings + + +The bindings consist of three pieces: + - api.py: Core library that manages authentication and api calls + - shell.py: A command line interface to api.py that allows you to invoke + a specific api command quickly + - oop.py: An object oriented interface to api.py inspired by django + +For definitive documentation on how the api works please visit: +http://www.linode.com/api/index.cfm + +## API Keys + + +When creating an api object you may specify the key manually, or use the +Api.user_getapikey which will return your apikey as well as set the internal +key that will be used for subsequent api calls. + +Both the shell.py and oop.py have mechanisms to pull the api key from the +environment variable LINODE_API_KEY as well. + +## Batching + + +Batching should be used with care, once enabled all api calls are cached until +Api.batchFlush() is called, however you must remember the order in which calls +were made as that's the order of the list returned to you + +## License + + +This code is provided under an MIT-style license. Please refer to the LICENSE +file in the root of the project for specifics. From 839074787d4ff04f079e0200416254202817349e Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Thu, 3 Jul 2014 22:00:34 -0400 Subject: [PATCH 13/34] Delete redundant README file README is dead, long live README.md --- README | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 README diff --git a/README b/README deleted file mode 100644 index 32eacf9..0000000 --- a/README +++ /dev/null @@ -1,34 +0,0 @@ -Linode Python Bindings ----------------------- - -The bindings consist of three pieces: - - api.py: Core library that manages authentication and api calls - - shell.py: A command line interface to api.py that allows you to invoke - a specific api command quickly - - oop.py: An object oriented interface to api.py inspired by django - -For definitive documentation on how the api works please visit: -http://www.linode.com/api/index.cfm - -API Keys --------- - -When creating an api object you may specify the key manually, or use the -Api.user_getapikey which will return your apikey as well as set the internal -key that will be used for subsequent api calls. - -Both the shell.py and oop.py have mechanisms to pull the api key from the -environment variable LINODE_API_KEY as well. - -Batching --------- - -Batching should be used with care, once enabled all api calls are cached until -Api.batchFlush() is called, however you must remember the order in which calls -were made as that's the order of the list returned to you - -License -------- - -This code is provided under an MIT-style license. Please refer to the LICENSE -file in the root of the project for specifics. From b7d910fe7a5386444b06f6b4a458d81eb18f7f57 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 4 Jul 2014 19:12:58 +1000 Subject: [PATCH 14/34] Fix broken link to API documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6b748d..ea75672 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The bindings consist of three pieces: - oop.py: An object oriented interface to api.py inspired by django For definitive documentation on how the api works please visit: -http://www.linode.com/api/index.cfm +https://www.linode.com/api ## API Keys From a6221833f29efbcfa0a264e77f85708a30f9f384 Mon Sep 17 00:00:00 2001 From: Johny Jose Date: Fri, 25 Jul 2014 15:58:43 +0530 Subject: [PATCH 15/34] remove 'PaymentTerm' as a required parameter. Signed-off-by: Johny Jose --- linode/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index 29f52a3..d497791 100755 --- a/linode/api.py +++ b/linode/api.py @@ -413,7 +413,7 @@ def linode_update(self, request): """ pass - @__api_request(required=['DatacenterID', 'PlanID', 'PaymentTerm'], + @__api_request(required=['DatacenterID', 'PlanID'], returns={u'LinodeID': 'New Linode ID'}) def linode_create(self, request): """Create a new Linode. From 8c401d40a9cb93d93090fa8f43e0d5b4614a9414 Mon Sep 17 00:00:00 2001 From: Dustin Hughes Date: Wed, 1 Oct 2014 10:11:27 -0500 Subject: [PATCH 16/34] Addition of linode_clone was added because it was missing from the API. PaymentTerms is required for linode_create this was discover during testing the methods. --- linode/api.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index d497791..a0368ba 100755 --- a/linode/api.py +++ b/linode/api.py @@ -413,7 +413,16 @@ def linode_update(self, request): """ pass - @__api_request(required=['DatacenterID', 'PlanID'], + @__api_request(required=['LinodeID', 'DatacenterID', 'PlanID', 'PaymentTerm'], + returns={u'LinodeID': 'New Linode ID'}) + def linode_clone(self, request): + """Create a new Linode. + + This will create a billing event. + """ + pass + + @__api_request(required=['DatacenterID', 'PlanID', 'PaymentTerm'], returns={u'LinodeID': 'New Linode ID'}) def linode_create(self, request): """Create a new Linode. From 66f157ac73654611639f83489faace231d39cecc Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sun, 5 Oct 2014 15:09:43 -0400 Subject: [PATCH 17/34] Update linode_clone method per Linode docs --- linode/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/linode/api.py b/linode/api.py index a0368ba..9dcd44a 100755 --- a/linode/api.py +++ b/linode/api.py @@ -413,11 +413,14 @@ def linode_update(self, request): """ pass - @__api_request(required=['LinodeID', 'DatacenterID', 'PlanID', 'PaymentTerm'], + @__api_request(required=['LinodeID', 'DatacenterID', 'PlanID'], + optional=['PaymentTerm'], returns={u'LinodeID': 'New Linode ID'}) def linode_clone(self, request): - """Create a new Linode. - + """Create a new Linode, then clone the specified LinodeID to the + new Linode. It is recommended that the source Linode be powered + down during the clone. + This will create a billing event. """ pass From 329cede2addb7e0b933fe6a2f1022dc968467404 Mon Sep 17 00:00:00 2001 From: tylerturk Date: Wed, 29 Oct 2014 07:53:03 -0500 Subject: [PATCH 18/34] Adding linode.ip.swap --- linode/api.py | 9 +++++++++ setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index 9dcd44a..76fa56b 100755 --- a/linode/api.py +++ b/linode/api.py @@ -649,6 +649,15 @@ def linode_ip_setrdns(self, request): """Sets the reverse DNS name of a public Linode IP.""" pass + @__api_request(required=['IPAddressID'], + optional=['withIPAddressID', 'toLinodeID'], + returns=[{u'ACTION': "linode.ip.swap", + u'ERRORARRAY': 'Array of errors', + u'DATA': 'The response'}]) + def linode_ip_swap(self, request): + """Exchanges Public IP addresses between two Linodes within a Datacenter""" + pass + @__api_request(required=['LinodeID'], optional=['pendingOnly', 'JobID'], returns=[{u'ACTION': "API action (e.g. u'linode.create')", u'DURATION': "Duration spent processing or ''", diff --git a/setup.py b/setup.py index 3c8bf6f..e5ab297 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "linode-python", - version = "1.0", + version = "1.1", description = "Python bindings for Linode API", author = "TJ Fontaine", author_email = "tjfontaine@gmail.com", From 340f50ef057487d2747357ad3fce31fd4845eead Mon Sep 17 00:00:00 2001 From: tylerturk Date: Wed, 29 Oct 2014 11:18:58 -0500 Subject: [PATCH 19/34] Also adding the ability to add public IP addresses --- linode/api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/linode/api.py b/linode/api.py index 76fa56b..767822c 100755 --- a/linode/api.py +++ b/linode/api.py @@ -631,6 +631,13 @@ def linode_ip_addprivate(self, request): that was added.""" pass + @__api_request(required=['LinodeID'], + returns={u'IPAddressID': 'New IP Address ID'}) + def linode_ip_addpublic(self, request): + """Assigns a Public IP to a Linode. Returns the IPAddressID + that was added.""" + pass + @__api_request(required=['LinodeID'], optional=['IPAddressID'], returns=[{u'ISPUBLIC': '0 or 1', u'IPADDRESS': '192.168.100.1', From 1ea8f49973fd59b9255c934b0f6d2183c67dac4a Mon Sep 17 00:00:00 2001 From: tylerturk Date: Fri, 31 Oct 2014 10:45:24 -0500 Subject: [PATCH 20/34] Updating to match the proper return dict and proper casing --- linode/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/linode/api.py b/linode/api.py index 767822c..356c145 100755 --- a/linode/api.py +++ b/linode/api.py @@ -632,7 +632,8 @@ def linode_ip_addprivate(self, request): pass @__api_request(required=['LinodeID'], - returns={u'IPAddressID': 'New IP Address ID'}) + returns={u'IPADDRESSID': 'New IP Address ID', + u'IPADDRESS': '192.168.100.1'}) def linode_ip_addpublic(self, request): """Assigns a Public IP to a Linode. Returns the IPAddressID that was added.""" @@ -658,9 +659,9 @@ def linode_ip_setrdns(self, request): @__api_request(required=['IPAddressID'], optional=['withIPAddressID', 'toLinodeID'], - returns=[{u'ACTION': "linode.ip.swap", - u'ERRORARRAY': 'Array of errors', - u'DATA': 'The response'}]) + returns=[{u'LINODEID': 'The ID of the Linode', + u'IPAADDRESS': '192.168.100.1', + u'IPADDRESSID': 'IP address ID'}]) def linode_ip_swap(self, request): """Exchanges Public IP addresses between two Linodes within a Datacenter""" pass From 32c54aa9ee980cd7199d943c9e57dc5752300bbc Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Fri, 7 Nov 2014 21:08:49 -0500 Subject: [PATCH 21/34] Minor typo fixes --- linode/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linode/api.py b/linode/api.py index 356c145..4566a7b 100755 --- a/linode/api.py +++ b/linode/api.py @@ -633,7 +633,7 @@ def linode_ip_addprivate(self, request): @__api_request(required=['LinodeID'], returns={u'IPADDRESSID': 'New IP Address ID', - u'IPADDRESS': '192.168.100.1'}) + u'IPADDRESS': '192.168.100.1'}) def linode_ip_addpublic(self, request): """Assigns a Public IP to a Linode. Returns the IPAddressID that was added.""" @@ -660,7 +660,7 @@ def linode_ip_setrdns(self, request): @__api_request(required=['IPAddressID'], optional=['withIPAddressID', 'toLinodeID'], returns=[{u'LINODEID': 'The ID of the Linode', - u'IPAADDRESS': '192.168.100.1', + u'IPADDRESS': '192.168.100.1', u'IPADDRESSID': 'IP address ID'}]) def linode_ip_swap(self, request): """Exchanges Public IP addresses between two Linodes within a Datacenter""" From 9ee60e8ba0557b68613cf52d8e4a6f926bdc797f Mon Sep 17 00:00:00 2001 From: Izaak Date: Fri, 5 Dec 2014 18:39:15 +0300 Subject: [PATCH 22/34] Python 3 patch for linode-python Hi Ryan, I needed to use the Linode API with Python 3 today, but discovered some errors when installing it. I saw that you have a Python 3 branch, but it has not been updated for a few years, so I just made some minor fixes to the master branch to make linode-python install cleanly (I am not sure if it actually works with Python 3 otherwise). Please find my patch attached. -- Izaak Signed-off-by: Ryan Tucker --- linode/VEpycurl.py | 10 +++++----- linode/api.py | 2 +- linode/deploy_abunch.py | 8 ++++---- linode/shell.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/linode/VEpycurl.py b/linode/VEpycurl.py index 154059c..dfc1fd9 100644 --- a/linode/VEpycurl.py +++ b/linode/VEpycurl.py @@ -81,9 +81,9 @@ def __init__(self, if verbose : self.pco.setopt(pycurl.VERBOSE, 1) if debug : - print 'PyCurl version info:' - print pycurl.version_info() - print + print('PyCurl version info:') + print(pycurl.version_info()) + print() self.pco.setopt(pycurl.DEBUGFUNCTION, self.debug) return @@ -113,12 +113,12 @@ def results(self) : return self.pc def debug(self, debug_type, debug_msg) : - print 'debug(%d): %s' % (debug_type, debug_msg) + print('debug(%d): %s' % (debug_type, debug_msg)) return try: # only call this once in a process. see libcurl docs for more info. pycurl.global_init(pycurl.GLOBAL_ALL) except: - print 'Fatal error: call to pycurl.global_init() failed for some reason' + print('Fatal error: call to pycurl.global_init() failed for some reason') sys.exit(1) diff --git a/linode/api.py b/linode/api.py index 4566a7b..a0a0502 100755 --- a/linode/api.py +++ b/linode/api.py @@ -269,7 +269,7 @@ def __send_request(self, request): if FULL_BODIED_JSON: try: s = json.loads(response, parse_float=Decimal) - except Exception, ex: + except Exception as ex: print(response) raise ex else: diff --git a/linode/deploy_abunch.py b/linode/deploy_abunch.py index c3b731a..9521447 100755 --- a/linode/deploy_abunch.py +++ b/linode/deploy_abunch.py @@ -119,7 +119,7 @@ if not options.kernel: raise Exception('Must specify a kernel to use for configuration') -except Exception, ex: +except Exception as ex: sys.stderr.write(str(ex) + linesep) parser.print_help() sys.exit('All options are required (yes I see the contradiction)') @@ -135,7 +135,7 @@ else: api_key = getpass('Enter API Key: ') -print 'Passwords must contain at least two of these four character classes: lower case letters - upper case letters - numbers - punctuation' +print('Passwords must contain at least two of these four character classes: lower case letters - upper case letters - numbers - punctuation') root_pass = getpass('Enter the root password for all resulting nodes: ') root_pass2 = getpass('Re-Enter the root password: ') @@ -219,5 +219,5 @@ def deploy_set(): needFlush = False deploy_set() -print 'List of created Linodes:' -print '[%s]' % (', '.join([str(l) for l in created_linodes])) +print('List of created Linodes:') +print('[%s]' % (', '.join([str(l) for l in created_linodes]))) diff --git a/linode/shell.py b/linode/shell.py index 0026f05..85db7d0 100755 --- a/linode/shell.py +++ b/linode/shell.py @@ -109,7 +109,7 @@ def usage(all=False): if len(sys.argv[1:]) > 0: try: optlist, args = getopt.getopt(sys.argv[1:], '', options) - except getopt.GetoptError, err: + except getopt.GetoptError as err: print(str(err)) usage() sys.exit(2) @@ -128,7 +128,7 @@ def usage(all=False): func = getattr(linode, command) try: print(json.dumps(func(**params), indent=2, cls=DecimalEncoder)) - except api.MissingRequiredArgument, mra: + except api.MissingRequiredArgument as mra: print('Missing option --%s' % mra.value.lower()) print('') usage() From 3ab348742ef481692a1122dd905e48260218094d Mon Sep 17 00:00:00 2001 From: tylerturk Date: Thu, 12 Feb 2015 21:20:36 -0600 Subject: [PATCH 23/34] Undefined vars cannot be pickled - https://docs.python.org/2/library/pickle.html#what-can-be-pickled-and-unpickled --- linode/api.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/linode/api.py b/linode/api.py index a0a0502..00cf887 100755 --- a/linode/api.py +++ b/linode/api.py @@ -91,8 +91,8 @@ def urllib_request(url, fields, headers): class MissingRequiredArgument(Exception): """Raised when a required parameter is missing.""" - - def __init__(self, value): + + def __init__(self, value=str()): self.value = value def __str__(self): return repr(self.value) @@ -123,8 +123,8 @@ class ApiError(Exception): 40: Limit of Linodes added per hour reached 41: Linode must have no disks before delete """ - - def __init__(self, value): + + def __init__(self, value=str()): self.value = value def __str__(self): return repr(self.value) @@ -343,7 +343,7 @@ def wrapper(self, **kw): if returns and wrapper.__doc__: # we either have a list of dicts or a just plain dict if len(wrapper.__doc__.split('\n')) is 1: # one-liners need whitespace - wrapper.__doc__ += '\n' + wrapper.__doc__ += '\n' if isinstance(returns, list): width = max(len(q) for q in returns[0].keys()) wrapper.__doc__ += '\n Returns list of dictionaries:\n\t[{\n' @@ -420,11 +420,11 @@ def linode_clone(self, request): """Create a new Linode, then clone the specified LinodeID to the new Linode. It is recommended that the source Linode be powered down during the clone. - + This will create a billing event. """ pass - + @__api_request(required=['DatacenterID', 'PlanID', 'PaymentTerm'], returns={u'LinodeID': 'New Linode ID'}) def linode_create(self, request): @@ -471,7 +471,7 @@ def linode_delete(self, request): returns={u'JobID': 'Job ID'}) def linode_reboot(self, request): """Submit a reboot job for a Linode. - + On job submission, returns the job ID. Does not wait for job completion (see linode_job_list). """ @@ -535,7 +535,7 @@ def linode_config_delete(self, request): linode_delete). """ pass - + @__api_request(required=['LinodeID'], returns=[{u'CREATE_DT': u'YYYY-MM-DD hh:mm:ss.0', u'DISKID': 'Disk ID', From ccdccd10786cd8653df3154e5b2262fc33790134 Mon Sep 17 00:00:00 2001 From: tylerturk Date: Fri, 13 Feb 2015 10:46:42 -0600 Subject: [PATCH 24/34] Switch to using reduce instead of str() --- linode/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/linode/api.py b/linode/api.py index 00cf887..fb20808 100755 --- a/linode/api.py +++ b/linode/api.py @@ -92,10 +92,12 @@ def urllib_request(url, fields, headers): class MissingRequiredArgument(Exception): """Raised when a required parameter is missing.""" - def __init__(self, value=str()): + def __init__(self, value): self.value = value def __str__(self): return repr(self.value) + def __reduce__(self): + return (self.__class__, (self.value, )) class ApiError(Exception): """Raised when a Linode API call returns an error. @@ -124,10 +126,12 @@ class ApiError(Exception): 41: Linode must have no disks before delete """ - def __init__(self, value=str()): + def __init__(self, value): self.value = value def __str__(self): return repr(self.value) + def __reduce__(self): + return (self.__class__, (self.value, )) class ApiInfo: valid_commands = {} From 709f34439766c289098736cfbcb1a862ccfe6862 Mon Sep 17 00:00:00 2001 From: Dustin Lacewell Date: Wed, 18 Feb 2015 20:17:06 -0800 Subject: [PATCH 25/34] linode_ip_list's LinodeAPI parameter should be optional --- linode/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index 4566a7b..9d603d7 100755 --- a/linode/api.py +++ b/linode/api.py @@ -639,7 +639,7 @@ def linode_ip_addpublic(self, request): that was added.""" pass - @__api_request(required=['LinodeID'], optional=['IPAddressID'], + @__api_request(optional=['IPAddressID', 'LinodeID'], returns=[{u'ISPUBLIC': '0 or 1', u'IPADDRESS': '192.168.100.1', u'IPADDRESSID': 'IP address ID', From 6c1299fa18af9550d5a6501af664e73329b20aaf Mon Sep 17 00:00:00 2001 From: "C. R. Oldham" Date: Thu, 26 Feb 2015 21:49:18 +0000 Subject: [PATCH 26/34] Make sure other sensitive vars are redacted --- linode/api.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/linode/api.py b/linode/api.py index 6742f04..4f2a9b3 100755 --- a/linode/api.py +++ b/linode/api.py @@ -36,6 +36,7 @@ import logging import urllib import urllib2 +import copy try: import json @@ -257,7 +258,13 @@ def __send_request(self, request): request['api_responseFormat'] = 'json' - logging.debug('Parmaters '+str(request)) + request_log = copy.deepcopy(request) + redact = ['api_key','rootsshkey','rootpass'] + for r in redact: + if r in request_log: + request_log[r] = '{0}: xxxx REDACTED xxxx'.format(r) + + logging.debug('Parameters '+str(request_log)) #request = urllib.urlencode(request) headers = { @@ -293,7 +300,7 @@ def __send_request(self, request): return s def __api_request(required=[], optional=[], returns=[]): - """Decorator to define required and optional paramters""" + """Decorator to define required and optional parameters""" for k in required: k = k.lower() if k not in ApiInfo.valid_params: From 632c13c31e915a36b81fc60e305dd168bb4e679f Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sat, 7 Mar 2015 21:16:47 -0500 Subject: [PATCH 27/34] Add an extra_requires for requests This will let folks do: pip install linode-python[requests] ... to install requests alongside linode-python. Fixes #23 comment 2 --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index e5ab297..4a9e0ed 100644 --- a/setup.py +++ b/setup.py @@ -8,4 +8,7 @@ author_email = "tjfontaine@gmail.com", url = "https://github.com/tjfontaine/linode-python", packages = ['linode'], + extras_require = { + 'requests': ["requests"], + }, ) From 803088737f665f65cf23c9a14001404d279ba27d Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sat, 7 Mar 2015 21:27:28 -0500 Subject: [PATCH 28/34] Bump to v1.1.1 Farked up the PyPi upload (I should do this more often). Also fixing a warning in the MANIFEST.in... --- MANIFEST.in | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 7e9bc7d..bb3ec5f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README \ No newline at end of file +include README.md diff --git a/setup.py b/setup.py index 4a9e0ed..fa5466a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "linode-python", - version = "1.1", + version = "1.1.1", description = "Python bindings for Linode API", author = "TJ Fontaine", author_email = "tjfontaine@gmail.com", From 34cc390410a662e770c55edace6a540149905621 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sat, 7 Mar 2015 21:34:05 -0500 Subject: [PATCH 29/34] Bump version number to v1.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa5466a..936a6a0 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = "linode-python", - version = "1.1.1", + version = "1.2", description = "Python bindings for Linode API", author = "TJ Fontaine", author_email = "tjfontaine@gmail.com", From a467b29f711099fba2d29072d6f1699ff063134b Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sat, 7 Mar 2015 21:34:26 -0500 Subject: [PATCH 30/34] Add LICENSE to package manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index bb3ec5f..04f196a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include README.md +include LICENSE From a29da188dcdf0690d5234b2c00efeef98001415b Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sun, 12 Jul 2015 09:21:15 -0400 Subject: [PATCH 31/34] linode.create: PaymentTerm now optional --- linode/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index 4f2a9b3..eb3ab5c 100755 --- a/linode/api.py +++ b/linode/api.py @@ -436,7 +436,8 @@ def linode_clone(self, request): """ pass - @__api_request(required=['DatacenterID', 'PlanID', 'PaymentTerm'], + @__api_request(required=['DatacenterID', 'PlanID'], + optional=['PaymentTerm'], returns={u'LinodeID': 'New Linode ID'}) def linode_create(self, request): """Create a new Linode. From 6e1b7dbc9539c534d45c2e8eea3da1fc452b2d4a Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanovic Date: Wed, 19 Aug 2015 19:09:57 +0200 Subject: [PATCH 32/34] Added PlanID fetched from Linode API List response. --- linode/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/linode/api.py b/linode/api.py index eb3ab5c..69ffb22 100755 --- a/linode/api.py +++ b/linode/api.py @@ -389,6 +389,7 @@ def wrapper(self, **kw): u'LABEL': 'linode label', u'LINODEID': 'Linode ID', u'LPM_DISPLAYGROUP': 'group label', + u'PLANID': 'plan id', u'STATUS': 'Status flag', u'TOTALHD': 'available disk (GB)', u'TOTALRAM': 'available RAM (MB)', From 2f34d292c45e7d60d9406f2556986780ca6436a7 Mon Sep 17 00:00:00 2001 From: InTheCloudDan Date: Thu, 18 Aug 2016 12:15:32 -0400 Subject: [PATCH 33/34] Update api.py Add ext4 as a disk option --- linode/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linode/api.py b/linode/api.py index 69ffb22..ec6a7ee 100755 --- a/linode/api.py +++ b/linode/api.py @@ -557,7 +557,7 @@ def linode_config_delete(self, request): u'LINODEID': 'Linode ID', u'SIZE': 'Size of disk (MB)', u'STATUS': 'Status flag', - u'TYPE': "in ['ext3', 'swap', 'raw']", + u'TYPE': "in ['ext4', 'ext3', 'swap', 'raw']", u'UPDATE_DT': u'YYYY-MM-DD hh:mm:ss.0'}]) def linode_disk_list(self, request): """Lists all disk images associated with a Linode.""" From 4da6249abffcaafeccea7b352db9a535722bea67 Mon Sep 17 00:00:00 2001 From: Timothy J Fontaine Date: Sat, 21 Mar 2026 08:28:59 -0700 Subject: [PATCH 34/34] Add archive notice to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ea75672..12ff723 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +> **⚠️ This project is archived and no longer maintained.** + # Linode Python Bindings