├── .gitignore ├── LICENSE ├── Paramiko_LICENSE ├── README.md └── src ├── ad-sample.py ├── api-sample.py ├── d42_api_linux_upload_sample_script.py ├── d42_api_solaris_sample_script.py ├── ipcalc.py ├── linux_auto_dics_multi.py ├── sample-script-facter-facts-to-d42.py └── winservice.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.idea 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Device42, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Paramiko_LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | 504 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** 2 | **The following scripts are obsolete. Please use [`nix_bsd_mac_inventory`]( https://github.com/device42/nix_bsd_mac_inventory ) script instead.** 3 | * d42\_api\_linux\_upload\_sample\_script.py 4 | * d42\_api\_solaris\_sample\_script.py 5 | * linux\_auto\_dics\_multi.py 6 | 7 | 8 | [Device42](http://www.device42.com/) is a comprehensive data center inventory management and IP Address management software that integrates centralized password management, impact charts and applications mappings with IT asset management. 9 | 10 | This project contains sample scripts to show how to use Device42 developer APIs and populate device42 appliance with your network inventory information. 11 | 12 | 13 | ## Scripts Provided 14 | ----------------------------- 15 | * **api-sample.py** : Runs against a single windows system and uploads info to device42 appliance 16 | * **ad-sample.py** : Can run against Active directory computers, servers or a given list and upload discovered systems' info to device42 appliance. 17 | * **d42_api_linux_upload_sample_script.py** : Runs on a single *nix based system and uploads info to device42 appliance. 18 | * **sample-script-facter-facts-to-d42.py** : Runs on puppet master and uploads nodes info from facter fact files to device42 appliance. 19 | * **d42_api_solaris_sample_script.py**: Runs on an individual solaris system and uploads info to device42 appliance. 20 | * **linux_auto_dics_multi.py**: Run on a *nix system with paramiko to get inventory using ssh from an IP range and upload to d42 appliance. 21 | * **winservice.py** : Can run against Active directory computers, servers or a given list and upload discovered services as application components to device42 appliance. 22 | 23 | ### Requirement 24 | ----------------------------- 25 | * python 2.7.x 26 | * ad-sample, api-sample and winservice scripts require Poweshell 1.0 or Powershell 2.0, .Net 4 and IronPython 2.7.x. 27 | * linux_auto_disc_multi requires installation of paramiko library. Install: sudo pip install paramiko (or Ubuntu/Debian: sudo apt-get install python-paramiko) 28 | 29 | ### Usage 30 | ----------------------------- 31 | 32 | * Follow the instructions in individual scripts. Instructions have been added as comments in the scripts provided. 33 | 34 | ### Further Documentation 35 | ---------------------------- 36 | * For api-sample.py: [Windows Single machine auto-discovery script][1] 37 | * For ad-sample.py: [Device42 windows AD based auto-disc script doc][2] 38 | * For linux_auto_disc_multi.py: [Python auto-discovery script to get system inventory info for linux machines on the network][3] 39 | 40 | 41 | [1]: http://docs.device42.com/auto-discovery/auto-discover-windows-machinesingle-apis/ 42 | [2]: http://docs.device42.com/auto-discovery/auto-populate-windows-machines-ad-apis/ 43 | [3]: http://blog.device42.com/2013/08/python-auto-discovery-script-to-get-system-inventory-info-for-linux-machines-on-the-network/ 44 | 45 | -------------------------------------------------------------------------------- /src/ad-sample.py: -------------------------------------------------------------------------------- 1 | """ 2 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 3 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 4 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 5 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 6 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 7 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 8 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | """ 10 | 11 | ############################################## 12 | # queries active directory for each computer 13 | # adds device and ip to device42 appliance via REST APIs 14 | # 15 | # Requires: 16 | # powershell 17 | # ironpython 18 | # .net 4 19 | # 20 | # to run: 21 | # ipy.exe ad-sample.py 22 | # v2.2, Updated: 02-08-2014 23 | ############################################## 24 | 25 | import types 26 | import os.path 27 | import urllib 28 | import urllib2 29 | import traceback 30 | import base64 31 | import System 32 | import clr 33 | import math 34 | import ssl 35 | import functools 36 | clr.AddReference("System.DirectoryServices") 37 | clr.AddReference('System.Management.Automation') 38 | 39 | from System.Management.Automation import RunspaceInvoke 40 | # +--------------------------------------------------------------------------- 41 | 42 | # create a runspace to run shell commands from 43 | RUNSPACE = RunspaceInvoke() 44 | 45 | BASE_URL='https://your-url-here' #make sure to NOT to end in / 46 | 47 | API_DEVICE_URL=BASE_URL+'/api/device/' 48 | API_IP_URL =BASE_URL+'/api/ip/' 49 | 50 | USER ='put-your-user-name-here' 51 | PASSWORD='put-your-password-here' 52 | 53 | DRY_RUN = False # donot post just print the request that will be send 54 | DEBUG = True 55 | 56 | old_init = ssl.SSLSocket.__init__ 57 | @functools.wraps(old_init) 58 | def init_with_tls1(self, *args, **kwargs): 59 | kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1 60 | old_init(self, *args, **kwargs) 61 | ssl.SSLSocket.__init__ = init_with_tls1 62 | 63 | 64 | def post(url, params): 65 | """http post with basic-auth params is dict like object""" 66 | try: 67 | data= urllib.urlencode(params) # convert to ascii chars 68 | headers = { 69 | 'Authorization' : 'Basic '+ base64.b64encode(USER + ':' + PASSWORD), 70 | 'Content-Type' : 'application/x-www-form-urlencoded' 71 | } 72 | 73 | if DRY_RUN: 74 | print url, headers, data 75 | else: 76 | req = urllib2.Request(url, data, headers) 77 | 78 | if DEBUG: print '---REQUEST---',req.get_full_url() 79 | if DEBUG: print req.headers 80 | if DEBUG: print req.data 81 | 82 | reponse = urllib2.urlopen(req, context=ssl._create_unverified_context()) 83 | 84 | if DEBUG: print '---RESPONSE---' 85 | if DEBUG: print reponse.getcode() 86 | if DEBUG: print reponse.info() 87 | if DEBUG: print reponse.read() 88 | except urllib2.HTTPError as err: 89 | print '---RESPONSE---' 90 | if DEBUG: print err.getcode() 91 | if DEBUG: print err.info() 92 | if DEBUG: print err.read() 93 | except urllib2.URLError as err: 94 | print '---RESPONSE---' 95 | print err 96 | 97 | def get_computers(): 98 | """Enumerates ALL computer objects in AD""" 99 | searcher = System.DirectoryServices.DirectorySearcher() 100 | searcher.SearchRoot = System.DirectoryServices.DirectoryEntry() 101 | searcher.Filter = "(objectCategory=computer)" 102 | searcher.PropertiesToLoad.Add("name") 103 | return sorted([a for item in searcher.FindAll() for a in item.Properties['name']]) 104 | 105 | def get_servers(): 106 | """Enumerates ALL Servers objects in AD""" 107 | searcher = System.DirectoryServices.DirectorySearcher() 108 | searcher.SearchRoot = System.DirectoryServices.DirectoryEntry() 109 | searcher.Filter = "(&(objectCategory=computer)(OperatingSystem=Windows*Server*))" 110 | searcher.PropertiesToLoad.Add("name") 111 | return sorted([a for item in searcher.FindAll() for a in item.Properties['name']]) 112 | 113 | def get_fromfile(): 114 | """Enumerates Computer Names in a text file Create a text file and enter 115 | the names of each computer. One computer name per line. Supply the path 116 | to the text file when prompted. 117 | """ 118 | while True: 119 | filename = raw_input('Enter the path for the text file: ') 120 | if filename: 121 | if not os.path.exists(filename): 122 | print "file not exists or insufficient permissions '%s'" % filename 123 | elif not os.path.isfile(filename): 124 | print "not a file, may be a dir '%s'" % filename 125 | else: 126 | f = open(filename) 127 | try: computers = [line.strip() for line in f] 128 | finally: f.close() 129 | return sorted(computers) 130 | 131 | def get_frommanualentry(): 132 | """'SingleEntry' - Enumerates Computer from user input""" 133 | while True: 134 | c = raw_input('Enter Computer Name or IP: ') 135 | if c: return [c] 136 | 137 | def wmi(query): 138 | """create list of dict from result of wmi query""" 139 | return [dict([(prop.Name, prop.Value) for prop in psobj.Properties]) 140 | for psobj in RUNSPACE.Invoke(query)] 141 | 142 | def wmi_1(query): 143 | a = wmi(query) 144 | if a: return a[0] 145 | else: return {} 146 | 147 | def to_ascii(s): 148 | """remove non-ascii characters""" 149 | if type(s) == types.StringType: 150 | return s.encode('ascii','ignore') 151 | else: 152 | return str(s) 153 | 154 | def closest_memory_assumption(v): 155 | if v < 512: v = 128 * math.ceil(v / 128.0) 156 | elif v < 1024: v = 256 * math.ceil(v / 256.0) 157 | elif v < 4096: v = 512 * math.ceil(v / 512.0) 158 | elif v < 8192: v = 1024 * math.ceil(v / 1024.0) 159 | else: v = 2048 * math.ceil(v / 2048.0) 160 | return int(v) 161 | 162 | def main(): 163 | banner="""\ 164 | 165 | +----------------------------------------------------+ 166 | | Domain Admin rights are required to enumerate information | 167 | +----------------------------------------------------+ 168 | """ 169 | print banner 170 | 171 | menu="""\ 172 | Which computer resources would you like to run auto-discovery on? 173 | [1] All Domain Computers 174 | [2] All Domain Servers 175 | [3] Computer names from a File 176 | [4] Choose a Computer manually 177 | """ 178 | while True: 179 | resp = raw_input(menu) 180 | if resp == '1': computers = get_computers(); break 181 | elif resp == '2': computers = get_servers(); break 182 | elif resp == '3': computers = get_fromfile(); break 183 | elif resp == '4': computers = get_frommanualentry(); break 184 | 185 | if not computers: 186 | print "ERROR: No computer found" 187 | else: 188 | for c in computers: 189 | try: 190 | computer_system = wmi_1("Get-WmiObject Win32_ComputerSystem -Comp %s" % c) 191 | operating_system = wmi_1("Get-WmiObject Win32_OperatingSystem -Comp %s" % c) 192 | bios = wmi_1("Get-WmiObject Win32_BIOS -Comp %s" % c) 193 | mem = closest_memory_assumption(int(computer_system.get('TotalPhysicalMemory')) / 1047552) 194 | dev_name = to_ascii(computer_system.get('Name')).lower() 195 | device = { 196 | 'name' : dev_name, 197 | 'memory' : mem, 198 | } 199 | if 'Caption' in operating_system: 200 | device.update({'os' : to_ascii(operating_system.get('Caption'))}) 201 | if 'CSDVersion' in operating_system: device.update({'osver' : to_ascii(operating_system.get('CSDVersion'))}) 202 | if 'Manufacturer' in operating_system: device.update({'osmanufacturer': to_ascii(operating_system.get('Manufacturer'))}) 203 | if 'SerialNumber' in operating_system: device.update({'osserial' : to_ascii(operating_system.get('SerialNumber'))}) 204 | if 'Version' in operating_system: device.update({'osverno' : to_ascii(operating_system.get('Version'))}) 205 | manufacturer = '' 206 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', 'Xen']: 207 | if mftr == to_ascii(computer_system.get('Manufacturer')).strip(): 208 | manufacturer = 'virtual' 209 | device.update({ 'manufacturer' : 'vmware', }) 210 | break 211 | if manufacturer != 'virtual': 212 | device.update({ 213 | 'manufacturer': to_ascii(computer_system.get('Manufacturer')).strip(), 214 | 'hardware': to_ascii(computer_system.get('Model')).strip(), 215 | 'serial_no': to_ascii(bios.get('SerialNumber')).strip(), 216 | }) 217 | cpucount = 0 218 | for cpu in wmi("Get-WmiObject Win32_Processor -Comp %s" % c): 219 | cpucount += 1 220 | cpuspeed = cpu.get('MaxClockSpeed') 221 | cpucores = cpu.get('NumberOfCores') 222 | if cpucount > 0: 223 | 224 | device.update({ 225 | 'cpucount': cpucount, 226 | 'cpupower': cpuspeed, 227 | 'cpucore': cpucores, 228 | }) 229 | 230 | 231 | post(API_DEVICE_URL, device) 232 | 233 | for ntwk in wmi("Get-WmiObject Win32_NetworkAdapterConfiguration -Comp %s | where{$_.IPEnabled -eq \"True\"}" % c): 234 | for ipaddr in ntwk.get('IPAddress'): 235 | ip = { 236 | 'ipaddress' : ipaddr, 237 | 'macaddress' : ntwk.get('MACAddress'), 238 | 'tag' : ntwk.get('Description'), 239 | 'device' : dev_name, 240 | } 241 | try: post(API_IP_URL, ip) 242 | except: print 'Exception occured trying to upload info for IP: %s' % ipaddr 243 | except Exception, err: 244 | print 'failed for machine', c, str(err) 245 | if __name__=="__main__": 246 | main() -------------------------------------------------------------------------------- /src/api-sample.py: -------------------------------------------------------------------------------- 1 | """ 2 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 3 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 4 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 5 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 6 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 7 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 8 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | """ 10 | ################################################## 11 | # a sample script to show how to use 12 | # /api/ip/add-or-update 13 | # /api/device/add-or-update 14 | # 15 | # requires ironPython (http://ironpython.codeplex.com/) and 16 | # powershell (http://support.microsoft.com/kb/968929) 17 | ################################################## 18 | 19 | import clr 20 | 21 | clr.AddReference('System.Management') # Added for DateTime Conversion 22 | clr.AddReference('System.Management.Automation') 23 | 24 | from System.Management.Automation import ( 25 | PSMethod, RunspaceInvoke 26 | ) 27 | RUNSPACE = RunspaceInvoke() 28 | 29 | import urllib 30 | import urllib2 31 | import traceback 32 | import base64 33 | import math 34 | import ssl 35 | import functools 36 | BASE_URL = 'https://d42applianceaddress' 37 | 38 | API_DEVICE_URL = BASE_URL + '/api/1.0/devices/' 39 | API_IP_URL = BASE_URL + '/api/1.0/ips/' 40 | API_CUSTOMFIELD_URL = BASE_URL+'/api/1.0/device/custom_field/' 41 | 42 | USER = 'd42username' 43 | PASSWORD = 'd42password' 44 | 45 | 46 | old_init = ssl.SSLSocket.__init__ 47 | @functools.wraps(old_init) 48 | def init_with_tls1(self, *args, **kwargs): 49 | kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1 50 | old_init(self, *args, **kwargs) 51 | ssl.SSLSocket.__init__ = init_with_tls1 52 | 53 | 54 | def api_call(url, method, params=None): 55 | """ 56 | http with basic-auth 57 | method is string of http method 58 | params is dict like object 59 | """ 60 | try: 61 | data = urllib.urlencode(dict([k, str(v).encode('utf-8')] for k, v in params.items())) # convert to ascii chars 62 | headers = { 63 | 'Authorization': 'Basic ' + base64.b64encode(USER + ':' + PASSWORD), 64 | 'Content-Type': 'application/x-www-form-urlencoded' 65 | } 66 | 67 | if params: 68 | req = urllib2.Request(url=url, data=data, headers=headers) 69 | else: 70 | req = urllib2.Request(url=url, headers=headers) 71 | if method == 'PUT' : 72 | req.get_method = lambda: method 73 | 74 | print '---REQUEST---', req.get_full_url() 75 | print req.headers 76 | print req.data 77 | 78 | reponse = urllib2.urlopen(req, context=ssl._create_unverified_context()) 79 | 80 | print '---RESPONSE---' 81 | print reponse.getcode() 82 | print reponse.info() 83 | print reponse.read() 84 | except urllib2.HTTPError as err: 85 | print '---RESPONSE---' 86 | print err.getcode() 87 | print err.info() 88 | print err.read() 89 | except urllib2.URLError as err: 90 | print '---RESPONSE---' 91 | print err 92 | 93 | 94 | def to_ascii(s): 95 | # ignore non-ascii chars 96 | return s.encode('ascii', 'ignore') 97 | 98 | 99 | def wmi(query): 100 | return [dict([(prop.Name, prop.Value) for prop in psobj.Properties]) for psobj in RUNSPACE.Invoke(query)] 101 | 102 | 103 | def closest_memory_assumption(v): 104 | return int(256 * math.ceil(v / 256.0)) 105 | 106 | 107 | def add_or_update_device(): 108 | computer_system = wmi('Get-WmiObject Win32_ComputerSystem -Namespace "root\CIMV2"')[0] # take first 109 | bios = wmi('Get-WmiObject Win32_BIOS -Namespace "root\CIMV2"')[0] 110 | operating_system = wmi('Get-WmiObject Win32_OperatingSystem -Namespace "root\CIMV2"')[0] 111 | mem = closest_memory_assumption(int(computer_system.get('TotalPhysicalMemory')) / 1047552) 112 | dev_name = to_ascii(computer_system.get('Name')).lower() 113 | device = { 114 | 'name': dev_name, 115 | 'memory': mem, 116 | 'os': to_ascii(operating_system.get('Caption')), 117 | 'osver': operating_system.get('CSDVersion'), 118 | 'osmanufacturer': to_ascii(operating_system.get('Manufacturer')), 119 | 'osserial': operating_system.get('SerialNumber'), 120 | 'osverno': operating_system.get('Version'), 121 | } 122 | manufacturer = '' 123 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', 'Xen']: 124 | if mftr == to_ascii(computer_system.get('Manufacturer')).strip(): 125 | manufacturer = 'virtual' 126 | device.update({'manufacturer': 'vmware', }) 127 | break 128 | if manufacturer != 'virtual': 129 | device.update({ 130 | 'manufacturer': to_ascii(computer_system.get('Manufacturer')).strip(), 131 | 'hardware': to_ascii(computer_system.get('Model')).strip(), 132 | 'serial_no': to_ascii(bios.get('SerialNumber')).strip(), 133 | }) 134 | cpucount = 0 135 | for cpu in wmi('Get-WmiObject Win32_Processor -Namespace "root\CIMV2"'): 136 | cpucount += 1 137 | cpuspeed = cpu.get('MaxClockSpeed') 138 | cpucores = cpu.get('NumberOfCores') 139 | if cpucount > 0: 140 | device.update({ 141 | 'cpucount': cpucount, 142 | 'cpupower': cpuspeed, 143 | 'cpucore': cpucores, 144 | }) 145 | api_call(API_DEVICE_URL, 'POST', device) 146 | 147 | network_adapter_configuration = wmi('Get-WmiObject Win32_NetworkAdapterConfiguration -Namespace "root\CIMV2" | where{$_.IPEnabled -eq "True"}') 148 | for ntwk in network_adapter_configuration: 149 | for ipaddr in ntwk.get('IPAddress'): 150 | ip = { 151 | 'ipaddress' : ipaddr, 152 | 'macaddress': ntwk.get('MACAddress'), 153 | 'tag': ntwk.get('Description'), 154 | 'device': dev_name, 155 | } 156 | api_call(API_IP_URL, 'POST', ip) 157 | 158 | # Update Custom Field using PUT method 159 | domname = to_ascii(computer_system.get('Domain')).lower() 160 | dom = { 161 | 'name': dev_name, 162 | 'key': 'Domain', 163 | 'value': domname 164 | } 165 | api_call(API_CUSTOMFIELD_URL, 'PUT', dom) 166 | 167 | def main(): 168 | try: 169 | add_or_update_device() 170 | except: 171 | traceback.print_exc() 172 | 173 | if __name__ == "__main__": 174 | main() 175 | -------------------------------------------------------------------------------- /src/d42_api_linux_upload_sample_script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | NOTE: 5 | This script is obsolete. 6 | Please use "nix_bsd_mac_inventory (https://github.com/device42/nix_bsd_mac_inventory)" script instead. 7 | 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 10 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 12 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 13 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 15 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | ################################################################ 19 | # v1.1.0 of linux script that 20 | # gets system info on a *nix based system, parses it and 21 | # uploads to device42 appliance using APIs 22 | # tested on Redhat, Fedora and Ubuntu installations 23 | # OS Detection doesn't work correctly on CentOS 5.x. 24 | # Set GET_OS_DETAILS = False, if you want to ignore OS details 25 | ############################################################### 26 | 27 | import urllib 28 | import urllib2 29 | import traceback 30 | from base64 import b64encode 31 | import subprocess 32 | import math 33 | import simplejson as json 34 | 35 | ##### Change Following 11 lines to match your environment ##### 36 | D42_API_URL = 'https://your-d42-IP-or-FQDN-here' #make sure to not end in / 37 | D42_USERNAME = 'your-d42-username-here' 38 | D42_PASSWORD = 'your-d42-password-here' 39 | GET_SERIAL_INFO = True 40 | GET_HARDWARE_INFO = True 41 | GET_OS_DETAILS = True 42 | GET_CPU_INFO = True 43 | GET_MEMORY_INFO = True 44 | uploadipv6 = True 45 | ignoreDomain = True #If you want to strip the domain name part from the hostname. 46 | DEBUG = False #True if you want to print detailed debug log 47 | 48 | def to_ascii(s): # ignore non-ascii chars 49 | try: return s.encode('ascii','ignore') 50 | except: return None 51 | 52 | def post(params, what): 53 | if what == 'device': THE_URL = D42_API_URL + '/api/device/' 54 | elif what == 'ip': THE_URL = D42_API_URL + '/api/ip/' 55 | data= urllib.urlencode(params) 56 | headers = { 57 | 'Authorization' : 'Basic '+ b64encode(D42_USERNAME + ':' + D42_PASSWORD), 58 | 'Content-Type' : 'application/x-www-form-urlencoded' 59 | } 60 | req = urllib2.Request(THE_URL, data, headers) 61 | if DEBUG: print '---REQUEST---',req.get_full_url() 62 | if DEBUG: print req.headers 63 | if DEBUG: print req.data 64 | try: 65 | r = urllib2.urlopen(req) 66 | if r.getcode() == 200: 67 | obj = r.read() 68 | msg = json.loads(obj) 69 | return True, msg 70 | else: 71 | return False, r.getcode() 72 | except urllib2.HTTPError, e: 73 | error_response = e.read() 74 | if DEBUG: print e.code, error_response 75 | return False, error_response 76 | except Exception,e: 77 | return False, str(e) 78 | 79 | def linux(): 80 | device_name = subprocess.Popen(['/bin/hostname'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 81 | if ignoreDomain: device_name = to_ascii(device_name).rstrip().split('.')[0] 82 | else: device_name = to_ascii(device_name).rstrip() 83 | device = {'name': device_name,} 84 | uuid = subprocess.Popen(['sudo', '/usr/sbin/dmidecode', '-s', 'system-uuid'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 85 | if uuid and uuid != '': device.update({'uuid': to_ascii(uuid).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").rstrip()}) 86 | 87 | if GET_OS_DETAILS: 88 | release = subprocess.Popen(['/usr/bin/python', '-m' 'platform'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 89 | device.update({'os': release.split('-with-')[1].split('-')[0], 'osver': release.split('-with-')[1].split('-')[1]}) 90 | 91 | if GET_SERIAL_INFO: 92 | serial_no = subprocess.Popen(['sudo', '/usr/sbin/dmidecode', '-s', 'system-serial-number'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 93 | if serial_no and serial_no != '': device.update({'serial_no': to_ascii(serial_no).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip()}) 94 | 95 | manufacturer = subprocess.Popen(['sudo', '/usr/sbin/dmidecode', '-s', 'system-manufacturer'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 96 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', 'Xen']: 97 | if mftr == to_ascii(manufacturer).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip(): 98 | manufacturer = 'virtual' 99 | device.update({ 'type' : 'virtual', }) 100 | break 101 | if manufacturer != 'virtual' and GET_HARDWARE_INFO: 102 | hardware = subprocess.Popen(['sudo', '/usr/sbin/dmidecode', '-s', 'system-product-name'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 103 | if hardware and hardware != '': 104 | device.update({ 105 | 'manufacturer': to_ascii(manufacturer).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip(), 106 | 'hardware': to_ascii(hardware).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip(), 107 | }) 108 | 109 | if GET_MEMORY_INFO: 110 | memory_total = subprocess.Popen(['grep', 'MemTotal', '/proc/meminfo'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0].replace(' ', '').replace('MemTotal:','').replace('kB','') 111 | memory = closest_memory_assumption(int(memory_total)/1024) 112 | device.update({'memory': memory}) 113 | 114 | if GET_CPU_INFO: 115 | cpucount = 0 116 | cpuspeed = '' 117 | corecount = 0 118 | 119 | cpucountinfo = subprocess.Popen(['cat', '/proc/cpuinfo'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 120 | for item in cpucountinfo.split('\n'): 121 | if 'processor' in item: 122 | cpucount += 1 123 | 124 | cpuinfo = subprocess.Popen(['sudo', 'dmidecode', '-s', 'processor-frequency'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 125 | for item in cpuinfo.split('\n'): 126 | if 'MHz' in item: 127 | cpuspeed = item.split(' ')[0] 128 | break 129 | 130 | coreinfo = subprocess.Popen(['sudo', 'dmidecode', '-t', 'processor'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 131 | for item in coreinfo.split('\n'): 132 | if 'Core Count' in item: 133 | corecount = int(item.replace('Core Count: ', '').strip()) 134 | break 135 | if corecount == 0: corecount = 1 136 | 137 | if cpucount != 0: 138 | cpucount /= corecount 139 | device.update({ 140 | 'cpucount': cpucount, 141 | 'cpucore': corecount, 142 | }) 143 | if cpuspeed != '': 144 | device.update({'cpupower': cpuspeed,}) 145 | 146 | ADDED, msg = post(device, 'device') 147 | if ADDED: 148 | print 'Device done: ' + str(msg) 149 | device_name_in_d42 = msg['msg'][2] 150 | 151 | ipinfo = subprocess.Popen(['/sbin/ifconfig', '-a'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] 152 | ipinfo_lines = ipinfo.split('\n') 153 | 154 | for i, item in enumerate(ipinfo_lines): 155 | if 'Ethernet' in item: 156 | if 'inet addr' in ipinfo_lines[i+1]: 157 | ipv4_address = ipinfo_lines[i+1].split()[1].replace('addr:', '') 158 | ip = { 159 | 'ipaddress': ipv4_address, 160 | 'tag': item.split("\n")[0].split()[0], 161 | 'macaddress' : item.split("\n")[0].split()[4], 162 | 'device' : device_name_in_d42, 163 | } 164 | ADDED, msg_ip = post(ip, 'ip') 165 | if ADDED: 166 | print ipv4_address + ': ' + str(msg_ip) 167 | else: 168 | print ipv4_address + ': failed with message = ' + str(msg_ip) 169 | if uploadipv6 and ('inet6 addr' in ipinfo_lines[i+1] or 'inet6 addr' in ipinfo_lines[i+2]): 170 | if 'inet6 addr' in ipinfo_lines[i+1]: ipv6_address = ipinfo_lines[i+1].split()[2].split('/')[0] 171 | else: ipv6_address = ipinfo_lines[i+2].split()[2].split('/')[0] 172 | ip = { 173 | 'ipaddress': ipv6_address, 174 | 'tag': item.split("\n")[0].split()[0], 175 | 'macaddress' : item.split("\n")[0].split()[4], 176 | 'device' : device_name_in_d42, 177 | } 178 | ADDED, msg_ip = post(ip, 'ip') 179 | if ADDED: 180 | print ipv4_address + ': ' + str(msg_ip) 181 | else: 182 | print ipv4_address + ': failed with message = ' + str(msg_ip) 183 | else: 184 | print 'Failed with message: ' + str(msg) 185 | 186 | def closest_memory_assumption(v): 187 | if v < 512: v = 128 * math.ceil(v / 128.0) 188 | elif v < 1024: v = 256 * math.ceil(v / 256.0) 189 | elif v < 4096: v = 512 * math.ceil(v / 512.0) 190 | elif v < 8192: v = 1024 * math.ceil(v / 1024.0) 191 | else: v = 2048 * math.ceil(v / 2048.0) 192 | return int(v) 193 | 194 | 195 | def main(): 196 | try: 197 | linux() 198 | except: 199 | traceback.print_exc() 200 | 201 | if __name__ == "__main__": 202 | main() 203 | -------------------------------------------------------------------------------- /src/d42_api_solaris_sample_script.py: -------------------------------------------------------------------------------- 1 | """ 2 | NOTE: 3 | This script is obsolete. 4 | Please use "nix_bsd_mac_inventory (https://github.com/device42/nix_bsd_mac_inventory)" script instead. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 7 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 8 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 9 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 10 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 11 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 12 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | """ 14 | 15 | ############################################## 16 | # v1.0.0P1 Beta of solaris script that 17 | # gets system info from a solaris system, parses it and 18 | # uploads to device42 appliance using APIs 19 | # tested on Solaris Sparc 10(sparc and x86) 20 | ############################################## 21 | import types 22 | import subprocess 23 | import re 24 | import urllib2 25 | import urllib 26 | import base64 27 | 28 | ##### Change Following 4 lines to match your environment ##### 29 | d42url = 'https://your-d42-url-here' 30 | urluser = 'your-d42-username-here' 31 | urlpass = 'your-d42-password-here' 32 | ignoreDomain = True #If you want to strip the domain name part from the hostname. 33 | 34 | def post(url, params): 35 | result = '' 36 | try: 37 | data= urllib.urlencode(params) 38 | headers = { 39 | 'Authorization' : 'Basic '+ base64.b64encode(urluser + ':' + urlpass), 40 | 'Content-Type' : 'application/x-www-form-urlencoded' 41 | } 42 | 43 | req = urllib2.Request(url, data, headers) 44 | 45 | print '---REQUEST---',req.get_full_url() 46 | print req.headers 47 | print req.data 48 | reponse = urllib2.urlopen(req) 49 | 50 | print '---RESPONSE---' 51 | print reponse 52 | result = str(reponse.read()) 53 | except Exception, Err: 54 | print '-----EXCEPTION OCCURED-----' 55 | print str(Err) 56 | return result 57 | 58 | def to_ascii(s): 59 | """remove non-ascii characters""" 60 | if type(s) == types.StringType: 61 | return s.encode('ascii','ignore') 62 | else: 63 | return str(s) 64 | def cpu(): 65 | cpu_info = {} 66 | cpu= subprocess.Popen(['psrinfo', '-v'], stdout=subprocess.PIPE) 67 | i = cpu.stdout.readlines()[2] 68 | info = re.findall("The (.+) processor operates at (.+) MHz", i) 69 | cpu_info['cpupower'] = info[0][1] 70 | cpu= subprocess.Popen(['psrinfo', '-p'], stdout=subprocess.PIPE) 71 | cpus = cpu.stdout.readlines() 72 | cpu_info['cpucount'] = cpus[0][0] 73 | cpu = subprocess.Popen(['kstat', 'cpu_info'], stdout=subprocess.PIPE) 74 | cores = 0 75 | for x in cpu.stdout.readlines(): 76 | if "core_id" in x: 77 | cores +=1 78 | cpu_info["Cores"] = str(cores) 79 | try: 80 | cpu_info["cpucore"] = str(cores/int(cpus[0][0])) 81 | except: 82 | cpu_info["Cores per CPU"] = "N/A" 83 | return cpu_info 84 | 85 | def memory(): 86 | memory_info = {} 87 | mem = subprocess.Popen(['prtconf'], stdout = subprocess.PIPE) 88 | for x in mem.stdout.readlines(): 89 | if "Memory" in x: 90 | break 91 | m = re.findall("Memory size: (.+)", x)[0].split(" ") 92 | memory_info['memory'] = m[0] 93 | return memory_info 94 | 95 | def ip(): 96 | ip_info = [] 97 | i = subprocess.Popen(['ifconfig', '-a'], stdout=subprocess.PIPE) 98 | out = i.stdout.readlines() 99 | devices = [] 100 | num = 0 101 | for x in out: 102 | if ":" in x and "ether" not in x: 103 | devices.append([x]) 104 | if len(devices)!=1: 105 | num +=1 106 | else: 107 | devices[num].append(x) 108 | for x in devices: 109 | device_specs = {} 110 | switch = {'inet':'ipaddress', 'ether':'macaddress'} 111 | device_name = x[0].split(" ")[0].strip(":") 112 | dev_info = "".join(x) 113 | specs = [] 114 | specs.append(re.compile("(inet .+)", re.DOTALL).findall(dev_info)) 115 | #----Extra information not passed to the post function, can be added in future by 116 | #----referencing it as "list[number].split()[1]" ([0] is its name) 117 | specs.append(re.compile("(netmask .+)", re.DOTALL).findall(dev_info)) 118 | specs.append(re.compile("(broadcast .+)", re.DOTALL).findall(dev_info)) 119 | specs.append(re.compile("(groupname .+)", re.DOTALL).findall(dev_info)) 120 | #---- 121 | specs.append(re.compile("(ether .+)", re.DOTALL).findall(dev_info)) 122 | for spec in specs: 123 | if spec != []: 124 | if spec[0].split()[0].strip() == "inet" or spec[0].split()[0].strip() == "ether": 125 | device_specs[switch[spec[0].split()[0].strip()]] = spec[0].split()[1].strip() 126 | device_specs.update({'tag':device_name}) 127 | ip_info.append(device_specs) 128 | return ip_info 129 | 130 | def sys(): 131 | sys_info = {} 132 | info = subprocess.Popen(["uname", "-i"], stdout = subprocess.PIPE) 133 | i = info.stdout.readline() 134 | prtdiag = subprocess.Popen(["/usr/platform/"+i.strip()+"/sbin/prtdiag"], stdout = subprocess.PIPE) 135 | prtdiagout = prtdiag.stdout.readline() 136 | uname = subprocess.Popen(['uname', '-a'], stdout = subprocess.PIPE) 137 | u = uname.stdout.readline() 138 | sys_info['os'] = u.split()[0] + " " + u.split()[2] 139 | sys_info['osver'] = u.split()[3] 140 | showrev = subprocess.Popen(['showrev', '-p'], stdout = subprocess.PIPE) 141 | p = showrev.stdout.readlines() 142 | patch = p[len(p)-1].split()[1] 143 | sys_info['osverno'] = patch 144 | sys = re.findall("System Configuration: (.+)", prtdiagout)[0] 145 | manufacturer = " ".join(sys.split()[0:2]) 146 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', 'Xen', 'innotek']: 147 | if mftr.lower() == to_ascii(manufacturer).lower(): 148 | manufacturer = 'virtual' 149 | sys_info['manufacturer'] = 'vmware' 150 | break 151 | if manufacturer != 'virtual': 152 | sys_info['manufacturer'] = manufacturer 153 | sys_info['hardware'] = " ".join(sys.split(" ")[len(sys.split(" "))-3:]) 154 | try: 155 | smbios = subprocess.Popen(['smbios'], stdout = subprocess.PIPE) 156 | smbiosout = smbios.stdout.readlines() 157 | for line in smbiosout: 158 | if "Serial Number:" in line: 159 | sys_info['serial_no'] = line.strip("Serial Number: ").strip() 160 | except: 161 | pass 162 | return sys_info 163 | 164 | if d42url[:-1] == '/': 165 | API_IP_URL = d42url + 'api/ip/' 166 | API_DEVICE_URL = d42url + 'api/device/' 167 | else: 168 | API_IP_URL = d42url + '/api/ip/' 169 | API_DEVICE_URL = d42url + '/api/device/' 170 | name = subprocess.Popen(['hostname'], stdout = subprocess.PIPE) 171 | name = name.stdout.readlines()[0].strip("\n") 172 | if ignoreDomain: name = to_ascii(name).strip().split('.')[0] 173 | else: name = to_ascii(name).strip() 174 | device = {'name':name} 175 | device.update(cpu()) 176 | device.update(sys()) 177 | device.update(memory()) 178 | post(API_DEVICE_URL, device) 179 | i = {} 180 | for i in ip(): 181 | i['device'] = name 182 | if 'macaddress' in i: i['macaddress'] = ":".join([j.zfill(2) for j in i['macaddress'].split(":")]).lower() 183 | if i.get('ipaddress') is not None and i.get('ipaddress') != '127.0.0.1' and i.get('ipaddress') != '0.0.0.0': post(API_IP_URL, i) 184 | 185 | -------------------------------------------------------------------------------- /src/ipcalc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pep8-ignore: E501 3 | 4 | ''' 5 | ==================================== 6 | :mod:`ipcalc` IP subnet calculator 7 | ==================================== 8 | .. moduleauthor:: Wijnand Modderman-Lenstra 9 | .. note:: BSD License 10 | 11 | About 12 | ===== 13 | 14 | This module allows you to perform network calculations. 15 | 16 | References 17 | ========== 18 | 19 | References: 20 | * http://www.estoile.com/links/ipv6.pdf 21 | * http://www.iana.org/assignments/ipv4-address-space 22 | * http://www.iana.org/assignments/multicast-addresses 23 | * http://www.iana.org/assignments/ipv6-address-space 24 | * http://www.iana.org/assignments/ipv6-tla-assignments 25 | * http://www.iana.org/assignments/ipv6-multicast-addresses 26 | * http://www.iana.org/assignments/ipv6-anycast-addresses 27 | 28 | Thanks 29 | ====== 30 | 31 | I wish to thank the following people for their input: 32 | * Bas van Oostveen (trbs) 33 | * Peter van Dijk (Habbie) 34 | * Hans van Kranenburg (Knorrie) 35 | * Jeroen Habraken (VeXocide) 36 | * Torbjörn Lönnemark (tobbez) 37 | * Anthony Cornehl (twinshadow) 38 | 39 | ''' 40 | 41 | __version__ = '1.0.0' 42 | 43 | 44 | try: 45 | bin 46 | except NameError: 47 | def bin(x): 48 | ''' 49 | Stringifies an int or long in base 2. 50 | ''' 51 | if x < 0: 52 | return '-%s' % bin(-x) 53 | out = [] 54 | if x == 0: 55 | out.append('0') 56 | while x > 0: 57 | out.append('01'[x & 1]) 58 | x >>= 1 59 | out.reverse() 60 | return '0b%s' % ''.join(out) 61 | 62 | 63 | class IP(object): 64 | ''' 65 | Represents a single IP address. 66 | 67 | :param ip: the ip address 68 | :type ip: :class:`IP` or str or long or int 69 | 70 | >>> localhost = IP("127.0.0.1") 71 | >>> print localhost 72 | 127.0.0.1 73 | >>> localhost6 = IP("::1") 74 | >>> print localhost6 75 | 0000:0000:0000:0000:0000:0000:0000:0001 76 | ''' 77 | 78 | # Hex-to-Bin conversion masks 79 | _bitmask = { 80 | '0': '0000', '1': '0001', '2': '0010', '3': '0011', 81 | '4': '0100', '5': '0101', '6': '0110', '7': '0111', 82 | '8': '1000', '9': '1001', 'a': '1010', 'b': '1011', 83 | 'c': '1100', 'd': '1101', 'e': '1110', 'f': '1111' 84 | } 85 | 86 | # IP range specific information, see IANA allocations. 87 | _range = { 88 | 4: { 89 | '01': 'CLASS A', 90 | '10': 'CLASS B', 91 | '110': 'CLASS C', 92 | '1110': 'CLASS D MULTICAST', 93 | '11100000': 'CLASS D LINKLOCAL', 94 | '1111': 'CLASS E', 95 | '00001010': 'PRIVATE RFC1918', # 10/8 96 | '101011000001': 'PRIVATE RFC1918', # 172.16/12 97 | '1100000010101000': 'PRIVATE RFC1918', # 192.168/16 98 | }, 99 | 6: { 100 | '00000000': 'RESERVED', # ::/8 101 | '00000001': 'UNASSIGNED', # 100::/8 102 | '0000001': 'NSAP', # 200::/7 103 | '0000010': 'IPX', # 400::/7 104 | '0000011': 'UNASSIGNED', # 600::/7 105 | '00001': 'UNASSIGNED', # 800::/5 106 | '0001': 'UNASSIGNED', # 1000::/4 107 | '0010000000000000': 'RESERVED', # 2000::/16 Reserved 108 | '0010000000000001': 'ASSIGNABLE', # 2001::/16 Sub-TLA Assignments [RFC2450] 109 | '00100000000000010000000': 'ASSIGNABLE IANA', # 2001:0000::/29 - 2001:01F8::/29 IANA 110 | '00100000000000010000001': 'ASSIGNABLE APNIC', # 2001:0200::/29 - 2001:03F8::/29 APNIC 111 | '00100000000000010000010': 'ASSIGNABLE ARIN', # 2001:0400::/29 - 2001:05F8::/29 ARIN 112 | '00100000000000010000011': 'ASSIGNABLE RIPE', # 2001:0600::/29 - 2001:07F8::/29 RIPE NCC 113 | '0010000000000010': '6TO4', # 2002::/16 "6to4" [RFC3056] 114 | '0011111111111110': '6BONE TEST', # 3ffe::/16 6bone Testing [RFC2471] 115 | '0011111111111111': 'RESERVED', # 3fff::/16 Reserved 116 | '010': 'GLOBAL-UNICAST', # 4000::/3 117 | '011': 'UNASSIGNED', # 6000::/3 118 | '100': 'GEO-UNICAST', # 8000::/3 119 | '101': 'UNASSIGNED', # a000::/3 120 | '110': 'UNASSIGNED', # c000::/3 121 | '1110': 'UNASSIGNED', # e000::/4 122 | '11110': 'UNASSIGNED', # f000::/5 123 | '111110': 'UNASSIGNED', # f800::/6 124 | '1111110': 'UNASSIGNED', # fc00::/7 125 | '111111100': 'UNASSIGNED', # fe00::/9 126 | '1111111010': 'LINKLOCAL', # fe80::/10 127 | '1111111011': 'SITELOCAL', # fec0::/10 128 | '11111111': 'MULTICAST', # ff00::/8 129 | '0' * 96: 'IPV4COMP', # ::/96 130 | '0' * 80 + '1' * 16: 'IPV4MAP', # ::ffff:0:0/96 131 | '0' * 128: 'UNSPECIFIED', # ::/128 132 | '0' * 127 + '1': 'LOOPBACK' # ::1/128 133 | } 134 | } 135 | 136 | def __init__(self, ip, mask=None, version=0): 137 | self.mask = mask 138 | self.v = 0 139 | # Parse input 140 | if ip is None: 141 | raise ValueError('Can not pass None') 142 | elif isinstance(ip, IP): 143 | self.ip = ip.ip 144 | self.dq = ip.dq 145 | self.v = ip.v 146 | self.mask = ip.mask 147 | elif isinstance(ip, (int, long)): 148 | self.ip = long(ip) 149 | if self.ip <= 0xffffffff: 150 | self.v = version or 4 151 | self.dq = self._itodq(ip) 152 | else: 153 | self.v = version or 6 154 | self.dq = self._itodq(ip) 155 | else: 156 | # If string is in CIDR or netmask notation 157 | if '/' in ip: 158 | ip, mask = ip.split('/', 1) 159 | self.mask = mask 160 | self.v = version or 0 161 | self.dq = ip 162 | self.ip = self._dqtoi(ip) 163 | assert self.v != 0, 'Could not parse input' 164 | # Netmask defaults to one ip 165 | if self.mask is None: 166 | self.mask = self.v == 4 and 32 or 128 167 | # Netmask is numeric CIDR subnet 168 | elif isinstance(self.mask, (int, long)) or self.mask.isdigit(): 169 | self.mask = int(self.mask) 170 | # Netmask is in subnet notation 171 | elif isinstance(self.mask, basestring): 172 | limit = [32, 128][':' in self.mask] 173 | inverted = ~self._dqtoi(self.mask) 174 | count = 0 175 | while inverted & pow(2, count): 176 | count += 1 177 | self.mask = (limit - count) 178 | else: 179 | raise ValueError('Invalid netmask') 180 | # Validate subnet size 181 | if self.v == 6: 182 | self.dq = self._itodq(self.ip) 183 | if not 0 <= self.mask <= 128: 184 | raise ValueError('IPv6 subnet size must be between 0 and 128') 185 | elif self.v == 4: 186 | if not 0 <= self.mask <= 32: 187 | raise ValueError('IPv4 subnet size must be between 0 and 32') 188 | 189 | def bin(self): 190 | ''' 191 | Full-length binary representation of the IP address. 192 | 193 | >>> ip = IP("127.0.0.1") 194 | >>> print ip.bin() 195 | 01111111000000000000000000000001 196 | ''' 197 | return bin(self.ip).split('b')[1].rjust(self.mask, '0') 198 | 199 | def hex(self): 200 | ''' 201 | Full-length hexadecimal representation of the IP address. 202 | 203 | >>> ip = IP("127.0.0.1") 204 | >>> print ip.hex() 205 | 7f000001 206 | ''' 207 | if self.v == 4: 208 | return '%08x' % self.ip 209 | else: 210 | return '%032x' % self.ip 211 | 212 | def subnet(self): 213 | return self.mask 214 | 215 | def version(self): 216 | ''' 217 | IP version. 218 | 219 | >>> ip = IP("127.0.0.1") 220 | >>> print ip.version() 221 | 4 222 | ''' 223 | return self.v 224 | 225 | def info(self): 226 | ''' 227 | Show IANA allocation information for the current IP address. 228 | 229 | >>> ip = IP("127.0.0.1") 230 | >>> print ip.info() 231 | CLASS A 232 | ''' 233 | b = self.bin() 234 | self.v == 4 and 32 or 128 235 | for i in range(len(b), 0, -1): 236 | if b[:i] in self._range[self.v]: 237 | return self._range[self.v][b[:i]] 238 | return 'UNKNOWN' 239 | 240 | def _dqtoi(self, dq): 241 | ''' 242 | Convert dotquad or hextet to long. 243 | ''' 244 | # hex notation 245 | if dq.startswith('0x'): 246 | ip = long(dq[2:], 16) 247 | if ip > 0xffffffffffffffffffffffffffffffffL: 248 | raise ValueError('%s: IP address is bigger than 2^128' % dq) 249 | if ip <= 0xffffffff: 250 | self.v = 4 251 | else: 252 | self.v = 6 253 | return ip 254 | 255 | # IPv6 256 | if ':' in dq: 257 | # Split hextets 258 | hx = dq.split(':') 259 | if ':::' in dq: 260 | raise ValueError("%s: IPv6 address can't contain :::" % dq) 261 | # Mixed address (or 4-in-6), ::ffff:192.0.2.42 262 | if '.' in dq: 263 | return self._dqtoi(hx[-1]) 264 | if len(hx) > 8: 265 | raise ValueError('%s: IPv6 address with more than 8 hexlets' % dq) 266 | elif len(hx) < 8: 267 | # No :: in address 268 | if not '' in hx: 269 | raise ValueError('%s: IPv6 address invalid: ' 270 | 'compressed format malformed' % dq) 271 | elif not (dq.startswith('::') or dq.endswith('::')) and len([x for x in hx if x == '']) > 1: 272 | raise ValueError('%s: IPv6 address invalid: ' 273 | 'compressed format malformed' % dq) 274 | ix = hx.index('') 275 | px = len(hx[ix + 1:]) 276 | for x in xrange(ix + px + 1, 8): 277 | hx.insert(ix, '0') 278 | elif dq.endswith('::'): 279 | pass 280 | elif '' in hx: 281 | raise ValueError('%s: IPv6 address invalid: ' 282 | 'compressed format detected in full notation' % dq()) 283 | ip = '' 284 | hx = [x == '' and '0' or x for x in hx] 285 | for h in hx: 286 | if len(h) < 4: 287 | h = '%04x' % int(h, 16) 288 | if not 0 <= int(h, 16) <= 0xffff: 289 | raise ValueError('%r: IPv6 address invalid: ' 290 | 'hexlets should be between 0x0000 and 0xffff' % dq) 291 | ip += h 292 | self.v = 6 293 | return long(ip, 16) 294 | elif len(dq) == 32: 295 | # Assume full heximal notation 296 | self.v = 6 297 | return long(h, 16) 298 | 299 | # IPv4 300 | if '.' in dq: 301 | q = dq.split('.') 302 | q.reverse() 303 | if len(q) > 4: 304 | raise ValueError('%s: IPv4 address invalid: ' 305 | 'more than 4 bytes' % dq) 306 | for x in q: 307 | if not 0 <= int(x) <= 255: 308 | raise ValueError('%s: IPv4 address invalid: ' 309 | 'bytes should be between 0 and 255' % dq) 310 | while len(q) < 4: 311 | q.insert(1, '0') 312 | self.v = 4 313 | return sum(long(byte) << 8 * index for index, byte in enumerate(q)) 314 | 315 | raise ValueError('Invalid address input') 316 | 317 | def _itodq(self, n): 318 | ''' 319 | Convert long to dotquad or hextet. 320 | ''' 321 | if self.v == 4: 322 | return '.'.join(map(str, [ 323 | (n >> 24) & 0xff, 324 | (n >> 16) & 0xff, 325 | (n >> 8) & 0xff, 326 | n & 0xff, 327 | ])) 328 | else: 329 | n = '%032x' % n 330 | return ':'.join(n[4 * x:4 * x + 4] for x in xrange(0, 8)) 331 | 332 | def __str__(self): 333 | ''' 334 | Return dotquad representation of the IP. 335 | 336 | >>> ip = IP("::1") 337 | >>> print str(ip) 338 | 0000:0000:0000:0000:0000:0000:0000:0001 339 | ''' 340 | return self.dq 341 | 342 | def __int__(self): 343 | return int(self.ip) 344 | 345 | def __long__(self): 346 | return self.ip 347 | 348 | def __lt__(self, other): 349 | return long(self) < long(IP(other)) 350 | 351 | def __le__(self, other): 352 | return long(self) <= long(IP(other)) 353 | 354 | def __ge__(self, other): 355 | return long(self) >= long(IP(other)) 356 | 357 | def __gt__(self, other): 358 | return long(self) > long(IP(other)) 359 | 360 | def __eq__(self, other): 361 | return long(self) == long(IP(other)) 362 | 363 | def size(self): 364 | return 1 365 | 366 | def clone(self): 367 | ''' 368 | Return a new object with a copy of this one. 369 | 370 | >>> ip = IP('127.0.0.1') 371 | >>> ip.clone() # doctest: +ELLIPSIS 372 | 373 | ''' 374 | return IP(self) 375 | 376 | def to_ipv4(self): 377 | ''' 378 | Convert (an IPv6) IP address to an IPv4 address, if possible. Only works 379 | for IPv4-compat (::/96), IPv4-mapped (::ffff/96), and 6-to-4 (2002::/16) addresses. 380 | 381 | >>> ip = IP('2002:c000:022a::') 382 | >>> print ip.to_ipv4() 383 | 192.0.2.42 384 | ''' 385 | if self.v == 4: 386 | return self 387 | else: 388 | if self.bin().startswith('0' * 96): 389 | return IP(long(self), version=4) 390 | elif self.bin().startswith('0' * 80 + '1' * 16): 391 | return IP(long(self) & 0xffffffff, version=4) 392 | elif long(self) & 0x20020000000000000000000000000000L: 393 | return IP((long(self) - 0x20020000000000000000000000000000L) >> 80, version=4) 394 | else: 395 | return ValueError('%s: IPv6 address is not IPv4 compatible or mapped, ' 396 | 'nor an 6-to-4 IP' % self.dq) 397 | 398 | @classmethod 399 | def from_bin(cls, value): 400 | value = value.lstrip('b') 401 | if len(value) == 32: 402 | return cls(int(value, 2)) 403 | elif len(value) == 128: 404 | return cls(long(value, 2)) 405 | else: 406 | return ValueError('%r: invalid binary notation' % (value,)) 407 | 408 | @classmethod 409 | def from_hex(cls, value): 410 | if len(value) == 8: 411 | return cls(int(value, 16)) 412 | elif len(value) == 32: 413 | return cls(long(value, 16)) 414 | else: 415 | raise ValueError('%r: invalid hexadecimal notation' % (value,)) 416 | 417 | def to_ipv6(self, type='6-to-4'): 418 | ''' 419 | Convert (an IPv4) IP address to an IPv6 address. 420 | 421 | >>> ip = IP('192.0.2.42') 422 | >>> print ip.to_ipv6() 423 | 2002:c000:022a:0000:0000:0000:0000:0000 424 | 425 | >>> print ip.to_ipv6('compat') 426 | 0000:0000:0000:0000:0000:0000:c000:022a 427 | 428 | >>> print ip.to_ipv6('mapped') 429 | 0000:0000:0000:0000:0000:ffff:c000:022a 430 | ''' 431 | assert type in ['6-to-4', 'compat', 'mapped'], 'Conversion type not supported' 432 | if self.v == 4: 433 | if type == '6-to-4': 434 | return IP(0x20020000000000000000000000000000L | long(self) << 80, version=6) 435 | elif type == 'compat': 436 | return IP(long(self), version=6) 437 | elif type == 'mapped': 438 | return IP(0xffff << 32 | long(self), version=6) 439 | else: 440 | return self 441 | 442 | def to_reverse(self): 443 | ''' 444 | Convert the IP address to a PTR record in .in-addr.arpa for IPv4 and 445 | .ip6.arpa for IPv6 addresses. 446 | 447 | >>> ip = IP('192.0.2.42') 448 | >>> print ip.to_reverse() 449 | 42.2.0.192.in-addr.arpa 450 | >>> print ip.to_ipv6().to_reverse() 451 | 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.a.2.2.0.0.0.0.c.2.0.0.2.ip6.arpa 452 | ''' 453 | if self.v == 4: 454 | return '.'.join(list(self.dq.split('.')[::-1]) + ['in-addr', 'arpa']) 455 | else: 456 | return '.'.join(list(self.hex())[::-1] + ['ip6', 'arpa']) 457 | 458 | def to_tuple(self): 459 | ''' 460 | Used for comparisons. 461 | ''' 462 | return (self.dq, self.mask) 463 | 464 | 465 | class Network(IP): 466 | ''' 467 | Network slice calculations. 468 | 469 | :param ip: network address 470 | :type ip: :class:`IP` or str or long or int 471 | :param mask: netmask 472 | :type mask: int or str 473 | 474 | 475 | >>> localnet = Network('127.0.0.1/8') 476 | >>> print localnet 477 | 127.0.0.1 478 | ''' 479 | 480 | def netmask(self): 481 | ''' 482 | Network netmask derived from subnet size, as IP object. 483 | 484 | >>> localnet = Network('127.0.0.1/8') 485 | >>> print localnet.netmask() 486 | 255.0.0.0 487 | ''' 488 | return IP(self.netmask_long(), version=self.version()) 489 | 490 | def netmask_long(self): 491 | ''' 492 | Network netmask derived from subnet size, as long. 493 | 494 | >>> localnet = Network('127.0.0.1/8') 495 | >>> print localnet.netmask_long() 496 | 4278190080 497 | ''' 498 | if self.version() == 4: 499 | return (0xffffffffL >> (32 - self.mask)) << (32 - self.mask) 500 | else: 501 | return (0xffffffffffffffffffffffffffffffffL >> (128 - self.mask)) << (128 - self.mask) 502 | 503 | def network(self): 504 | ''' 505 | Network address, as IP object. 506 | 507 | >>> localnet = Network('127.128.99.3/8') 508 | >>> print localnet.network() 509 | 127.0.0.0 510 | ''' 511 | return IP(self.network_long(), version=self.version()) 512 | 513 | def network_long(self): 514 | ''' 515 | Network address, as long. 516 | 517 | >>> localnet = Network('127.128.99.3/8') 518 | >>> print localnet.network_long() 519 | 2130706432 520 | ''' 521 | return self.ip & self.netmask_long() 522 | 523 | def broadcast(self): 524 | ''' 525 | Broadcast address, as IP object. 526 | 527 | >>> localnet = Network('127.0.0.1/8') 528 | >>> print localnet.broadcast() 529 | 127.255.255.255 530 | ''' 531 | # XXX: IPv6 doesn't have a broadcast address, but it's used for other 532 | # calculations such as 533 | return IP(self.broadcast_long(), version=self.version()) 534 | 535 | def broadcast_long(self): 536 | ''' 537 | Broadcast address, as long. 538 | 539 | >>> localnet = Network('127.0.0.1/8') 540 | >>> print localnet.broadcast_long() 541 | 2147483647 542 | ''' 543 | if self.version() == 4: 544 | return self.network_long() | (0xffffffffL - self.netmask_long()) 545 | else: 546 | return self.network_long() \ 547 | | (0xffffffffffffffffffffffffffffffffL - self.netmask_long()) 548 | 549 | def host_first(self): 550 | ''' 551 | First available host in this subnet. 552 | ''' 553 | if (self.version() == 4 and self.mask > 30) or \ 554 | (self.version() == 6 and self.mask > 126): 555 | return self 556 | else: 557 | return IP(self.network_long() + 1, version=self.version()) 558 | 559 | def host_last(self): 560 | ''' 561 | Last available host in this subnet. 562 | ''' 563 | if (self.version() == 4 and self.mask == 32) or \ 564 | (self.version() == 6 and self.mask == 128): 565 | return self 566 | elif (self.version() == 4 and self.mask == 31) or \ 567 | (self.version() == 6 and self.mask == 127): 568 | return IP(long(self) + 1, version=self.version()) 569 | else: 570 | return IP(self.broadcast_long() - 1, version=self.version()) 571 | 572 | def in_network(self, other): 573 | ''' 574 | Check if the given IP address is within this network. 575 | ''' 576 | other = Network(other) 577 | return long(other) >= long(self) and long(other) < long(self) + self.size() - other.size() + 1 578 | 579 | def __contains__(self, ip): 580 | ''' 581 | Check if the given ip is part of the network. 582 | 583 | >>> '192.0.2.42' in Network('192.0.2.0/24') 584 | True 585 | >>> '192.168.2.42' in Network('192.0.2.0/24') 586 | False 587 | ''' 588 | return self.in_network(ip) 589 | 590 | def __lt__(self, other): 591 | return self.size() < IP(other).size() 592 | 593 | def __le__(self, other): 594 | return self.size() <= IP(other).size() 595 | 596 | def __gt__(self, other): 597 | return self.size() > IP(other).size() 598 | 599 | def __ge__(self, other): 600 | return self.size() >= IP(other).size() 601 | 602 | def __eq__(self, other): 603 | return self.size() == IP(other).size() 604 | 605 | def __getitem__(self, key): 606 | if isinstance(key, slice): 607 | # Work-around IPv6 subnets being huge. Slice indices don't like 608 | # long int. 609 | x = key.start or 0 610 | slice_stop = (key.stop or self.size()) - 1 611 | slice_step = key.step or 1 612 | arr = list() 613 | while x < slice_stop: 614 | arr.append(IP(long(self) + x)) 615 | x += slice_step 616 | return tuple(arr) 617 | else: 618 | return IP(long(self) + key) 619 | 620 | def __iter__(self): 621 | ''' 622 | Generate a range of usable host IP addresses within the network, as IP 623 | objects. 624 | 625 | >>> for ip in Network('192.168.114.0/30'): 626 | ... print str(ip) 627 | ... 628 | 192.168.114.0 629 | 192.168.114.1 630 | 192.168.114.2 631 | 192.168.114.3 632 | ''' 633 | 634 | curr = long(self.host_first()) 635 | stop = long(self.host_last()) 636 | while curr <= stop: 637 | yield IP(curr) 638 | curr += 1 639 | 640 | def has_key(self, ip): 641 | ''' 642 | Check if the given ip is part of the network. 643 | 644 | :param ip: the ip address 645 | :type ip: :class:`IP` or str or long or int 646 | 647 | >>> net = Network('192.0.2.0/24') 648 | >>> net.has_key('192.168.2.0') 649 | False 650 | >>> net.has_key('192.0.2.42') 651 | True 652 | ''' 653 | return self.__contains__(ip) 654 | 655 | def size(self): 656 | ''' 657 | Number of ip's within the network. 658 | 659 | >>> net = Network('192.0.2.0/24') 660 | >>> print net.size() 661 | 256 662 | ''' 663 | return 2 ** ((self.version() == 4 and 32 or 128) - self.mask) 664 | 665 | 666 | if __name__ == '__main__': 667 | tests = [ 668 | ('192.168.114.42', 23, ['192.168.0.1', '192.168.114.128', '10.0.0.1']), 669 | ('123::', 128, ['123:456::', '::1', '123::456']), 670 | ('::42', 64, ['::1', '1::']), 671 | ('2001:dead:beef:1:c01d:c01a::', 48, ['2001:dead:beef:babe::']), 672 | ('10.10.0.0', '255.255.255.0', ['10.10.0.20', '10.10.10.20']), 673 | ('2001:dead:beef:1:c01d:c01a::', 'ffff:ffff:ffff::', ['2001:dead:beef:babe::']), 674 | ('10.10.0.0/255.255.240.0', None, ['10.10.0.20', '10.10.250.0']), 675 | ] 676 | 677 | for ip, mask, test_ip in tests: 678 | net = Network(ip, mask) 679 | print '===========' 680 | print 'ip address:', net 681 | print 'to ipv6...:', net.to_ipv6() 682 | print 'ip version:', net.version() 683 | print 'ip info...:', net.info() 684 | print 'subnet....:', net.subnet() 685 | print 'num ip\'s..:', net.size() 686 | print 'integer...:', long(net) 687 | print 'hex.......:', net.hex() 688 | print 'netmask...:', net.netmask() 689 | # Not implemented in IPv6 690 | if net.version() == 4: 691 | print 'network...:', net.network() 692 | print 'broadcast.:', net.broadcast() 693 | print 'first host:', net.host_first() 694 | print 'reverse...:', net.host_first().to_reverse() 695 | print 'last host.:', net.host_last() 696 | print 'reverse...:', net.host_last().to_reverse() 697 | for ip in test_ip: 698 | print '%s in network: ' % ip, ip in net -------------------------------------------------------------------------------- /src/linux_auto_dics_multi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # IMPORTANT!!! 3 | # This script is deprecated. Please use: https://github.com/device42/nix_bsd_mac_inventory 4 | 5 | 6 | """ 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 8 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 9 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 10 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 11 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 13 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | """ 15 | 16 | ######################################################################################################################################################### 17 | # v1.2.0 of python script that uses paramiko to run remote commands using ssh and 18 | # gets system info on *nix based systems, parses it and uploads to device42 appliance using APIs 19 | # tested on Redhat, Fedora and Ubuntu installations. Cent OS 5.x OS detection issue discussed below. 20 | # paramiko has a LGPL license that is included with the repository. ipcalc has a BSD LICENSE mentioned on top of the ipcalc.py file. 21 | # OS detection doesn't work correctly for CentOS 5.x, These show as redhat 5.x. Set GET_OS_DETAILS to False for CentOS 5.x based systems. 22 | # LINES 39-58 to match your environment and requirements. If used in conjuction with other auto-discovery methods, you can configure which info to ignore 23 | # Network slash notations added in v1.1.0 24 | # Note: 25 | # By default, root has permissions to run dmidecode. If you are running auto-discover as a non-root user, you would need following in your /etc/sudoers file. 26 | # %your-group-here ALL = (ALL) NOPASSWD:/usr/sbin/dmidecode 27 | # If this permission is missing, auto-discovery client would not be able to find hardware, manufacturer, serial #, and so on. 28 | # You might also have to comment out Default Requiretty in /etc/sudoers file. 29 | ######################################################################################################################################################### 30 | 31 | 32 | import sys 33 | import re 34 | import paramiko 35 | import math 36 | import urllib2, urllib 37 | from base64 import b64encode 38 | import simplejson as json 39 | 40 | ssh = paramiko.SSHClient() 41 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 42 | 43 | D42_API_URL = 'https://D42_IP_or_FQDN' #make sure to NOT to end in / 44 | D42_USERNAME = 'D42USER' 45 | D42_PASSWORD = 'D42PASS' 46 | USE_IP_RANGE = True 47 | IP_RANGE = ['192.168.11.10', '192.168.11.202'] #Start and End IP. There is no validation in the script. Please make sure these are in same subnet. Valid if USE_IP_RANGE = True 48 | NETWORKS = ['10.10.0.0/23', '10.11.8.0/23',] #End with , if a single network. always use / notation for the network. Only valid if USE_IP_RANGE = False 49 | LINUX_USER = 'USER' 50 | LINUX_PASSWORD = 'PASS' #Change USE_KEY_FILE to False if using password. password for linux servers. not required if using key file. 51 | USE_KEY_FILE = False #change this to true, if not using password. 52 | KEY_FILE = '/path/.ssh/id_rsa.pub' #key file name (with full path if not in same directory as the script) 53 | PORT = 22 #ssh port to use 54 | TIMEOUT = 5 #timeout in seconds for the ssh session 55 | GET_SERIAL_INFO = True 56 | GET_HARDWARE_INFO = True 57 | GET_OS_DETAILS = True 58 | GET_CPU_INFO = True 59 | GET_MEMORY_INFO = True 60 | ignoreDomain = True 61 | uploadipv6 = True 62 | DEBUG = False 63 | 64 | 65 | 66 | def to_ascii(s): 67 | try: return s.encode('ascii','ignore') 68 | except: return None 69 | 70 | def closest_memory_assumption(v): 71 | if v < 512: v = 128 * math.ceil(v / 128.0) 72 | elif v < 1024: v = 256 * math.ceil(v / 256.0) 73 | elif v < 4096: v = 512 * math.ceil(v / 512.0) 74 | elif v < 8192: v = 1024 * math.ceil(v / 1024.0) 75 | else: v = 2048 * math.ceil(v / 2048.0) 76 | return int(v) 77 | 78 | def enumerate_ips(): 79 | iplist = [] 80 | start = list(map(int, IP_RANGE[0].split("."))) 81 | end = list(map(int, IP_RANGE[1].split("."))) 82 | temp = start 83 | iplist.append(IP_RANGE[0]) 84 | while temp != end: 85 | start[3] += 1 86 | for i in (3, 2, 1): 87 | if temp[i] == 256: 88 | temp[i] = 0 89 | temp[i-1] += 1 90 | ipadd=(".".join(map(str, temp))) 91 | iplist.append(ipadd) 92 | return iplist 93 | 94 | def post(params, what): 95 | if what == 'device': THE_URL = D42_API_URL + '/api/device/' 96 | elif what == 'ip': THE_URL = D42_API_URL + '/api/ip/' 97 | elif what == 'mac': THE_URL = D42_API_URL + '/api/1.0/macs/' 98 | data= urllib.urlencode(params) 99 | headers = { 100 | 'Authorization' : 'Basic '+ b64encode(D42_USERNAME + ':' + D42_PASSWORD), 101 | 'Content-Type' : 'application/x-www-form-urlencoded' 102 | } 103 | req = urllib2.Request(THE_URL, data, headers) 104 | if DEBUG: print '---REQUEST---',req.get_full_url() 105 | if DEBUG: print req.headers 106 | if DEBUG: print req.data 107 | try: 108 | r = urllib2.urlopen(req) 109 | if r.getcode() == 200: 110 | obj = r.read() 111 | msg = json.loads(obj) 112 | return True, msg 113 | else: 114 | return False, r.getcode() 115 | except urllib2.HTTPError, e: 116 | error_response = e.read() 117 | if DEBUG: print e.code, error_response 118 | return False, error_response 119 | except Exception,e: 120 | return False, str(e) 121 | 122 | def grab_and_post_inventory_data(machine_name): 123 | try: 124 | if not USE_KEY_FILE: ssh.connect(str(machine_name), port=PORT, username=LINUX_USER, password=LINUX_PASSWORD, timeout=TIMEOUT) 125 | else: ssh.connect(str(machine_name), port=PORT, username=LINUX_USER, key_filename=KEY_FILE, timeout=TIMEOUT) 126 | except paramiko.AuthenticationException: 127 | print str(machine_name) + ': authentication failed' 128 | return None 129 | except Exception as err: 130 | print str(machine_name) + ': ' + str(err) 131 | return None 132 | devargs = {} 133 | 134 | print '[+] Connecting to: %s' % machine_name 135 | stdin, stdout, stderr = ssh.exec_command("/bin/hostname") 136 | data_err = stderr.readlines() 137 | data_out = stdout.readlines() 138 | device_name = None 139 | if not data_err: 140 | if ignoreDomain: device_name = to_ascii(data_out[0].rstrip()).split('.')[0] 141 | else: device_name = to_ascii(data_out[0].rstrip()) 142 | if device_name != '': 143 | devargs.update({'name': device_name}) 144 | else: 145 | if DEBUG: 146 | print data_err 147 | 148 | if not device_name: 149 | return None 150 | 151 | if device_name != '': 152 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -s system-uuid") 153 | data_err = stderr.readlines() 154 | data_out = stdout.readlines() 155 | if not data_err: 156 | if len(data_out) > 0: 157 | uuid = data_out[0].rstrip() 158 | if uuid and uuid != '': devargs.update({'uuid': uuid}) 159 | else: 160 | if DEBUG: 161 | print data_err 162 | 163 | 164 | if GET_SERIAL_INFO: 165 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -s system-serial-number") 166 | data_err = stderr.readlines() 167 | data_out = stdout.readlines() 168 | if not data_err: 169 | if len(data_out) > 0: 170 | serial_no = data_out[0].rstrip() 171 | if serial_no and serial_no != '': devargs.update({'serial_no': serial_no}) 172 | else: 173 | if DEBUG: 174 | print data_err 175 | 176 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -s system-manufacturer") 177 | data_err = stderr.readlines() 178 | data_out = stdout.readlines() 179 | if not data_err: 180 | if len(data_out) > 0: 181 | manufacturer = data_out[0].rstrip() 182 | if manufacturer and manufacturer != '': 183 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', ' Xen']: 184 | if mftr == to_ascii(manufacturer).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip(): 185 | manufacturer = 'virtual' 186 | devargs.update({ 'type' : 'virtual', }) 187 | break 188 | if manufacturer != 'virtual' and GET_HARDWARE_INFO: 189 | devargs.update({'manufacturer': to_ascii(manufacturer).replace("# SMBIOS implementations newer than version 2.6 are not\n# fully supported by this version of dmidecode.\n", "").strip()}) 190 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -s system-product- name") 191 | data_err = stderr.readlines() 192 | data_out = stdout.readlines() 193 | if not data_err: 194 | hardware = data_out[0].rstrip() 195 | if hardware and hardware != '': devargs.update({'hardware': hardware}) 196 | else: 197 | if DEBUG: 198 | print data_err 199 | else: 200 | if DEBUG: 201 | print data_err 202 | 203 | 204 | if GET_OS_DETAILS: 205 | stdin, stdout, stderr = ssh.exec_command("/usr/bin/python -m platform") 206 | data_err = stderr.readlines() 207 | data_out = stdout.readlines() 208 | if not data_err: 209 | if len(data_out) > 0: 210 | release = data_out[0].rstrip() 211 | if release and release != '': 212 | devargs.update({ 213 | 'os': release.split('-with-')[1].split('-')[0], 214 | 'osver': release.split('-with-')[1].split('-')[1], 215 | }) 216 | else: 217 | if DEBUG: 218 | print data_err 219 | 220 | 221 | if GET_MEMORY_INFO: 222 | stdin, stdout, stderr = ssh.exec_command("grep MemTotal /proc/meminfo") 223 | data_err = stderr.readlines() 224 | data_out = stdout.readlines() 225 | if not data_err: 226 | memory_raw = data_out[0].replace(' ', '').replace('MemTotal:','').replace('kB','') 227 | if memory_raw and memory_raw != '': 228 | memory = closest_memory_assumption(int(memory_raw)/1024) 229 | devargs.update({'memory': memory}) 230 | else: 231 | if DEBUG: 232 | print data_err 233 | 234 | if GET_CPU_INFO: 235 | cpucount = 0 236 | cpuspeed = '' 237 | corecount = 0 238 | 239 | stdin, stdout, stderr = ssh.exec_command("cat /proc/cpuinfo | grep processor | wc -l ") 240 | data_err = stderr.readlines() 241 | data_out = stdout.readlines() 242 | if not data_err: 243 | cpucount = int(data_out[0].strip()) 244 | else: 245 | if DEBUG: 246 | print data_err 247 | 248 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -s processor-frequency") 249 | data_err = stderr.readlines() 250 | data_out = stdout.readlines() 251 | if not data_err: 252 | if len(data_out) > 0: 253 | cpuspeedinfo = data_out 254 | for item in cpuspeedinfo: 255 | if 'MHz' in item: 256 | cpuspeed = item.split(' ')[0] 257 | break 258 | if cpuspeed != '': devargs.update({'cpupower': cpuspeed,}) 259 | else: 260 | if DEBUG: 261 | print data_err 262 | 263 | stdin, stdout, stderr = ssh.exec_command("sudo dmidecode -t processor") 264 | data_err = stderr.readlines() 265 | data_out = stdout.readlines() 266 | if not data_err: 267 | if len(data_out) > 0: 268 | coreinfo = data_out 269 | for item in coreinfo: 270 | if 'Core Count' in item: 271 | corecount = int(item.replace('Core Count: ', '').strip()) 272 | break 273 | if corecount == 0: 274 | corecount = 1 275 | if cpucount > 0: 276 | cpucount /= corecount 277 | devargs.update({'cpucount': cpucount}) 278 | devargs.update({'cpucore': corecount}) 279 | else: 280 | if DEBUG: 281 | print data_err 282 | 283 | ADDED, msg = post(devargs, 'device') 284 | 285 | if ADDED: 286 | print str(machine_name) + ': ' + msg['msg'][0] 287 | device_name_in_d42 = msg['msg'][2] 288 | stdin, stdout, stderr = ssh.exec_command("/sbin/ifconfig -a") #TODO add just macs without IPs 289 | data_err = stderr.readlines() 290 | data_out = stdout.readlines() 291 | if not data_err: 292 | ipinfo = stdout.readlines() 293 | 294 | # ======= MAC only=========# 295 | for rec in ipinfo: 296 | if 'hwaddr' in rec.lower(): 297 | mac = re.search(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', rec, re.I).group() 298 | print 'MAC: %s' % mac 299 | print rec.split("\n")[0].split()[0] 300 | mac_address = { 301 | 'macaddress' : mac, 302 | 'port_name': rec.split("\n")[0].split()[0], 303 | 'device' : device_name_in_d42, 304 | 'override': 'smart' 305 | } 306 | ADDED, msg_mac = post(mac_address, 'mac') 307 | if ADDED: 308 | print mac + ': ' + str(msg_mac) 309 | else: 310 | print mac + ': failed with message = ' + str(msg_mac) 311 | print '\n\n' 312 | # ======= / MAC only =========# 313 | 314 | for i, item in enumerate(ipinfo): 315 | if 'Ethernet' in item: 316 | if 'inet addr' in ipinfo[i+1]: 317 | ipv4_address = ipinfo[i+1].split()[1].replace('addr:', '') 318 | ip = { 319 | 'ipaddress': ipv4_address, 320 | 'tag': item.split("\n")[0].split()[0], 321 | 'macaddress' : item.split("\n")[0].split()[4], 322 | 'device' : device_name_in_d42, 323 | } 324 | ADDED, msg_ip = post(ip, 'ip') 325 | if ADDED: 326 | print ipv4_address + ': ' + str(msg_ip) 327 | else: 328 | print ipv4_address + ': failed with message = ' + str(msg_ip) 329 | if uploadipv6 and ('inet6 addr' in ipinfo[i+1] or 'inet6 addr' in ipinfo [i+2]): 330 | if 'inet6 addr' in ipinfo[i+1]: ipv6_address = ipinfo[i+1].split()[2 ].split('/')[0] 331 | else: ipv6_address = ipinfo[i+2].split()[2].split('/')[0] 332 | ip = { 333 | 'ipaddress': ipv6_address, 334 | 'tag': item.split("\n")[0].split()[0], 335 | 'macaddress' : item.split("\n")[0].split()[4], 336 | 'device' : device_name_in_d42, 337 | } 338 | ADDED, msg_ip = post(ip, 'ip') 339 | if ADDED: 340 | print ipv6_address + ' : ' + str(msg_ip) 341 | else: 342 | print ipv6_address + ': failed with message = ' + str(msg_ip) 343 | else: 344 | if DEBUG: 345 | print data_err 346 | else: 347 | print str(machine_name) + ': failed with message: ' + str(msg) 348 | else: 349 | print str(machine_name) + ': failed with message: ' + "Can't determine hostname (non-unix system?)" 350 | ssh.close() 351 | return devargs 352 | 353 | if USE_IP_RANGE: 354 | iplist = enumerate_ips() 355 | for ip in iplist: 356 | grab_and_post_inventory_data(ip) 357 | else: 358 | try: import ipcalc 359 | except: 360 | print 'Unable to import ipcalc.' 361 | print 'Please run: pip install git+git://github.com/tehmaze/ipcalc.git@master' 362 | print 'or drop ipcalc.py in the same path as this script' 363 | print 'or make sure it is installed and in the system path.' 364 | print 'https://github.com/tehmaze/ipcalc/blob/master/ipcalc.py' 365 | sys.exit(0) 366 | for network in NETWORKS: 367 | for ip in ipcalc.Network(network): 368 | grab_and_post_inventory_data(ip) 369 | 370 | -------------------------------------------------------------------------------- /src/sample-script-facter-facts-to-d42.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 4 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 5 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 6 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 7 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 8 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 9 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | """ 11 | 12 | ################################################################ 13 | # The script goes through the yaml fact files created by facter 14 | # and populates device42 database with following info: 15 | #device name, manufacturer, hardware model, serial #, os info, memory, cpucount and cpucores info 16 | # IP address, interface name and mac address. 17 | # Script tested with python 2.4 18 | ################################################################ 19 | 20 | 21 | import types 22 | import os.path 23 | import urllib 24 | import urllib2 25 | import traceback 26 | import base64 27 | import sys 28 | import glob 29 | import math 30 | #Device42 URL and credentials 31 | BASE_URL='https://your-device42-url' #Please make sure there is no / in the end 32 | 33 | API_DEVICE_URL=BASE_URL+'/api/device/' 34 | API_IP_URL =BASE_URL+'/api/ip/' 35 | 36 | USER ='your-device42-user-name' 37 | PASSWORD='your-device42-password-here' 38 | DRY_RUN = False 39 | 40 | # puppet config dir 41 | puppetdir="/var/opt/lib/pe-puppet/yaml/node/" #Change to reflect node directory with yaml fact files. 42 | 43 | 44 | def post(url, params): 45 | """http post with basic-auth params is dict like object""" 46 | try: 47 | data= urllib.urlencode(params) # convert to ascii chars 48 | headers = { 49 | 'Authorization' : 'Basic '+ base64.b64encode(USER + ':' + PASSWORD), 50 | 'Content-Type' : 'application/x-www-form-urlencoded' 51 | } 52 | 53 | if DRY_RUN: 54 | print url, headers, data 55 | else: 56 | req = urllib2.Request(url, data, headers) 57 | print '---REQUEST---',req.get_full_url() 58 | print req.headers 59 | print req.data 60 | 61 | response = urllib2.urlopen(req) 62 | print '---RESPONSE---' 63 | print response.read() 64 | 65 | except Exception, Err: 66 | print '-----EXCEPTION OCCURED-----' 67 | print str(Err) 68 | def to_ascii(s): 69 | """remove non-ascii characters""" 70 | if type(s) == types.StringType: 71 | return s.encode('ascii','ignore') 72 | else: 73 | return str(s) 74 | def closest_memory_assumption(v): 75 | if v < 512: v = 128 * math.ceil(v / 128.0) 76 | elif v < 1024: v = 256 * math.ceil(v / 256.0) 77 | elif v < 4096: v = 512 * math.ceil(v / 512.0) 78 | elif v < 8192: v = 1024 * math.ceil(v / 1024.0) 79 | else: v = 2048 * math.ceil(v / 2048.0) 80 | return int(v) 81 | for infile in glob.glob( os.path.join(puppetdir, '*yaml') ): 82 | d = {} 83 | 84 | f = open(infile) 85 | print "---Going through fact file: %s" % infile 86 | for line in f: 87 | if "--" not in line: 88 | 89 | line = line.strip().replace('"','') 90 | try: 91 | key, val = line.split(':',1) 92 | d[key] = val.strip() 93 | except: pass 94 | 95 | f.close() 96 | device_name = to_ascii(d['clientcert']) #using clientcert as the nodename here, you can change it to your liking. 97 | os = to_ascii(d.get('operatingsystem', None)) 98 | osver = to_ascii(d.get('operatingsystemrelease', None)) 99 | device = { 100 | 'name' : device_name,} 101 | 102 | if os is not None: device.update({'os' : os,}) 103 | if osver is not None: device.update({'osverno' :osver,}) 104 | manufacturer = to_ascii(d.get('manufacturer', None)).strip() 105 | if manufacturer is not None: 106 | for mftr in ['VMware, Inc.', 'Bochs', 'KVM', 'QEMU', 'Microsoft Corporation', 'Xen']: 107 | if mftr == manufacturer: 108 | manufacturer = 'virtual' 109 | device.update({ 'manufacturer' : 'vmware', }) 110 | break 111 | if manufacturer != 'virtual': 112 | hw = to_ascii(d.get('productname', None)) 113 | sn = to_ascii(d.get('serialnumber', None)) 114 | 115 | if hw is not None: device.update({ 116 | 'manufacturer' : manufacturer, 117 | 'hardware' : hw, 118 | }) 119 | if sn is not None: device.update({ 120 | 'serial_no' : sn, 121 | }) 122 | mem_b = d.get('memorysize',None).split(' ')[1] 123 | if mem_b is not None: 124 | if mem_b == 'MB': 125 | memory = closest_memory_assumption(int(float(d['memorysize'].split(' ')[0]))) 126 | else: memory = closest_memory_assumption(int(float(d['memorysize'].split(' ')[0])*1024)) 127 | device.update({'memory': memory,}) 128 | cpucount = int(d.get('physicalprocessorcount', None)) 129 | if cpucount is not None: 130 | if cpucount == 0: cpucount = 1 131 | cpucore = int(d.get('processorcount', None)) 132 | device.update({ 133 | 'cpucount': cpucount, 134 | 'cpucore': cpucore, 135 | }) 136 | 137 | post(API_DEVICE_URL, device) 138 | interfaces = d.get('interfaces',None).split(',') 139 | if interfaces is not None: 140 | for interface in interfaces: 141 | if not 'loopback' in interface.lower(): 142 | ipkey = 'ipaddress'+'_'+interface.replace(' ','').lower() 143 | mackey = 'macaddress'+'_'+interface.replace(' ','').lower() 144 | try: macaddress = d[mackey] 145 | except: macaddress = d.get('macaddress', None) 146 | ip = { 147 | 'ipaddress' : d.get(ipkey, None), 148 | 149 | 'device' : device_name, 150 | 'tag': interface.replace('_', ' ') 151 | } 152 | if macaddress is not None: ip.update({'macaddress' : macaddress,}) 153 | if ip.get('ipaddress') is not None and ip.get('ipaddress') != '127.0.0.1': post(API_IP_URL, ip) 154 | 155 | -------------------------------------------------------------------------------- /src/winservice.py: -------------------------------------------------------------------------------- 1 | """ 2 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 3 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 4 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 5 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 6 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 7 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 8 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | """ 10 | 11 | ############################################## 12 | # queries active directory for each computer 13 | # and finds the running services and adds as application components to Device42 via REST APIs 14 | # works best with Device42 v5.6.0 and above (to automatically add the service accounts as components) 15 | # Requires: 16 | # powershell 17 | # ironpython 18 | # .net 4 19 | # 20 | # to run: 21 | # ipy.exe ad-sample.py 22 | # v1.0, Created: 02-08-2014 23 | ############################################## 24 | 25 | import types 26 | import os.path 27 | import urllib 28 | import urllib2 29 | import traceback 30 | import base64 31 | import System 32 | import clr 33 | import math 34 | clr.AddReference("System.DirectoryServices") 35 | clr.AddReference('System.Management.Automation') 36 | 37 | from System.Management.Automation import RunspaceInvoke 38 | # +--------------------------------------------------------------------------- 39 | 40 | # create a runspace to run shell commands from 41 | RUNSPACE = RunspaceInvoke() 42 | 43 | D42_URL='https://your-url-here' #make sure to NOT to end in / 44 | D42_USER ='put-your-user-name-here' 45 | D42_PASSWORD='put-your-password-here' 46 | 47 | PATH_NAME_STRINGS_TO_IGNORE = ['system32', 'vmware', ] #Any service running under a path containing this string will be ignored. 48 | ADD_SERVICE_ACCOUNT_AS_DEPENDENCY = True #Change to False if you don't want to record dependency on service account 49 | SERVICE_ACCOUNT_PREFIX = 'ServiceAccount_' #Prefix to add to service accounts to see the impact - change to '' if no prefix is required 50 | IGNORE_ALL_SERVICES_RUNNING_AS_LOCALSYSTEM = False #If you just want to get services running under service accounts - set this to True 51 | 52 | DRY_RUN = False #Set to True to NOT post to D42 and just print the parameters that will be sent 53 | DEBUG = False 54 | 55 | def post(params): 56 | """http post with basic-auth params is dict like object""" 57 | try: 58 | data= urllib.urlencode(params) # convert to ascii chars 59 | headers = { 60 | 'Authorization' : 'Basic '+ base64.b64encode(D42_USER + ':' + D42_PASSWORD), 61 | 'Content-Type' : 'application/x-www-form-urlencoded' 62 | } 63 | 64 | if DRY_RUN: 65 | print params 66 | else: 67 | req = urllib2.Request(D42_URL, data, headers) 68 | 69 | if DEBUG: print '---REQUEST---',req.get_full_url() 70 | if DEBUG: print req.headers 71 | if DEBUG: print req.data 72 | 73 | reponse = urllib2.urlopen(req) 74 | 75 | if DEBUG: print '---RESPONSE---' 76 | if DEBUG: print reponse.getcode() 77 | if DEBUG: print reponse.info() 78 | print reponse.read() 79 | except urllib2.HTTPError as err: 80 | print '---RESPONSE---' 81 | if DEBUG: print err.getcode() 82 | if DEBUG: print err.info() 83 | if DEBUG: print err.read() 84 | except urllib2.URLError as err: 85 | print '---RESPONSE---' 86 | print err 87 | 88 | def get_computers(): 89 | """Enumerates ALL computer objects in AD""" 90 | searcher = System.DirectoryServices.DirectorySearcher() 91 | searcher.SearchRoot = System.DirectoryServices.DirectoryEntry() 92 | searcher.Filter = "(objectCategory=computer)" 93 | searcher.PropertiesToLoad.Add("name") 94 | return sorted([a for item in searcher.FindAll() for a in item.Properties['name']]) 95 | 96 | def get_servers(): 97 | """Enumerates ALL Servers objects in AD""" 98 | searcher = System.DirectoryServices.DirectorySearcher() 99 | searcher.SearchRoot = System.DirectoryServices.DirectoryEntry() 100 | searcher.Filter = "(&(objectCategory=computer)(OperatingSystem=Windows*Server*))" 101 | searcher.PropertiesToLoad.Add("name") 102 | return sorted([a for item in searcher.FindAll() for a in item.Properties['name']]) 103 | 104 | def get_fromfile(): 105 | """Enumerates Computer Names in a text file Create a text file and enter 106 | the names of each computer. One computer name per line. Supply the path 107 | to the text file when prompted. 108 | """ 109 | while True: 110 | filename = raw_input('Enter the path for the text file: ') 111 | if filename: 112 | if not os.path.exists(filename): 113 | print "file not exists or insufficient permissions '%s'" % filename 114 | elif not os.path.isfile(filename): 115 | print "not a file, may be a dir '%s'" % filename 116 | else: 117 | f = open(filename) 118 | try: computers = [line.strip() for line in f] 119 | finally: f.close() 120 | return sorted(computers) 121 | 122 | def get_frommanualentry(): 123 | """'SingleEntry' - Enumerates Computer from user input""" 124 | while True: 125 | c = raw_input('Enter Computer Name or IP: ') 126 | if c: return [c] 127 | 128 | def wmi(query): 129 | """create list of dict from result of wmi query""" 130 | return [dict([(prop.Name, prop.Value) for prop in psobj.Properties]) 131 | for psobj in RUNSPACE.Invoke(query)] 132 | 133 | def to_ascii(s): 134 | """remove non-ascii characters""" 135 | if type(s) == types.StringType: 136 | return s.encode('ascii','ignore') 137 | else: 138 | return str(s) 139 | 140 | def main(): 141 | banner="""\ 142 | 143 | +----------------------------------------------------+ 144 | | Domain Admin rights are required to enumerate information | 145 | +----------------------------------------------------+ 146 | """ 147 | print banner 148 | 149 | menu="""\ 150 | Which computer resources would you like to run auto-discovery on? 151 | [1] All Domain Computers 152 | [2] All Domain Servers 153 | [3] Computer names from a File 154 | [4] Choose a Computer manually 155 | """ 156 | while True: 157 | resp = raw_input(menu) 158 | if resp == '1': computers = get_computers(); break 159 | elif resp == '2': computers = get_servers(); break 160 | elif resp == '3': computers = get_fromfile(); break 161 | elif resp == '4': computers = get_frommanualentry(); break 162 | 163 | if not computers: 164 | print "ERROR: No computer found" 165 | else: 166 | for c in computers: 167 | try: 168 | services = wmi("Get-WmiObject win32_service -Comp %s" % c) 169 | for service in services: 170 | #eprint service 171 | s = 0 172 | if service.get('State') == 'Running': 173 | if IGNORE_ALL_SERVICES_RUNNING_AS_LOCALSYSTEM and service.get('StartName').lower() == 'localsystem': 174 | continue 175 | else: 176 | for partial_path_name in PATH_NAME_STRINGS_TO_IGNORE: 177 | if partial_path_name in service.get('PathName').lower(): 178 | s = 1 179 | break 180 | if s == 0: 181 | args = {'name': service.get('Name')+' ('+service.get('SystemName')+' )', 'device':service.get('SystemName') } 182 | if ADD_SERVICE_ACCOUNT_AS_DEPENDENCY and service.get('StartName').lower() != 'localsystem' : 183 | args.update({'depends_on': SERVICE_ACCOUNT_PREFIX + service.get('StartName')}) 184 | try: post(args) 185 | except Exception, err: print 'Post to D42 failed with error', str(err) 186 | except Exception, err: 187 | print 'failed for machine', c, str(err) 188 | 189 | if __name__=="__main__": 190 | main() --------------------------------------------------------------------------------