Python 3 library and CLI application for SporeStack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
8.0 KiB

  1. """
  2. Validates arguments.
  3. All functions return True if valid, or raise an exception.
  4. """
  5. import string
  6. from sshpubkeys import SSHKey
  7. def machine_id(machine_id):
  8. """
  9. Validates the machine_id
  10. Must be a 64 byte lowercase hex string.
  11. A sha256sum, in effect.
  12. """
  13. if not isinstance(machine_id, str):
  14. raise TypeError("machine_id must be a string.")
  15. if len(machine_id) != 64:
  16. raise ValueError("machine_id must be exactly 64 bytes/characters.")
  17. for letter in machine_id:
  18. if letter not in "0123456789abcdef":
  19. raise ValueError("machine_id must be only 0-9, a-f (lowercase)")
  20. return True
  21. def settlement_token(settlement_token):
  22. """
  23. Validates a settlement token.
  24. Identical format to machine IDs.
  25. """
  26. return machine_id(settlement_token)
  27. def operating_system(operating_system):
  28. """
  29. Validates an operating_system argument.
  30. """
  31. if operating_system is None:
  32. return True
  33. if not isinstance(operating_system, str):
  34. raise TypeError("operating_system must be null or a string.")
  35. if len(operating_system) < 1:
  36. raise ValueError("operating_system must have at least one letter.")
  37. if len(operating_system) > 16:
  38. raise ValueError("operating_system must have 16 letters or less.")
  39. for character in operating_system:
  40. if character not in string.ascii_lowercase + string.digits + "-":
  41. msg = "operating_system must only contain a-z, digits, -"
  42. raise ValueError(msg)
  43. return True
  44. def flavor(flavor):
  45. """
  46. Validates an flavor argument.
  47. """
  48. if flavor is None:
  49. return True
  50. if not isinstance(flavor, str):
  51. raise TypeError("flavor must be null or a string.")
  52. if len(flavor) < 1:
  53. raise ValueError("flavor must have at least one letter.")
  54. if len(flavor) > 16:
  55. raise ValueError("flavor must have 16 letters or less.")
  56. for character in flavor:
  57. if character not in string.ascii_lowercase + string.digits + "-":
  58. msg = "flavor must only contain a-z, digits, -"
  59. raise ValueError(msg)
  60. return True
  61. def ssh_key(ssh_key):
  62. """
  63. Validates an ssh_key argument.
  64. """
  65. if ssh_key is None:
  66. return True
  67. if not isinstance(ssh_key, str):
  68. raise TypeError("ssh_key must be null or a string.")
  69. ssh_key_object = SSHKey(ssh_key, skip_option_parsing=True, disallow_options=True)
  70. try:
  71. ssh_key_object.parse()
  72. except Exception as e:
  73. raise ValueError("Invalid SSH key: {}".format(e))
  74. return True
  75. def days(days, zero_allowed=False):
  76. """
  77. Makes sure our argument is valid.
  78. 0-28
  79. Keep in mind that 0 days implies no expiration,
  80. may be used in the future with settlement tokens.
  81. 0 is invalid unless zero_allowed is True.
  82. """
  83. if not isinstance(days, int):
  84. raise TypeError("days must be an integer type")
  85. if zero_allowed is True:
  86. minimum_days = 0
  87. else:
  88. minimum_days = 1
  89. if days < minimum_days or days > 28:
  90. raise ValueError("days must be {}-28".format(minimum_days))
  91. return True
  92. def organization(organization):
  93. if organization is None:
  94. return True
  95. if not isinstance(organization, str):
  96. raise TypeError("organization must be string.")
  97. if len(organization) < 1:
  98. raise ValueError("organization must have at least one letter.")
  99. if len(organization) > 16:
  100. raise ValueError("organization must have 16 letters or less.")
  101. for character in organization:
  102. if character not in string.ascii_letters:
  103. raise ValueError("organization must only contain a-z, A-Z")
  104. return True
  105. def unsigned_int(variable):
  106. """
  107. Make sure variable is an unsigned int.
  108. """
  109. if not isinstance(variable, int):
  110. return False
  111. if variable < 0:
  112. return False
  113. return True
  114. def disk(disk):
  115. """
  116. Makes sure disk is valid.
  117. 0 is valid, means no disk.
  118. """
  119. if not unsigned_int(disk):
  120. raise TypeError("disk must be an unsigned integer.")
  121. return True
  122. def memory(memory):
  123. """
  124. Makes sure memory is valid.
  125. """
  126. if not unsigned_int(memory):
  127. raise TypeError("memory must be an unsigned integer.")
  128. if memory == 0:
  129. raise ValueError("0 not acceptable for memory")
  130. return True
  131. def expiration(expiration):
  132. """
  133. Makes sure expiration is valid.
  134. """
  135. if not unsigned_int(expiration):
  136. raise TypeError("expiration must be an unsigned integer.")
  137. return True
  138. def cores(cores):
  139. """
  140. Makes sure cores is valid.
  141. """
  142. if not unsigned_int(cores):
  143. raise TypeError("cores must be an unsigned integer.")
  144. return True
  145. def currency(currency):
  146. """
  147. Makes sure currency is valid.
  148. """
  149. if currency is None:
  150. return True
  151. if not isinstance(currency, str):
  152. raise TypeError("currency must be None or str.")
  153. return True
  154. def bandwidth(bandwidth):
  155. """
  156. Bandwidth is in gigabytes per day.
  157. -1 is "unlimited".
  158. 0 means no bandwidth. Only valid if ipv4 and ipv6 are False.
  159. """
  160. if isinstance(bandwidth, int):
  161. if bandwidth >= -1:
  162. return True
  163. else:
  164. raise ValueError("bandwidth can be no lower than -1.")
  165. else:
  166. raise TypeError("bandwidth must be integer.")
  167. def _ip(ip, ip_type, cidr):
  168. """
  169. Helper for ipv4 and ipv6
  170. """
  171. # bool is int in Python 3, so have to test for bool first...
  172. if ip is False:
  173. return True
  174. if not isinstance(ip, str):
  175. raise TypeError("ipv4 must be false or string.")
  176. elif ip == cidr:
  177. return True
  178. elif ip == "nat":
  179. return True
  180. elif ip == "tor":
  181. return True
  182. else:
  183. raise ValueError("{} must be one of: False|{}".format(ip_type, cidr))
  184. def ipv4(ipv4):
  185. return _ip(ipv4, "ipv4", "/32")
  186. def ipv6(ipv6):
  187. return _ip(ipv6, "ipv6", "/128")
  188. # further calls are for validating compatibilities between
  189. # argument cominations.
  190. def further_ipv4_ipv6(ipv4, ipv6):
  191. """
  192. More validation with the combination of ipv4 and ipv6 options.
  193. We don't support mixed nat/tor modes, so this handles that.
  194. """
  195. message = "ipv4 and ipv6 must be the same if either is tor or nat."
  196. if ipv4 in ["tor", "nat"] or ipv6 in ["tor", "nat"]:
  197. if ipv4 != ipv6:
  198. raise ValueError(message)
  199. return True
  200. def _further_dollars_cents(dollars, cents):
  201. if dollars is None and cents is None:
  202. raise ValueError("dollars or cents must be set.")
  203. if dollars is not None and cents is not None:
  204. raise ValueError("dollars or cents must be set, not both.")
  205. return True
  206. def ipxescript(ipxescript):
  207. if ipxescript is None:
  208. return True
  209. if not isinstance(ipxescript, str):
  210. raise TypeError("ipxescript must be a string or null.")
  211. if len(ipxescript) == 0:
  212. raise ValueError("ipxescript must be more than zero bytes long.")
  213. if len(ipxescript) > 4000:
  214. raise ValueError("ipxescript must be less than 4,000 bytes long.")
  215. for letter in ipxescript:
  216. if letter not in string.printable:
  217. raise ValueError("ipxescript must only contain ascii characters.")
  218. return True
  219. def region(region):
  220. if region is None:
  221. return True
  222. if not isinstance(region, str):
  223. raise TypeError("region must be a string or null.")
  224. if len(region) == 0:
  225. raise ValueError("region must be more than zero bytes long.")
  226. if len(region) > 200:
  227. raise ValueError("region must be less than 200 bytes long.")
  228. for letter in region:
  229. if letter not in string.printable:
  230. raise ValueError("region must only contain ascii characters.")
  231. return True
  232. def affiliate_amount(amount):
  233. if amount is None:
  234. return True
  235. if unsigned_int(amount) is True:
  236. if amount != 0:
  237. return True
  238. raise TypeError("affiliate_amount must be null or non-zero unsigned int.")
  239. def cents(cents):
  240. if not unsigned_int(cents):
  241. raise TypeError("cents must be unsigned integer.")
  242. return True