Compare commits
646 Commits
threading_
...
stable-2.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e8aa2499e | |||
| 89424880ff | |||
| b20520f500 | |||
| fde8c9b4ef | |||
| 02f3eed0e9 | |||
| aea7abcb15 | |||
| 0db22a8b29 | |||
| 52f6b03a11 | |||
| fca0cccebf | |||
| c0d3915bd2 | |||
| 6020c0d4f6 | |||
| bc9b44d907 | |||
| fb14b36f7f | |||
| 857f959135 | |||
| 5bd16d438d | |||
| deaa9a21f9 | |||
| 6233daaa44 | |||
| f0cae29d57 | |||
| 2c116617de | |||
| 204d0f7bea | |||
| fcba7a086a | |||
| a787488990 | |||
| 0255ab2e27 | |||
| 797d999513 | |||
| 3a963a68c2 | |||
| 6e1f29cde2 | |||
| 30b0103ceb | |||
| 218103d540 | |||
| f0f2031eaf | |||
| b123fa4d6f | |||
| 7256dd17ae | |||
| 54cfb022c2 | |||
| 012998c5f7 | |||
| 6eb485c136 | |||
| 49f3dda7f6 | |||
| b77de1bd5d | |||
| 2fb4f547a9 | |||
| eaa2fcc73f | |||
| 4c6612eeba | |||
| c4085eb07c | |||
| b27e86ac07 | |||
| 9e116c1b65 | |||
| 4cddc2e7c9 | |||
| 03a0ea682b | |||
| 152c895b1f | |||
| 60d9513efb | |||
| 4eea3e91ad | |||
| a9e8f38342 | |||
| fd37f27d2b | |||
| 4d5b9ae803 | |||
| 43e0a0369b | |||
| 82754c7a31 | |||
| a606321975 | |||
| f1f8d0319f | |||
| 4f5bf2e08f | |||
| b4004e5046 | |||
| 48d821e611 | |||
| 4a58187103 | |||
| e8e2c5a9f7 | |||
| 01cbdb9878 | |||
| 4f049ab74e | |||
| a859c8bba5 | |||
| 48dd3be986 | |||
| 4114a4cfc5 | |||
| ba4f0e9228 | |||
| b8e02b0dc1 | |||
| 09d38049eb | |||
| c22f8a4ae4 | |||
| 4367a45668 | |||
| afa71ace67 | |||
| 4bb52d52b9 | |||
| 5fbe1e2798 | |||
| a4edac599a | |||
| d31ba7240f | |||
| 2c2ca9b34e | |||
| 54ad5b5f49 | |||
| aa6f3f91ba | |||
| 7787ed26bf | |||
| 64db935b25 | |||
| 46934c195c | |||
| 0ba2fb1b47 | |||
| 20e47b6132 | |||
| 9dc41ae578 | |||
| 368130f131 | |||
| 37523edaf7 | |||
| 578255dd4b | |||
| c4984a4c4c | |||
| 11346284b1 | |||
| 051bcb2c6f | |||
| 8bb12fba3d | |||
| 4b7e4cfa2b | |||
| 8db83172d3 | |||
| c3917f395a | |||
| 0777565a29 | |||
| 3adb5a62e0 | |||
| f5af666164 | |||
| abfada309c | |||
| fe8d1fbf8d | |||
| 275676f2cf | |||
| d60b61ed04 | |||
| 9e37d827d4 | |||
| 75facf5324 | |||
| bb4283edc4 | |||
| 1e3231c967 | |||
| a0c3182f2e | |||
| b544df3425 | |||
| 873f023b13 | |||
| ee116637d1 | |||
| 457f0bdc74 | |||
| 78d153d5a0 | |||
| 99fcf6d824 | |||
| 8348890d7f | |||
| 5b6b402755 | |||
| e471fb3a94 | |||
| 26e6b0c639 | |||
| caaebec7f0 | |||
| e024cc8470 | |||
| d6d795cef0 | |||
| 33c3a3459a | |||
| d4d6b17228 | |||
| d36a651588 | |||
| 278d450329 | |||
| 6b8bd1ec09 | |||
| 95dbb735f1 | |||
| a92e08c39e | |||
| 33e0e5d4fe | |||
| 566299dd31 | |||
| 62106d5dcf | |||
| da332138dd | |||
| cb1bd058ad | |||
| 1244624d7c | |||
| b5c51c4c56 | |||
| 53a53ebf73 | |||
| 5d6da688ed | |||
| 3aca12b451 | |||
| 49a27da0b2 | |||
| 5c97a27d20 | |||
| 2d9cbdc107 | |||
| 60b3c2ad9c | |||
| 8e949c616e | |||
| 95ac4c4a96 | |||
| 4251db2345 | |||
| 927548e2f6 | |||
| ad61c12b06 | |||
| 80ae8e9b29 | |||
| fdc8a75218 | |||
| 81278323f3 | |||
| 896becab34 | |||
| db3ae98233 | |||
| c55b078d92 | |||
| d038997d5c | |||
| 3d0462b456 | |||
| 30f813d716 | |||
| 994ed2c443 | |||
| 2b40463a48 | |||
| 743b396e5e | |||
| 6e34fd16d5 | |||
| f00d44e0b4 | |||
| ac8598f1db | |||
| 584af4e9f8 | |||
| 392d268809 | |||
| a8afa6b96c | |||
| c7052b42c7 | |||
| 611048c6ba | |||
| 435cdcf84a | |||
| c869423e0c | |||
| 96dd226235 | |||
| eff04e9fa3 | |||
| 3d446b2fab | |||
| 987ad8993c | |||
| 76b5b35458 | |||
| 360703234e | |||
| 25c95a96a8 | |||
| 46846b1973 | |||
| d0a1679113 | |||
| 504558eab3 | |||
| 6698dc00e3 | |||
| e579afc432 | |||
| 96296e8c71 | |||
| a53cc97f19 | |||
| c276466262 | |||
| 532b4338f4 | |||
| 7a6c5dd1ab | |||
| 8f8d4d103f | |||
| 5b39de5013 | |||
| 9d435fd47b | |||
| 1628a25d8d | |||
| 0a7bad68dc | |||
| 2385e2be27 | |||
| 91f70a4462 | |||
| 28fa228d93 | |||
| 795e75433e | |||
| ddc124297a | |||
| 9a05efcab9 | |||
| 0e0400b12e | |||
| 8eba034942 | |||
| 13901eeb70 | |||
| 668266552e | |||
| 065f74f0ee | |||
| b66d9cc14e | |||
| a1c15a9277 | |||
| 316ef8a069 | |||
| a3817721fc | |||
| 4490cc9e9e | |||
| 32b4342209 | |||
| 85e9a07117 | |||
| 23020bf9d9 | |||
| 5432414c9d | |||
| 022ba4fbea | |||
| 71444d0c37 | |||
| 154b0d2210 | |||
| 4deb45eff6 | |||
| b223c7e0a2 | |||
| ffadbf680d | |||
| 29ac96e09e | |||
| 7f5a0715bf | |||
| 8ced21101f | |||
| 69175a5077 | |||
| 333d177325 | |||
| 0c8459b01b | |||
| fa65417ba0 | |||
| 144d0b6f9e | |||
| 8ad00c0128 | |||
| f9f83c5826 | |||
| e947321831 | |||
| 9d94a309ca | |||
| 1ad1ad6ace | |||
| 94660f792a | |||
| b1e0f78a07 | |||
| 888096ec4c | |||
| 0c31f31385 | |||
| e36f568194 | |||
| 6ad5a31437 | |||
| 269550993d | |||
| e943505ab2 | |||
| 7424e1c417 | |||
| 6cca156fde | |||
| 940a78c5ba | |||
| 1731962155 | |||
| 44b3d67ef3 | |||
| ba453452f0 | |||
| 4b49b96ca1 | |||
| 7c38f7cdfa | |||
| eeaa676577 | |||
| 5512c94017 | |||
| cac3cfa9eb | |||
| bf34bdb32c | |||
| 3a25aca272 | |||
| 73aa4c930d | |||
| ec26494167 | |||
| 7fbd7fc4f8 | |||
| e998b7e40f | |||
| 2bfdb77ed4 | |||
| 8813b4f03d | |||
| e1dbb02b66 | |||
| a40b1c455d | |||
| b5c2f2dc71 | |||
| dddcf0f6ad | |||
| 8c2e9257d4 | |||
| ff578cc1b3 | |||
| 419d21c509 | |||
| 5605091dc1 | |||
| 6cb0666d0a | |||
| 08f3b83812 | |||
| ae5c9f93b7 | |||
| 19cf1b2281 | |||
| 68d21e6729 | |||
| 09635b8460 | |||
| ebf7c6c37b | |||
| ccd170cbdd | |||
| be0b86ce81 | |||
| 603d52f8fe | |||
| a1ceb1c404 | |||
| b6e4266aa5 | |||
| 9aa1a2b871 | |||
| c5971f018f | |||
| 8455251bec | |||
| e4fae653da | |||
| ba6276a4b1 | |||
| ef5e9806dd | |||
| 1b00e43d2a | |||
| 9954077a0a | |||
| c37aca62ef | |||
| 5ebaaf26bb | |||
| dd5dde6c0e | |||
| 767f0e5d3f | |||
| db121421a6 | |||
| 8bfc36db6e | |||
| 58d23ba378 | |||
| a09fbb4b15 | |||
| cb8517ba0a | |||
| 5512e7a27c | |||
| 5d6fd93474 | |||
| c5aec5703a | |||
| 33b88e9770 | |||
| 25ff5bd353 | |||
| 0fefd8493b | |||
| da8963d788 | |||
| 250a1cfeca | |||
| cedd0f5da2 | |||
| ec545d7b42 | |||
| 9d4875a727 | |||
| 23277a622c | |||
| d07ab49904 | |||
| 3932c53328 | |||
| d7ce5e68ea | |||
| 748b7c423f | |||
| 2eb727f1fe | |||
| 31b531ad65 | |||
| 704c6f09c7 | |||
| 70328b18d4 | |||
| 2e5b4a9b2c | |||
| b05b5b9347 | |||
| 4978566117 | |||
| 121941635d | |||
| bf94109ade | |||
| e42b4a266b | |||
| 93fed65ea2 | |||
| 095be9c904 | |||
| dfb00f4c5f | |||
| 0d57e17d67 | |||
| c3d5e2da91 | |||
| af0583d8b6 | |||
| 718554a39e | |||
| 20008be317 | |||
| f9d781bcab | |||
| 081e98739e | |||
| dd20427db9 | |||
| c57e5e1305 | |||
| 1eef136c7c | |||
| 091a379da1 | |||
| ef40b0085b | |||
| f82e80dac6 | |||
| cd659994bd | |||
| 0ace2b4332 | |||
| ecb38fdf73 | |||
| 87578049a0 | |||
| fb636cb6f0 | |||
| f67a59bce6 | |||
| 8d0ea29df0 | |||
| 1a8d6fb4cb | |||
| 598b34a326 | |||
| bffa355c6c | |||
| a5e6639739 | |||
| daf2b5e5fb | |||
| 3ad37b320f | |||
| 1cd78b91d9 | |||
| 74f6c46e46 | |||
| 97da8e161b | |||
| e5f4d2b9c2 | |||
| b99f4892d7 | |||
| 7849e595b1 | |||
| 4542e83fa0 | |||
| c7b3e420b7 | |||
| ac8cb2a106 | |||
| af417120e1 | |||
| 3b82490c6f | |||
| c5067cff6b | |||
| 180ce9ffe6 | |||
| 3a6fd9448e | |||
| 7e605f4e84 | |||
| f7318ec891 | |||
| 4506da6a6f | |||
| a43374ed32 | |||
| cc18296cc5 | |||
| d94ac763b6 | |||
| 40ea76cf5a | |||
| cdd6ab000b | |||
| 58438762a1 | |||
| 41e3027fda | |||
| 6f92ca058a | |||
| 5a47644dc6 | |||
| 1f3f4cf702 | |||
| 5f0be9dcd0 | |||
| 5a3f52d988 | |||
| 15f635f7c3 | |||
| b87ffb2082 | |||
| 787303284f | |||
| a1886911fc | |||
| ab399146b4 | |||
| 572c07ddb3 | |||
| 2cab2118a1 | |||
| f57d7bb016 | |||
| 4a7230aa3c | |||
| 228553f931 | |||
| 77fa3f1598 | |||
| 5c8e11ac9f | |||
| c249662e10 | |||
| ec3a5b0e17 | |||
| af018b75f0 | |||
| 778173ec07 | |||
| 8a604185fd | |||
| 98d223a51b | |||
| 0467017a70 | |||
| 776551afa5 | |||
| cf224ab31b | |||
| 41a70bf9ca | |||
| 6915e026ed | |||
| 20c5aee565 | |||
| e509122aad | |||
| ab79e96c3c | |||
| b4380432f6 | |||
| 126878488f | |||
| 4b2d80f662 | |||
| 9b00648f21 | |||
| 456ec66b0a | |||
| 19c4e1a992 | |||
| c60b3166b1 | |||
| d1c14e8aa4 | |||
| 6ada5cc074 | |||
| f20cd780a4 | |||
| 107308f6df | |||
| 8c6a2a848c | |||
| 58666c0ca4 | |||
| 476e8c7136 | |||
| 2ca4155f23 | |||
| 4b5553c97f | |||
| 0878f45a6c | |||
| a71b6041f6 | |||
| ee19b19984 | |||
| a1c0c1582b | |||
| 283f7f9e24 | |||
| 1ea67950e1 | |||
| 0d2d9f897c | |||
| a021b4bd8b | |||
| c05aa93739 | |||
| 62811bf134 | |||
| 68fff4a4b5 | |||
| 4567527249 | |||
| 1fdeef511c | |||
| bc990004e1 | |||
| c9b0026cd0 | |||
| 0609b9ded0 | |||
| d74509a235 | |||
| c84a332531 | |||
| cc59d89538 | |||
| b7ba7d40ae | |||
| 0347dcda05 | |||
| 81efc176f4 | |||
| 79492a9d2d | |||
| a769f0b0f4 | |||
| b9ee0c5477 | |||
| d7c8d8ab4d | |||
| 898650d6bc | |||
| 27073dd9bd | |||
| c92765e26e | |||
| db738e0a2d | |||
| 32be9a9273 | |||
| 8e1b422e4a | |||
| 82dcaa725f | |||
| 7cd2b8d7a0 | |||
| c4b6b87344 | |||
| f66852f1ef | |||
| d1d0f382a2 | |||
| 39c2e4c28b | |||
| b721fa2ef5 | |||
| b8400cd897 | |||
| 8b5267f09f | |||
| 06be3c56a9 | |||
| 92bdd13bb1 | |||
| 69562c5d20 | |||
| 4452429b45 | |||
| a256745eae | |||
| 345749884d | |||
| 5667fe6746 | |||
| c9de302fc6 | |||
| 6883cad07e | |||
| 85fa2a9dd4 | |||
| 5865356e92 | |||
| bc43de7b21 | |||
| a574dc7bf1 | |||
| c5fcae130f | |||
| 45c2887b19 | |||
| 84d5e6e645 | |||
| 92c4a3319c | |||
| 1a30df265d | |||
| ee69fd140b | |||
| f57d0bab32 | |||
| a491adc425 | |||
| 5f53a159a0 | |||
| f2cf1dba92 | |||
| 561d424a0e | |||
| ba977b7cd7 | |||
| 53b19611e1 | |||
| b8d6f23fb7 | |||
| b96407be86 | |||
| bdf46e928f | |||
| 9acba3fa68 | |||
| 71e8c841ce | |||
| 701a47c850 | |||
| ffde769567 | |||
| 3b5a758525 | |||
| 4b10f3c13c | |||
| e1c1c19a9b | |||
| 690a793aae | |||
| d392c493b9 | |||
| 6799b21124 | |||
| cac25fa7b0 | |||
| 74b6dd7799 | |||
| 6b74dca0ff | |||
| d56ba0945d | |||
| 8f6c284ccf | |||
| 9818ca366d | |||
| 856edfe80c | |||
| 9a850db605 | |||
| bb5202fa7e | |||
| d72dbffa23 | |||
| b9ee5aae81 | |||
| 0fe53eda86 | |||
| c02b89759c | |||
| 69e5f2ae67 | |||
| 3cd89bd8a4 | |||
| 4f56567963 | |||
| 292ee29275 | |||
| ede49d9115 | |||
| e2a1ea09dc | |||
| 1a45463725 | |||
| c6e9039b23 | |||
| 0da250c9f1 | |||
| 84d09cdf32 | |||
| d6a8f10a70 | |||
| c3e3580baf | |||
| beb40dd10e | |||
| efb717fe73 | |||
| bfb50df9a7 | |||
| 5e09ddb6a4 | |||
| 3ba95b8ef7 | |||
| 55583c71ff | |||
| ebda0bebfc | |||
| 6969bef70a | |||
| 7941f1ede7 | |||
| fc09da86e1 | |||
| 50500332ae | |||
| ac9bb4df8e | |||
| dbe83cc099 | |||
| dc99a7dac2 | |||
| ac1cb3257f | |||
| 5e3979c25d | |||
| d6fb616e54 | |||
| bf805d1453 | |||
| ec0cbfac4b | |||
| f0e19d8334 | |||
| 08be1ae562 | |||
| 179f6c8f23 | |||
| d1124359f5 | |||
| 5545532328 | |||
| 45b56cb081 | |||
| 401f6d68d6 | |||
| f0cbd32dd1 | |||
| 0dc890fbac | |||
| f15e1f25ae | |||
| f8cef96c2c | |||
| 1b2411c6d2 | |||
| 131729726f | |||
| 5744abfe4e | |||
| 07ea6a6adf | |||
| 1ba7e6b6f6 | |||
| 7afc9ac899 | |||
| 1b188c1fb9 | |||
| af83d4d4df | |||
| 18dedd985a | |||
| 664b27aaf0 | |||
| 3859551eb3 | |||
| 53934702ce | |||
| 4a9c5d9574 | |||
| c528d8b17d | |||
| c8dc993fb4 | |||
| 7c84593eb0 | |||
| 657fd1dfd1 | |||
| 4465171e89 | |||
| 9ee2065477 | |||
| ca497b43e4 | |||
| bb0b90783c | |||
| d9a6714cb9 | |||
| 9785d5b2fb | |||
| da10768b10 | |||
| 48f6af4907 | |||
| 0c0d949c47 | |||
| 7c9c8396d1 | |||
| f001646f2c | |||
| f3c839b35f | |||
| 80c4a02c5f | |||
| 01abc097ee | |||
| 63812b0a2c | |||
| ba49443532 | |||
| 66481d3d2f | |||
| d1ed72ad37 | |||
| 75d19442e3 | |||
| f0bd79d41b | |||
| 15695ea3bc | |||
| 58f002d8f0 | |||
| c16622b072 | |||
| 422baea136 | |||
| 631eaccc64 | |||
| 43310bb7b7 | |||
| 7f1a7a4372 | |||
| 2cfe8b95de | |||
| 30734d178c | |||
| 8b9ccc15f7 | |||
| 9154981684 | |||
| 6201f9eb14 | |||
| 5eae522d94 | |||
| 4d3e5a14e6 | |||
| 0edc943c68 | |||
| 2551b53a9a | |||
| 7d79105cf4 | |||
| aafb5bb168 | |||
| 223fc34ab9 | |||
| dc61ea999a | |||
| 2152bd8da2 | |||
| 4e6cdf8989 | |||
| 4f8a2d3806 | |||
| 3db0bbe76c | |||
| 9bafde67a8 | |||
| 566ef91e26 | |||
| 8f6fd60fe2 | |||
| 116ba9196d | |||
| 89a2d1d6ad | |||
| e4f052c1a9 | |||
| c093d14697 | |||
| acd9303b7b | |||
| 0c2c19edb4 | |||
| 3f8e8a3390 | |||
| 115d91069c | |||
| b73a1be03a | |||
| 169b5dfaec | |||
| 8a563047e6 | |||
| 1799376ab7 | |||
| 5ae1967aea | |||
| 0e864fd81b | |||
| 0ca10bf3d5 | |||
| 7ba4f43be4 | |||
| c935d2f038 | |||
| 923c9ef17c | |||
| 8857a80109 | |||
| dc8ad50eaa | |||
| 3f276c5def | |||
| ac92218e38 | |||
| ec05cd6336 | |||
| e9439c54f6 | |||
| c72ac9b230 | |||
| e4494f85b6 | |||
| 8283fd51c3 | |||
| eccfcf020c | |||
| c9ea993c88 | |||
| d86e2e2aa9 |
@ -20,6 +20,5 @@ data_file = test/results/coverage/coverage
|
||||
omit =
|
||||
*/python*/dist-packages/*
|
||||
*/python*/site-packages/*
|
||||
*/python*/distutils/*
|
||||
*/pyshared/*
|
||||
*/python*/distutils
|
||||
*/pytest
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,4 +1 @@
|
||||
CHANGELOG.md merge=union
|
||||
.github/ export-ignore
|
||||
hacking/ export-ignore
|
||||
ticket-stubs/ export-ignore
|
||||
|
||||
1117
.github/BOTMETA.yml
vendored
1117
.github/BOTMETA.yml
vendored
File diff suppressed because it is too large
Load Diff
11
.github/ISSUE_TEMPLATE.md
vendored
11
.github/ISSUE_TEMPLATE.md
vendored
@ -13,26 +13,21 @@ Also test if the latest release, and master branch are affected too.
|
||||
<!--- Name of the module/plugin/task/feature -->
|
||||
|
||||
##### ANSIBLE VERSION
|
||||
<!--- Paste verbatim output from "ansible --version" between quotes below -->
|
||||
<!--- Paste verbatim output from “ansible --version” between quotes below -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
##### CONFIGURATION
|
||||
<!---
|
||||
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
|
||||
|
||||
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
|
||||
Mention any settings you have changed/added/removed in ansible.cfg
|
||||
(or using the ANSIBLE_* environment variables).
|
||||
|
||||
-->
|
||||
|
||||
##### OS / ENVIRONMENT
|
||||
<!---
|
||||
Mention the OS you are running Ansible from, and the OS you are
|
||||
managing, or say "N/A" for anything that is not platform-specific.
|
||||
Also mention the specific version of what you are trying to control,
|
||||
e.g. if this is a network bug the version of firmware on the network device.
|
||||
managing, or say “N/A” for anything that is not platform-specific.
|
||||
-->
|
||||
|
||||
##### SUMMARY
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -18,7 +18,7 @@ the change does.
|
||||
<!--- Name of the module/plugin/module/task -->
|
||||
|
||||
##### ANSIBLE VERSION
|
||||
<!--- Paste verbatim output from "ansible --version" between quotes below -->
|
||||
<!--- Paste verbatim output from “ansible --version” between quotes below -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
48
.gitignore
vendored
48
.gitignore
vendored
@ -20,37 +20,30 @@ rpm-build
|
||||
# Mac OS X stuff...
|
||||
.DS_Store
|
||||
# manpage build stuff...
|
||||
docs/man/man1/ansible*.1
|
||||
docs/man/man1/ansible*.1.asciidoc.in
|
||||
docs/man/man1/ansible.1
|
||||
docs/man/man1/ansible-doc.1
|
||||
docs/man/man1/ansible-galaxy.1
|
||||
docs/man/man1/ansible-playbook.1
|
||||
docs/man/man1/ansible-pull.1
|
||||
docs/man/man1/ansible-vault.1
|
||||
docs/man/man3/*
|
||||
# Sublime stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
# docsite stuff...
|
||||
docs/api/_build/
|
||||
docs/api/rst/
|
||||
docs/docsite/rst/modules_by_category.rst
|
||||
docs/docsite/rst/playbooks_directives.rst
|
||||
docs/docsite/rst/list_of_*.rst
|
||||
docs/docsite/rst/*_module.rst
|
||||
docs/docsite/*.html
|
||||
docs/docsite/_build
|
||||
docs/docsite/_static/*.gif
|
||||
docs/docsite/_static/*.png
|
||||
docs/docsite/_static/websupport.js
|
||||
docs/docsite/htmlout
|
||||
docs/docsite/searchindex.js
|
||||
docs/docsite/rst_warnings
|
||||
docs/docsite/rst/*_module.rst
|
||||
docs/docsite/rst/ansible.rst
|
||||
docs/docsite/rst/ansible-*.rst
|
||||
docs/docsite/rst/community_maintained.rst
|
||||
docs/docsite/rst/config.rst
|
||||
docs/docsite/rst/core_maintained.rst
|
||||
docs/docsite/rst/list_of_*.rst
|
||||
docs/docsite/rst/modules_by_category.rst
|
||||
docs/docsite/rst/network_maintained.rst
|
||||
docs/docsite/rst/plugins_by_category.rst
|
||||
docs/docsite/rst/partner_maintained.rst
|
||||
docs/docsite/rst/playbook_keywords.rst
|
||||
docs/docsite/rst/playbooks_directives.rst
|
||||
docs/docsite/rst/plugins/*/*.rst
|
||||
docs/docsite/htmlout
|
||||
docs/docsite/_build
|
||||
docs/api/rst/
|
||||
docs/api/_build/
|
||||
# deb building stuff...
|
||||
/debian/
|
||||
deb-build
|
||||
@ -74,23 +67,21 @@ Vagrantfile
|
||||
.vagrant
|
||||
ansible.egg-info/
|
||||
/shippable/
|
||||
/test/integration/cloud-config-*.*
|
||||
!/test/integration/cloud-config-*.*.template
|
||||
# Release directory
|
||||
packaging/release/ansible_release
|
||||
/.cache/
|
||||
/test/results/coverage/*=coverage.*
|
||||
/test/results/coverage/coverage*
|
||||
/test/results/reports/coverage*.xml
|
||||
/test/results/reports/coverage*/
|
||||
/test/results/reports/coverage.xml
|
||||
/test/results/reports/coverage/
|
||||
/test/results/bot/*.json
|
||||
/test/results/junit/*.xml
|
||||
/test/results/logs/*.log
|
||||
/test/results/data/*.json
|
||||
/test/integration/inventory.remote
|
||||
/test/integration/inventory.networking
|
||||
/test/integration/inventory.winrm
|
||||
/test/integration/cloud-config-aws.yml
|
||||
# old submodule dirs
|
||||
lib/ansible/modules/core
|
||||
lib/ansible/modules/extras
|
||||
# python 'rope' stuff
|
||||
.ropeproject
|
||||
# local 'ack' config files
|
||||
@ -101,4 +92,3 @@ htmlcov/
|
||||
.coverage
|
||||
# ansible-test coverage results
|
||||
test/units/.coverage.*
|
||||
/test/integration/cloud-config-azure.yml
|
||||
|
||||
4
.mailmap
4
.mailmap
@ -32,5 +32,5 @@ Will Thames <will@thames.id.au> Will Thames <will.thames@xvt.com.au>
|
||||
nitzmahone <mdavis@ansible.com> Matt Davis <nitzmahone@users.noreply.github.com>
|
||||
Dag Wieers <dag@wieers.com> Dag Wieërs <dag@wieers.com>
|
||||
Dag Wieers <dag@wieers.com> Dag Wieers <dag.wieers@gmail.com>
|
||||
Rene Moser <mail@renemoser.net> René Moser <mail@renemoser.net>
|
||||
Rene Moser <mail@renemoser.net> Rene Moser <rene.moser@swisstxt.ch>
|
||||
|
||||
|
||||
|
||||
671
CHANGELOG.md
671
CHANGELOG.md
@ -1,621 +1,26 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
<a id="2.5"></a>
|
||||
<a id="2.3.4"></a>
|
||||
|
||||
## 2.5 "TBD" - ACTIVE DEVELOPMENT
|
||||
## 2.3.4 "Ramble On" - TBD
|
||||
* Flush stdin when passing the become password. Fixes some cases of timeout on
|
||||
Python3 with the ssh connection plugin: https://github.com/ansible/ansible/pull/35049
|
||||
|
||||
### Major Changes
|
||||
* Removed the previously deprecated 'accelerate' mode and all associated keywords and code.
|
||||
### Bugfixes
|
||||
* Fix setting of environment in a task that uses a loop:
|
||||
https://github.com/ansible/ansible/issues/32685
|
||||
* Fix https retrieval with TLSv1.2: https://github.com/ansible/ansible/pull/32053
|
||||
|
||||
### Deprecations
|
||||
|
||||
#### Deprecated Modules (to be removed in 2.9):
|
||||
|
||||
#### Removed Modules (previously deprecated):
|
||||
* accelerate
|
||||
|
||||
### Minor Changes
|
||||
* added a few new magic vars corresponding to configuration/command line options:
|
||||
`ansible_diff_mode`, `ansible_inventory_sources`, `ansible_limit`, `ansible_run_tags` , `ansible_forks` and `ansible_skip_tags`
|
||||
* Updated the bundled copy of the six library to 1.11.0
|
||||
|
||||
### New Modules
|
||||
|
||||
#### Windows
|
||||
|
||||
* win_scheduled_task_stat
|
||||
|
||||
|
||||
<a id="2.4"></a>
|
||||
|
||||
## 2.4 "Dancing Days" - 2017/09/18
|
||||
|
||||
### Major Changes
|
||||
|
||||
* Support for Python-2.4 and Python-2.5 on the managed system's side was dropped. If you need to manage a system that ships with Python-2.4 or Python-2.5, you'll need to install Python-2.6 or better on the managed system or run Ansible-2.3 until you can upgrade the system.
|
||||
* New import/include keywords to replace the old bare `include` directives. The use of `static: {yes|no}` on such includes is now deprecated.
|
||||
- Using `import_*` (`import_playbook`, `import_tasks`, `import_role`) directives are static.
|
||||
- Using `include_*` (`include_tasks`, `include_role`) directives are dynamic.
|
||||
This is done to avoid collisions and possible security issues as facts come from the remote targets and they might be compromised.
|
||||
* New `order` play level keyword that allows the user to change the order in which Ansible processes hosts when dispatching tasks.
|
||||
* Users can now set group merge priority for groups of the same depth (parent child relationship), using the new `ansible_group_priority` variable, when values are the same or don't exist it will fallback to the previous sorting by name'.
|
||||
* Inventory has been revamped:
|
||||
- Inventory classes have been split to allow for better management and deduplication
|
||||
- Logic that each inventory source duplicated is now common and pushed up to reconciliation
|
||||
- VariableManager has been updated for better interaction with inventory
|
||||
- Updated CLI with helper method to initialize base objects for plays
|
||||
- New inventory plugins for creating inventory
|
||||
- Old inventory formats are still supported via plugins
|
||||
- Inline host_list is also an inventory plugin, an example alternative `advanced_host_list` is also provided (it supports ranges)
|
||||
- New configuration option to list enabled plugins and precedence order `[inventory]enable_plugins` in ansible.cfg
|
||||
- vars_plugins have been reworked, they are now run from Vars manager and API has changed (need docs)
|
||||
- Loading group_vars/host_vars is now a vars plugin and can be overridden
|
||||
- It is now possible to specify mulitple inventory sources in the command line (-i /etc/hosts1 -i /opt/hosts2)
|
||||
- Inventory plugins can use the cache plugin (i.e. virtualbox) and is affected by `meta: refresh_inventory`
|
||||
- Group variable precedence is now configurable via new 'precedence' option in ansible.cfg (needs docs)
|
||||
- Improved warnings and error messages across the board
|
||||
* Configuration has been changed from a hardcoded listing in the constants module to dynamically loaded from yaml definitions
|
||||
- Also added an ansible-config CLI to allow for listing config options and dumping current config (including origin)
|
||||
- TODO: build upon this to add many features detailed in ansible-config proposal https://github.com/ansible/proposals/issues/35
|
||||
* Windows modules now support the use of multiple shared module_utils files in the form of Powershell modules (.psm1), via `#Requires -Module Ansible.ModuleUtils.Whatever.psm1`
|
||||
* Python module argument_spec now supports custom validation logic by accepting a callable as the `type` argument.
|
||||
* Windows become_method: runas now works across all authtypes and will auto-elevate under UAC if WinRM user has "Act as part of the operating system" privilege
|
||||
|
||||
### Deprecations
|
||||
* The behaviour when specifying `--tags` (or `--skip-tags`) multiple times on the command line
|
||||
has changed so that the tags are merged together by default. See the
|
||||
documentation for how to temporarily use the old behaviour if needed:
|
||||
https://docs.ansible.com/ansible/intro_configuration.html#merge-multiple-cli-tags
|
||||
* The `fetch` module's `validate_md5` parameter has been deprecated and will be
|
||||
removed in 2.8. If you wish to disable post-validation of the downloaded
|
||||
file, use validate_checksum instead.
|
||||
* Those using ansible as a library should note that the `ansible.vars.unsafe_proxy`
|
||||
module is deprecated and slated to go away in 2.8. The functionality has been
|
||||
moved to `ansible.utils.unsafe_proxy` to avoid a circular import.
|
||||
* The win_get_url module has the dictionary 'win_get_url' in its results deprecated,
|
||||
its content is now also available directly in the resulting output, like other modules.
|
||||
* previouslly deprecated 'hostfile' config settings have been 're-deprecated' as before the code did not warn about deprecated configuration settings, but it does now.
|
||||
|
||||
#### Deprecated Modules (to be removed in 2.8):
|
||||
* azure: use M(azure_rm_virtualmachine) instead
|
||||
* cs_nic: replaced by cs_instance_nic_secondaryip, also see new module cs_instance_nic for managing nics
|
||||
* ec2_facts: replaced by ec2_metadata_facts
|
||||
* ec2_remote_facts: replaced by ec2_instance_facts
|
||||
* panos_address: use M(panos_object) instead
|
||||
* panos_nat_policy: use M(panos_nat_rule) instead
|
||||
* panos_security_policy: use M(panos_security_rule) instead
|
||||
* panos_service: use M(panos_object) instead
|
||||
* s3: replaced by aws_s3
|
||||
* win_msi: use M(win_package) instead
|
||||
|
||||
#### Removed Modules (previouslly deprecated):
|
||||
* eos_template: use eos_config instead
|
||||
* ios_template: use ios_config instead
|
||||
* iosxr_template: use iosxr_config instead
|
||||
* junos_template: use junos_config instead
|
||||
* nxos_template: use nxos_config instead
|
||||
* openswitch
|
||||
* ops_template: use ops_config instead
|
||||
|
||||
|
||||
### Minor Changes
|
||||
* Now deprecated configuration options issue warnings when set.
|
||||
* Removed unused and deprecated config option `pattern`
|
||||
* Updated the copy of six bundled for modules to use from 1.4.1 to 1.10.0
|
||||
* The `include_dir` var is not a global anymore, as we now allow multiple inventory sources, it is now host dependant.
|
||||
This means it cannot be used wherever host vars are not permitted, for example in task/handler names.
|
||||
* Fixed a cornercase with ini inventory vars. Previously, if an inventory var
|
||||
was a quoted string with hash marks ("#") in it then the parsed string
|
||||
included the quotes. Now the string will not be quoted. Previously, if the
|
||||
quoting ended before the string finished and then the hash mark appeared, the
|
||||
hash mark was included as part of the string. Now it is treated as
|
||||
a trailing comment:
|
||||
|
||||
# Before:
|
||||
var1="string#comment" ===> var1: "\"string#comment\""
|
||||
var1="string" #comment ===> var1: "\"string\" #comment"
|
||||
# After:
|
||||
var1="string#comment" ===> var1: "string#comment"
|
||||
var1="string" #comment ===> var1: "string"
|
||||
|
||||
The new behaviour mirrors how the variables would appear if there was no hash
|
||||
mark in the string.
|
||||
* As of 2.4.0, the fetch module fails if there are errors reading the remote file.
|
||||
Use `ignore_errors` or `failed_when` in playbooks if you wish to ignore errors.
|
||||
* Experimentally added pmrun become method.
|
||||
* Enable the docker connection plugin to use su as a become method
|
||||
* Add an encoding parameter for the replace module so that it can operate on non-utf-8 files
|
||||
* By default, Ansible now uses the cryptography module to implement vault instead of the older pycrypto module.
|
||||
* Changed task state resulting from both `rc` and `failed` fields returned, 'rc' no longer overrides 'failed'. Test plugins have also been updated accordingly.
|
||||
* The win_unzip module no longer includes dictionary 'win_unzip' in its results,
|
||||
the content is now directly in the resulting output, like pretty much every other module.
|
||||
* Rewrite of the copy module so that it handles cornercases with symbolic links
|
||||
and empty directories. The copy module has a new parameter, `local_follow`
|
||||
which controls how links on the source system are treated. (The older
|
||||
parameter, follow is for links on the remote system.)
|
||||
* Update the handling of symbolic file permissions in file-related mode
|
||||
parameters to deal with multiple operators. For instance, `mode='u=rw+x-X'` to
|
||||
set the execute bit on directories, remove it from filea, and set read-write
|
||||
on both is now supported
|
||||
* Added better cookie parsing to fetch_url/open_url. Cookies are now in a dictionary named `cookies`
|
||||
in the fetch_url result. Anything using `open_url` directly can pass a cookie object as a named arg
|
||||
(`cookies`), and then parse/format the cookies in the result.
|
||||
* The bundled copy of six in lib/ansible/module_utils/six is now used
|
||||
unconditionally. The code to fallback on a system six interfered with static
|
||||
analysis of the code so the cost of using the fallback code became too high.
|
||||
Distributions which wish to unbundle may do so by replacing the bundled six
|
||||
in ansible/module_utils/six/__init__.py. Six is tricky to unbundle, however,
|
||||
so they may want to base their efforts off the code we were using:
|
||||
* https://github.com/ansible/ansible/blob/2fff690caab6a1c6a81973f704be3fbd0bde2c2f/lib/ansible/module_utils/six/__init__.py
|
||||
* Update ipaddr Jinja filters to replace existing non RFC compliant ones. Added additional filters for easier use
|
||||
of handling IP addresses. (PR# 26566)
|
||||
* datetime filter updated to use default format of datetime.datetime (ISO8601)
|
||||
* The junit plugin now has an option to report a junit test failure on changes for idempotent testing.
|
||||
* New 'diff' keyword allows setting diff mode on playbook objects, overriding command line option and config.
|
||||
* New config settings for inventory to:
|
||||
- control inventory plugins used
|
||||
- extensions of files to ignore when using inventory directory
|
||||
- patterns of flies to ignore when using inventory directory
|
||||
- option to toggle failed inventory source parsing between an error or a warning
|
||||
* More fixes for Python 3 across the code base.
|
||||
* win_shell and win_command modules now properly preserve quoted arguments passed on the command-line. Tasks that attempted to work around the issue by adding extra quotes/escaping may need to be reworked. See https://github.com/ansible/ansible/issues/23019 for additional detail.
|
||||
* All configuration paths are now relative to the `ansible.cfg` file used.
|
||||
* By user request, a 'configuration macro' (``CWD``) is available to force configured paths to be relative to the current working directory. Please note that this is unsafe and not recommended.
|
||||
|
||||
|
||||
#### New Callbacks:
|
||||
- full_skip
|
||||
- profile_roles
|
||||
- stderr
|
||||
|
||||
#### New Filters:
|
||||
- parse_cli
|
||||
- parse_cli_textfsm
|
||||
|
||||
#### New Inventory Plugins:
|
||||
- advanced_host_list
|
||||
- constructed
|
||||
- host_list
|
||||
- ini
|
||||
- script
|
||||
- virtualbox
|
||||
- yaml
|
||||
|
||||
#### New Inventory scripts:
|
||||
- lxd
|
||||
|
||||
#### New: Tests
|
||||
- any : true if any element is true
|
||||
- all: true if all elements are true
|
||||
|
||||
### Module Notes
|
||||
- The docker_container module has gained a new option, `working_dir` which allows
|
||||
specifying the working directory for the command being run in the image.
|
||||
- The ec2_win_password module now requires the cryptography python module be installed to run
|
||||
- The stat module added a field, lnk_target. When the file being stated is
|
||||
a symlink, lnk_target will contain the target of the link. This differs from
|
||||
lnk_source when the target is specified relative to the symlink. In this
|
||||
case, lnk_target will remain relative while lnk_source will be expanded to an
|
||||
absolute path.
|
||||
- The archive module has a new parameter exclude_path which lists paths to exclude from the archive
|
||||
- The yum module has a new parameter security which limits state=latest to security updates
|
||||
- The template module gained a follow parameter to match with copy and file.
|
||||
Like those modules, template defaults this parameter to False. Previously,
|
||||
template hardcoded this to true.
|
||||
- Added a new parameter to command module that lets users specify data to pipe
|
||||
into the command's stdin.
|
||||
- The azure_rm modules now accept a `cloud_environment` arg to access regional and private clouds.
|
||||
- The azure_rm modules and inventory script now require at least version 2.0.0 of the Azure Python SDK.
|
||||
|
||||
### New Modules
|
||||
|
||||
#### Cloud
|
||||
- amazon
|
||||
* aws_api_gateway
|
||||
* aws_direct_connect_connection
|
||||
* aws_direct_connect_link_aggregation_group
|
||||
* aws_s3
|
||||
* aws_s3_bucket_facts
|
||||
* aws_waf_facts
|
||||
* data_pipeline
|
||||
* dynamodb_ttl
|
||||
* ec2_instance_facts
|
||||
* ec2_metadata_facts
|
||||
* ec2_vpc_dhcp_option_facts
|
||||
* ec2_vpc_endpoint
|
||||
* ec2_vpc_endpoint_facts
|
||||
* ec2_vpc_peering_facts
|
||||
* ecs_attribute
|
||||
* elb_application_lb
|
||||
* elb_application_lb_facts
|
||||
* elb_target_group
|
||||
* elb_target_group_facts
|
||||
* iam_group
|
||||
* iam_managed_policy
|
||||
* lightsail
|
||||
* redshift_facts
|
||||
- azure
|
||||
* azure_rm_acs
|
||||
* azure_rm_availabilityset
|
||||
* azure_rm_availabilityset_facts
|
||||
* azure_rm_dnsrecordset
|
||||
* azure_rm_dnsrecordset_facts
|
||||
* azure_rm_dnszone
|
||||
* azure_rm_dnszone_facts
|
||||
* azure_rm_functionapp
|
||||
* azure_rm_functionapp_facts
|
||||
* azure_rm_loadbalancer
|
||||
* azure_rm_loadbalancer_facts
|
||||
* azure_rm_managed_disk
|
||||
* azure_rm_managed_disk_facts
|
||||
* azure_rm_virtualmachine_extension
|
||||
* azure_rm_virtualmachine_scaleset
|
||||
* azure_rm_virtualmachine_scaleset_facts
|
||||
- atomic
|
||||
* atomic_container
|
||||
- cloudstack
|
||||
* cs_instance_nic
|
||||
* cs_instance_nic_secondaryip
|
||||
* cs_network_acl
|
||||
* cs_network_acl_rule
|
||||
* cs_storage_pool
|
||||
* cs_vpn_gateway
|
||||
- digital_ocean
|
||||
* digital_ocean_floating_ip
|
||||
- docker
|
||||
* docker_secret
|
||||
* docker_volume
|
||||
- google
|
||||
* gce_labels
|
||||
* gcp_backend_service
|
||||
* gcp_forwarding_rule
|
||||
* gcp_healthcheck
|
||||
* gcp_target_proxy
|
||||
* gcp_url_map
|
||||
- misc
|
||||
* helm
|
||||
- ovirt
|
||||
* ovirt_host_storage_facts
|
||||
* ovirt_scheduling_policies_facts
|
||||
* ovirt_storage_connections
|
||||
- vmware
|
||||
* vcenter_license
|
||||
* vmware_guest_find
|
||||
* vmware_guest_tools_wait
|
||||
* vmware_resource_pool
|
||||
|
||||
#### Commands
|
||||
* telnet
|
||||
|
||||
#### Crypto
|
||||
* openssl_certificate
|
||||
* openssl_csr
|
||||
|
||||
#### Files
|
||||
* xml
|
||||
|
||||
#### Identity
|
||||
- cyberark
|
||||
* cyberark_authentication
|
||||
* cyberark_user
|
||||
- ipa
|
||||
* ipa_dnsrecord
|
||||
|
||||
#### Monitoring
|
||||
* sensu_client
|
||||
* sensu_handler
|
||||
* sensu_silence
|
||||
|
||||
#### Network
|
||||
- aci
|
||||
* aci_aep
|
||||
* aci_ap
|
||||
* aci_bd
|
||||
* aci_bd_subnet
|
||||
* aci_bd_to_l3out
|
||||
* aci_contract
|
||||
* aci_contract_subject_to_filter
|
||||
* aci_epg
|
||||
* aci_epg_monitoring_policy
|
||||
* aci_epg_to_contract
|
||||
* aci_epg_to_domain
|
||||
* aci_filter
|
||||
* aci_filter_entry
|
||||
* aci_intf_policy_fc
|
||||
* aci_intf_policy_l2
|
||||
* aci_intf_policy_lldp
|
||||
* aci_intf_policy_mcp
|
||||
* aci_intf_policy_port_channel
|
||||
* aci_intf_policy_port_security
|
||||
* aci_l3out_route_tag_policy
|
||||
* aci_rest
|
||||
* aci_taboo_contract
|
||||
* aci_tenant
|
||||
* aci_tenant_action_rule_profile
|
||||
* aci_tenant_span_dst_group
|
||||
* aci_vrf
|
||||
- aireos
|
||||
* aireos_command
|
||||
* aireos_config
|
||||
- aruba
|
||||
* aruba_command
|
||||
* aruba_config
|
||||
- avi
|
||||
* avi_actiongroupconfig
|
||||
* avi_alertconfig
|
||||
* avi_alertemailconfig
|
||||
* avi_alertscriptconfig
|
||||
* avi_alertsyslogconfig
|
||||
* avi_authprofile
|
||||
* avi_backup
|
||||
* avi_backupconfiguration
|
||||
* avi_cloud
|
||||
* avi_cloudconnectoruser
|
||||
* avi_cloudproperties
|
||||
* avi_cluster
|
||||
* avi_controllerproperties
|
||||
* avi_dnspolicy
|
||||
* avi_gslb
|
||||
* avi_gslbapplicationpersistenceprofile
|
||||
* avi_gslbgeodbprofile
|
||||
* avi_gslbhealthmonitor
|
||||
* avi_gslbservice
|
||||
* avi_hardwaresecuritymodulegroup
|
||||
* avi_httppolicyset
|
||||
* avi_ipaddrgroup
|
||||
* avi_ipamdnsproviderprofile
|
||||
* avi_microservicegroup
|
||||
* avi_network
|
||||
* avi_networksecuritypolicy
|
||||
* avi_poolgroupdeploymentpolicy
|
||||
* avi_prioritylabels
|
||||
* avi_scheduler
|
||||
* avi_seproperties
|
||||
* avi_serverautoscalepolicy
|
||||
* avi_serviceengine
|
||||
* avi_serviceenginegroup
|
||||
* avi_snmptrapprofile
|
||||
* avi_stringgroup
|
||||
* avi_trafficcloneprofile
|
||||
* avi_useraccountprofile
|
||||
* avi_vrfcontext
|
||||
* avi_vsdatascriptset
|
||||
* avi_vsvip
|
||||
* avi_webhook
|
||||
- bigswitch
|
||||
* bcf_switch
|
||||
- cloudengine
|
||||
* ce_aaa_server
|
||||
* ce_aaa_server_host
|
||||
* ce_acl
|
||||
* ce_acl_advance
|
||||
* ce_acl_interface
|
||||
* ce_bfd_global
|
||||
* ce_bfd_session
|
||||
* ce_bfd_view
|
||||
* ce_bgp
|
||||
* ce_bgp_af
|
||||
* ce_bgp_neighbor
|
||||
* ce_bgp_neighbor_af
|
||||
* ce_config
|
||||
* ce_dldp
|
||||
* ce_dldp_interface
|
||||
* ce_eth_trunk
|
||||
* ce_evpn_bd_vni
|
||||
* ce_evpn_bgp
|
||||
* ce_evpn_bgp_rr
|
||||
* ce_evpn_global
|
||||
* ce_facts
|
||||
* ce_file_copy
|
||||
* ce_info_center_debug
|
||||
* ce_info_center_global
|
||||
* ce_info_center_log
|
||||
* ce_info_center_trap
|
||||
* ce_interface
|
||||
* ce_interface_ospf
|
||||
* ce_ip_interface
|
||||
* ce_link_status
|
||||
* ce_mlag_config
|
||||
* ce_mlag_interface
|
||||
* ce_mtu
|
||||
* ce_netconf
|
||||
* ce_netstream_aging
|
||||
* ce_netstream_export
|
||||
* ce_netstream_global
|
||||
* ce_netstream_template
|
||||
* ce_ntp
|
||||
* ce_ntp_auth
|
||||
* ce_ospf
|
||||
* ce_ospf_vrf
|
||||
* ce_reboot
|
||||
* ce_rollback
|
||||
* ce_sflow
|
||||
* ce_snmp_community
|
||||
* ce_snmp_contact
|
||||
* ce_snmp_location
|
||||
* ce_snmp_target_host
|
||||
* ce_snmp_traps
|
||||
* ce_snmp_user
|
||||
* ce_startup
|
||||
* ce_static_route
|
||||
* ce_stp
|
||||
* ce_switchport
|
||||
* ce_vlan
|
||||
* ce_vrf
|
||||
* ce_vrf_af
|
||||
* ce_vrf_interface
|
||||
* ce_vrrp
|
||||
* ce_vxlan_arp
|
||||
* ce_vxlan_gateway
|
||||
* ce_vxlan_global
|
||||
* ce_vxlan_tunnel
|
||||
* ce_vxlan_vap
|
||||
- cloudvision
|
||||
* cv_server_provision
|
||||
- eos
|
||||
* eos_logging
|
||||
* eos_vlan
|
||||
* eos_vrf
|
||||
- f5
|
||||
* bigip_command
|
||||
* bigip_config
|
||||
* bigip_configsync_actions
|
||||
* bigip_gtm_pool
|
||||
* bigip_iapp_service
|
||||
* bigip_iapp_template
|
||||
* bigip_monitor_tcp_echo
|
||||
* bigip_monitor_tcp_half_open
|
||||
* bigip_provision
|
||||
* bigip_qkview
|
||||
* bigip_snmp
|
||||
* bigip_snmp_trap
|
||||
* bigip_ucs
|
||||
* bigip_user
|
||||
* bigip_virtual_address
|
||||
- fortios
|
||||
* fortios_address
|
||||
- interface
|
||||
* net_interface
|
||||
* net_linkagg
|
||||
* net_lldp_interface
|
||||
- ios
|
||||
* ios_interface
|
||||
* ios_logging
|
||||
* ios_static_route
|
||||
* ios_user
|
||||
- iosxr
|
||||
* iosxr_banner
|
||||
* iosxr_interface
|
||||
* iosxr_logging
|
||||
* iosxr_user
|
||||
- junos
|
||||
* junos_banner
|
||||
* junos_interface
|
||||
* junos_l3_interface
|
||||
* junos_linkagg
|
||||
* junos_lldp
|
||||
* junos_lldp_interface
|
||||
* junos_logging
|
||||
* junos_static_route
|
||||
* junos_system
|
||||
* junos_vlan
|
||||
* junos_vrf
|
||||
- layer2
|
||||
* net_l2_interface
|
||||
* net_vlan
|
||||
- layer3
|
||||
* net_l3_interface
|
||||
* net_vrf
|
||||
- netscaler
|
||||
* netscaler_cs_action
|
||||
* netscaler_cs_policy
|
||||
* netscaler_cs_vserver
|
||||
* netscaler_gslb_service
|
||||
* netscaler_gslb_site
|
||||
* netscaler_gslb_vserver
|
||||
* netscaler_lb_monitor
|
||||
* netscaler_lb_vserver
|
||||
* netscaler_save_config
|
||||
* netscaler_server
|
||||
* netscaler_service
|
||||
* netscaler_servicegroup
|
||||
* netscaler_ssl_certkey
|
||||
- nuage
|
||||
* nuage_vspk
|
||||
- nxos
|
||||
* nxos_banner
|
||||
* nxos_logging
|
||||
- panos
|
||||
* panos_nat_rule
|
||||
* panos_object
|
||||
* panos_security_rule
|
||||
- protocol
|
||||
* net_lldp
|
||||
- routing
|
||||
* net_static_route
|
||||
- system
|
||||
* net_banner
|
||||
* net_logging
|
||||
* net_system
|
||||
* net_user
|
||||
- vyos
|
||||
* vyos_banner
|
||||
* vyos_interface
|
||||
* vyos_l3_interface
|
||||
* vyos_linkagg
|
||||
* vyos_lldp
|
||||
* vyos_lldp_interface
|
||||
* vyos_logging
|
||||
* vyos_static_route
|
||||
* vyos_user
|
||||
|
||||
#### Notification
|
||||
* bearychat
|
||||
* catapult
|
||||
* office_365_connector_card
|
||||
|
||||
#### Remote Management
|
||||
- hpe
|
||||
* oneview_fc_network
|
||||
- imc
|
||||
* imc_rest
|
||||
- manageiq
|
||||
* manageiq_user
|
||||
|
||||
#### Source Control
|
||||
* github_deploy_key
|
||||
* github_issue
|
||||
|
||||
#### Storage
|
||||
* nuage_vpsk
|
||||
- panos
|
||||
* panos_sag
|
||||
- purestorage
|
||||
* purefa_hg
|
||||
* purefa_host
|
||||
* purefa_pg
|
||||
* purefa_snap
|
||||
* purefa_volume
|
||||
|
||||
#### System
|
||||
* aix_lvol
|
||||
* awall
|
||||
* dconf
|
||||
* interfaces_file
|
||||
|
||||
#### Web Infrastructure
|
||||
* gunicorn
|
||||
* rundeck_acl_policy
|
||||
* rundeck_project
|
||||
|
||||
#### Windows
|
||||
* win_defrag
|
||||
* win_domain_group
|
||||
* win_domain_user
|
||||
* win_dsc
|
||||
* win_eventlog
|
||||
* win_eventlog_entry
|
||||
* win_firewall
|
||||
* win_group_membership
|
||||
* win_hotfix
|
||||
* win_mapped_drive
|
||||
* win_pagefile
|
||||
* win_power_plan
|
||||
* win_psmodule
|
||||
* win_rabbitmq_plugin
|
||||
* win_route
|
||||
* win_security_policy
|
||||
* win_toast
|
||||
* win_user_right
|
||||
* win_wait_for
|
||||
* win_wakeonlan
|
||||
|
||||
<a id="2.3.3"></a>
|
||||
|
||||
## 2.3.3 "Ramble On" - TBD
|
||||
|
||||
### Bugfixes
|
||||
* Security fix for CVE-2017-7550 the jenkins_plugin module was logging the jenkins
|
||||
server password if the url_password was passed via the params field:
|
||||
https://github.com/ansible/ansible/pull/30875
|
||||
* Fix alternatives module handlling of non existing options
|
||||
* Fix synchronize traceback with the docker connection plugin
|
||||
* Do not escape backslashes in the template lookup plugin to mirror what the template module does
|
||||
@ -656,8 +61,17 @@ Ansible Changes By Release
|
||||
* Fixed check mode for enable on Solaris for service module
|
||||
* Fix cloudtrail module to allow AWS profiles other than the default
|
||||
* Fix an encoding issue with secret (password) vars_prompts
|
||||
* Fix for Windows become to show the stdout and stderr strings on a failure
|
||||
* Fix the issue SSL verification can not be disabled for Tower modules
|
||||
* Use safe_load instead on load to read a yaml document
|
||||
* Fix for win_file to respect check mode when deleting directories
|
||||
* Include_role now complains about invalid arguments
|
||||
* Added socket conditions to ignore for wait_for, no need to error for closing already closed connection
|
||||
* Updated hostname module to work on newer RHEL7 releases
|
||||
* Security fix to avoid provider password leaking in logs for network modules
|
||||
|
||||
<a id="2.3.2"></a>
|
||||
* Python3 fixes for azure modules
|
||||
|
||||
## 2.3.2 "Ramble On" - 2017-08-04
|
||||
|
||||
@ -810,7 +224,6 @@ Moving to Ansible 2.3 guide http://docs.ansible.com/ansible/porting_guide_2.3.ht
|
||||
#### New: lookups
|
||||
|
||||
- keyring: allows getting password from the 'controller' system's keyrings
|
||||
- chef_databag: allows querying Chef Databags via pychef library
|
||||
|
||||
#### New: cache
|
||||
|
||||
@ -856,8 +269,6 @@ Moving to Ansible 2.3 guide http://docs.ansible.com/ansible/porting_guide_2.3.ht
|
||||
- bigswitch:
|
||||
* bigmon_chain
|
||||
* bigmon_policy
|
||||
- cisco
|
||||
* cisco_spark
|
||||
- cloudengine:
|
||||
* ce_command
|
||||
- cloudscale_server
|
||||
@ -1435,7 +846,7 @@ Notice given that the following will be removed in Ansible 2.4:
|
||||
* Security fix for CVE-2016-8628 - Command injection by compromised server via fact variables. In some situations, facts returned by modules could overwrite connection-based facts or some other special variables, leading to injected commands running on the Ansible controller as the user running Ansible (or via escalated permissions).
|
||||
* Security fix for CVE-2016-8614 - apt_key module not properly validating keys in some situations.
|
||||
|
||||
### Minor Changes:
|
||||
###Minor Changes:
|
||||
* The subversion module from core now marks its password parameter as no_log so
|
||||
the password is obscured when logging.
|
||||
* The postgresql_lang and postgresql_ext modules from extras now mark
|
||||
@ -1515,7 +926,7 @@ Module fixes:
|
||||
|
||||
## 2.1 "The Song Remains the Same"
|
||||
|
||||
### Major Changes:
|
||||
###Major Changes:
|
||||
|
||||
* Official support for the networking modules, originally available in 2.0 as a tech preview.
|
||||
* Refactored and expanded support for Docker with new modules and many improvements to existing modules, as well as a new Kubernetes module.
|
||||
@ -1673,12 +1084,12 @@ Module fixes:
|
||||
* issubset
|
||||
* issuperset
|
||||
|
||||
#### New Inventory scripts:
|
||||
####New Inventory scripts:
|
||||
* brook
|
||||
* rackhd
|
||||
* azure_rm
|
||||
|
||||
### Minor Changes:
|
||||
###Minor Changes:
|
||||
|
||||
* Added support for pipelining mode to more connection plugins, which helps prevent
|
||||
module data from being written to disk.
|
||||
@ -1693,7 +1104,7 @@ Module fixes:
|
||||
10-first-callback.py and 20-second-callback.py.
|
||||
* Added (alpha) Centirfy's dzdo as another become meethod (privilege escalation)
|
||||
|
||||
### Deprecations:
|
||||
###Deprecations:
|
||||
|
||||
* Deprecated the use of "bare" variables in loops (ie. `with_items: foo`, where `foo` is a variable).
|
||||
The full jinja2 variable syntax of `{{foo}}` should always be used instead. This warning will be removed
|
||||
@ -1794,7 +1205,7 @@ Module fixes:
|
||||
|
||||
## 2.0 "Over the Hills and Far Away" - Jan 12, 2016
|
||||
|
||||
### Major Changes:
|
||||
###Major Changes:
|
||||
|
||||
* Releases are now named after Led Zeppelin songs, 1.9 will be the last Van Halen named release.
|
||||
* The new block/rescue/always directives allow for making task blocks and exception-like semantics
|
||||
@ -1817,7 +1228,7 @@ Module fixes:
|
||||
This re-executes inventory scripts, but does not force them to ignore any cache they might use.
|
||||
* New delegate_facts directive, a boolean that allows you to apply facts to the delegated host (true/yes) instead of the inventory_hostname (no/false) which is the default and previous behaviour.
|
||||
* local connections now work with 'su' as a privilege escalation method
|
||||
* Ansible 2.0 has deprecated the "ssh" from ansible_ssh_user, ansible_ssh_host, and ansible_ssh_port to become ansible_user, ansible_host, and ansible_port.
|
||||
* Ansible 2.0 has deprecated the “ssh” from ansible_ssh_user, ansible_ssh_host, and ansible_ssh_port to become ansible_user, ansible_host, and ansible_port.
|
||||
* New ssh configuration variables (`ansible_ssh_common_args`, `ansible_ssh_extra_args`) can be used to configure a
|
||||
per-group or per-host ssh ProxyCommand or set any other ssh options.
|
||||
`ansible_ssh_extra_args` is used to set options that are accepted only by ssh (not sftp or scp, which have their own analogous settings).
|
||||
@ -1825,7 +1236,7 @@ Module fixes:
|
||||
* Backslashes used when specifying parameters in jinja2 expressions in YAML dicts sometimes needed to be escaped twice.
|
||||
This has been fixed so that escaping once works. Here's an example of how playbooks need to be modified:
|
||||
|
||||
```yaml
|
||||
```
|
||||
# Syntax in 1.9.x
|
||||
- debug:
|
||||
msg: "{{ 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') }}"
|
||||
@ -1843,7 +1254,7 @@ format the trailing newlines were kept. In v2, both methods of specifying the
|
||||
string will keep the trailing newlines. If you relied on the trailing
|
||||
newline being stripped you can change your playbook like this:
|
||||
|
||||
```yaml
|
||||
```
|
||||
# Syntax in 1.9.2
|
||||
vars:
|
||||
message: >
|
||||
@ -1870,7 +1281,7 @@ variable syntax ('{{var_name}}') - bare variable names there are no longer accep
|
||||
In fact, even specifying args with variables has been deprecated, and will not be
|
||||
allowed in future versions:
|
||||
|
||||
```yaml
|
||||
```
|
||||
---
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
@ -1885,13 +1296,13 @@ allowed in future versions:
|
||||
with_items: my_dirs
|
||||
```
|
||||
|
||||
### Plugins
|
||||
###Plugins
|
||||
|
||||
* Rewritten dnf module that should be faster and less prone to encountering bugs in cornercases
|
||||
* WinRM connection plugin passes all vars named `ansible_winrm_*` to the underlying pywinrm client. This allows, for instance, `ansible_winrm_server_cert_validation=ignore` to be used with newer versions of pywinrm to disable certificate validation on Python 2.7.9+.
|
||||
* WinRM connection plugin put_file is significantly faster and no longer has file size limitations.
|
||||
|
||||
#### Deprecated Modules (new ones in parens):
|
||||
####Deprecated Modules (new ones in parens):
|
||||
|
||||
* ec2_ami_search (ec2_ami_find)
|
||||
* quantum_network (os_network)
|
||||
@ -1902,7 +1313,7 @@ allowed in future versions:
|
||||
* quantum_router_gateway (os_router)
|
||||
* quantum_router_interface (os_router)
|
||||
|
||||
#### New Modules:
|
||||
####New Modules:
|
||||
|
||||
- amazon
|
||||
* ec2_ami_copy
|
||||
@ -2118,7 +1529,7 @@ allowed in future versions:
|
||||
* zabbix_screen
|
||||
- znode
|
||||
|
||||
#### New Inventory scripts:
|
||||
####New Inventory scripts:
|
||||
|
||||
* cloudstack
|
||||
* fleetctl
|
||||
@ -2129,27 +1540,27 @@ allowed in future versions:
|
||||
* rudder
|
||||
* serf
|
||||
|
||||
#### New Lookups:
|
||||
####New Lookups:
|
||||
|
||||
* credstash
|
||||
* hashi_vault
|
||||
* ini
|
||||
* shelvefile
|
||||
|
||||
#### New Filters:
|
||||
####New Filters:
|
||||
|
||||
* combine
|
||||
|
||||
#### New Connection:
|
||||
####New Connection:
|
||||
|
||||
* docker: for talking to docker containers on the ansible controller machine without using ssh.
|
||||
|
||||
#### New Callbacks:
|
||||
####New Callbacks:
|
||||
|
||||
* logentries: plugin to send play data to logentries service
|
||||
* skippy: same as default but does not display skip messages
|
||||
|
||||
### Minor changes:
|
||||
###Minor changes:
|
||||
|
||||
* Many more tests. The new API makes things more testable and we took advantage of it.
|
||||
* big_ip modules now support turning off ssl certificate validation (use only for self-signed certificates).
|
||||
@ -3291,7 +2702,7 @@ the variable is still registered for the host, with the attribute skipped: True.
|
||||
* added basename and dirname as Jinja2 filters available to all templates
|
||||
* pip works better when sudoing from unprivileged users
|
||||
* fix for user creation with groups specification reporting 'changed' incorrectly in some cases
|
||||
* fix for some unicode encoding errors in outputting some data in verbose mode
|
||||
* fix for some unicode encoding errors in outputing some data in verbose mode
|
||||
* improved FreeBSD, NetBSD and Solaris facts
|
||||
* debug module always outputs data without having to specify -v
|
||||
* fix for sysctl module creating new keys (must specify checks=none)
|
||||
@ -3665,7 +3076,7 @@ New Modules:
|
||||
* new LSB facts (release, distro, etc)
|
||||
* pause module -- (pause seconds=10) (pause minutes=1) (pause prompt=foo) -- it's an action plugin
|
||||
* a module for adding entries to the main crontab (though you may still wish to just drop template files into cron.d)
|
||||
* debug module can be used for outputting messages without using 'shell echo'
|
||||
* debug module can be used for outputing messages without using 'shell echo'
|
||||
* a fail module is now available for causing errors, you might want to use it with only_if to fail in certain conditions
|
||||
|
||||
Other module Changes, Upgrades, and Fixes:
|
||||
|
||||
@ -14,15 +14,15 @@ Language
|
||||
* While not all components of Ansible must be in Python, core contributions to the Ansible repo must be written in Python. This is to maximize the ability of everyone to contribute.
|
||||
* If you want to write non-Python ansible modules or inventory scripts, that's fine, but they are not going to get merged in most likely. Sorry!!
|
||||
|
||||
PEP 8 and basic style checks
|
||||
PEP8 and basic style checks
|
||||
===========================
|
||||
|
||||
* [PEP 8](https://www.python.org/dev/peps/pep-0008/) is a great Python style guide, which you should read.
|
||||
* PEP 8 must not be strictly followed in all aspects, but most of it is good advice
|
||||
* PEP8 is a great Python style guide, which you should read.
|
||||
* PEP8 must not be strictly followed in all aspects, but most of it is good advice
|
||||
* In particular, we don't really care about line lengths. Buy a bigger monitor!
|
||||
* To run checks for things we care about, use [ansible-test](http://docs.ansible.com/ansible/dev_guide/testing_pep8.html#running-locally).
|
||||
* To run checks for things we care about, run "make pep8"
|
||||
* Similarly, additional checks can be made with "make pyflakes"
|
||||
* There is no need to submit code changes for PEP 8 and pyflakes fixes, as these break attribution history. Project leadership will make these periodically.
|
||||
* There is no need to submit code changes for pep8 and pyflake fixes, as these break attribution history. Project leadership will make these periodically.
|
||||
* Do not submit pull requests that simply adjust whitespace in the code
|
||||
|
||||
Testing
|
||||
@ -101,22 +101,22 @@ Use the dict constructor where possible when allocating dictionaries:
|
||||
|
||||
# this:
|
||||
foo = dict(
|
||||
a=12,
|
||||
b=34,
|
||||
a = 12,
|
||||
b = 34
|
||||
)
|
||||
|
||||
Do not line up variables
|
||||
Line up variables
|
||||
|
||||
# not this
|
||||
a = 12
|
||||
foosball = 34
|
||||
xyz = 'dog'
|
||||
|
||||
# this
|
||||
a = 12
|
||||
foosball = 34
|
||||
xyz = 'dog'
|
||||
|
||||
# this
|
||||
a = 12
|
||||
foosball = 34
|
||||
xyz = 'dog'
|
||||
|
||||
Don't use line continuations:
|
||||
|
||||
# no
|
||||
@ -131,13 +131,13 @@ Don't use line continuations:
|
||||
Spacing:
|
||||
|
||||
# no
|
||||
x = [1,2,3]
|
||||
x=[1,2,3]
|
||||
|
||||
# no
|
||||
x = [ 1, 2, 3 ]
|
||||
x = [1,2,3]
|
||||
|
||||
# yes
|
||||
x = [1, 2, 3]
|
||||
x = [ 1, 2, 3 ]
|
||||
|
||||
Spacing continued:
|
||||
|
||||
@ -277,7 +277,7 @@ To test if something is a string, consider that it may be unicode.
|
||||
if type(x) == str:
|
||||
|
||||
# yes
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.compat.six import string_types
|
||||
if isinstance(x, string_types):
|
||||
|
||||
Cleverness
|
||||
@ -314,3 +314,7 @@ This was not meant to be a scary document, so we hope it wasn't, but we also hop
|
||||
If you have questions about this document, please ask on the ansible-devel mailing list.
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
prune ticket_stubs
|
||||
prune hacking
|
||||
include README.md COPYING
|
||||
include SYMLINK_CACHE.json
|
||||
include requirements.txt
|
||||
include .coveragerc
|
||||
include .yamllint
|
||||
include examples/hosts
|
||||
include examples/ansible.cfg
|
||||
recursive-include lib/ansible/module_utils/powershell *
|
||||
include lib/ansible/module_utils/powershell.ps1
|
||||
recursive-include lib/ansible/modules *
|
||||
recursive-include lib/ansible/galaxy/data *
|
||||
recursive-include docs *
|
||||
include hacking/module_formatter.py
|
||||
include hacking/dump_playbook_attributes.py
|
||||
recursive-include hacking/templates *
|
||||
recursive-include packaging *
|
||||
recursive-include test *
|
||||
include Makefile
|
||||
|
||||
@ -47,9 +47,9 @@ A detailed explanation of the PR workflow can be seen here: https://github.com/a
|
||||
|
||||
***
|
||||
|
||||
# Maintainers list
|
||||
# Extras maintainers list
|
||||
|
||||
The full list of maintainers for modules is located here: https://github.com/ansible/ansible/blob/devel/.github/BOTMETA.yml
|
||||
The full list of maintainers for modules is located here: https://github.com/ansible/ansibullbot
|
||||
|
||||
## Changing Maintainership
|
||||
|
||||
|
||||
62
Makefile
62
Makefile
@ -9,7 +9,7 @@
|
||||
# make deb-src -------------- produce a DEB source
|
||||
# make deb ------------------ produce a DEB
|
||||
# make docs ----------------- rebuild the manpages (results are checked in)
|
||||
# make tests ---------------- run the tests (see https://docs.ansible.com/ansible/dev_guide/testing_units.html for requirements)
|
||||
# make tests ---------------- run the tests (see test/README.md for requirements)
|
||||
# make pyflakes, make pep8 -- source code checks
|
||||
|
||||
########################################################
|
||||
@ -21,15 +21,13 @@ OS = $(shell uname -s)
|
||||
# Manpages are currently built with asciidoc -- would like to move to markdown
|
||||
# This doesn't evaluate until it's called. The -D argument is the
|
||||
# directory of the target file ($@), kinda like `dirname`.
|
||||
|
||||
MANPAGES ?= $(patsubst %.asciidoc.in,%,$(wildcard ./docs/man/man1/ansible*.1.asciidoc.in))
|
||||
MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 docs/man/man1/ansible-pull.1 docs/man/man1/ansible-doc.1 docs/man/man1/ansible-galaxy.1 docs/man/man1/ansible-vault.1 docs/man/man1/ansible-console.1
|
||||
ifneq ($(shell which a2x 2>/dev/null),)
|
||||
ASCII2MAN = a2x -L -D $(dir $@) -d manpage -f manpage $<
|
||||
ASCII2HTMLMAN = a2x -L -D docs/html/man/ -d manpage -f xhtml
|
||||
else
|
||||
ASCII2MAN = @echo "ERROR: AsciiDoc 'a2x' command is not installed but is required to build $(MANPAGES)" && exit 1
|
||||
endif
|
||||
GENERATE_CLI = docs/bin/generate_man.py
|
||||
|
||||
PYTHON=python
|
||||
SITELIB = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
|
||||
@ -118,26 +116,17 @@ TARGET ?=
|
||||
|
||||
########################################################
|
||||
|
||||
.PHONY: all
|
||||
all: clean python
|
||||
|
||||
.PHONY: tests
|
||||
tests:
|
||||
$(ANSIBLE_TEST) units -v --python $(PYTHON_VERSION) $(TEST_FLAGS)
|
||||
|
||||
.PHONY: tests-py3
|
||||
tests-py3:
|
||||
$(ANSIBLE_TEST) units -v --python $(PYTHON3_VERSION) $(TEST_FLAGS)
|
||||
|
||||
.PHONY: tests-nonet
|
||||
tests-nonet:
|
||||
$(ANSIBLE_TEST) units -v --python $(PYTHON_VERSION) $(TEST_FLAGS) --exclude test/units/modules/network/
|
||||
|
||||
.PHONY: integration
|
||||
integration:
|
||||
$(ANSIBLE_TEST) integration -v --docker $(IMAGE) $(TARGET) $(TEST_FLAGS)
|
||||
|
||||
.PHONY: authors
|
||||
authors:
|
||||
sh hacking/authors.sh
|
||||
|
||||
@ -151,19 +140,15 @@ authors:
|
||||
%.1: %.1.asciidoc VERSION
|
||||
$(ASCII2MAN)
|
||||
|
||||
.PHONY: loc
|
||||
loc:
|
||||
sloccount lib library bin
|
||||
|
||||
.PHONY: pep8
|
||||
pep8:
|
||||
$(ANSIBLE_TEST) sanity --test pep8 --python $(PYTHON_VERSION) $(TEST_FLAGS)
|
||||
|
||||
.PHONY: pyflakes
|
||||
pyflakes:
|
||||
pyflakes lib/ansible/*.py lib/ansible/*/*.py bin/*
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "Cleaning up distutils stuff"
|
||||
rm -rf build
|
||||
@ -179,15 +164,8 @@ clean:
|
||||
find ./docs/man -type f -name "*.xml" -delete
|
||||
find ./docs/man -type f -name "*.asciidoc" -delete
|
||||
find ./docs/man/man3 -type f -name "*.3" -delete
|
||||
rm -f ./docs/man/man1/*
|
||||
@echo "Cleaning up output from test runs"
|
||||
rm -rf test/test_data
|
||||
rm -rf shippable/
|
||||
rm -rf logs/
|
||||
rm -rf .cache/
|
||||
rm -f test/units/.coverage*
|
||||
rm -rf test/results/*/*
|
||||
find test/ -type f -name '*.retry' -delete
|
||||
@echo "Cleaning up RPM building stuff"
|
||||
rm -rf MANIFEST rpm-build
|
||||
@echo "Cleaning up Debian building stuff"
|
||||
@ -197,33 +175,30 @@ clean:
|
||||
rm -rf docs/js
|
||||
@echo "Cleaning up authors file"
|
||||
rm -f AUTHORS.TXT
|
||||
@echo "Cleaning up tests"
|
||||
rm -f test/units/.coverage*
|
||||
rm -f test/results/*/*
|
||||
@echo "Cleaning up docsite"
|
||||
$(MAKE) -C docs/docsite clean
|
||||
$(MAKE) -C docs/api clean
|
||||
|
||||
.PHONY: python
|
||||
python:
|
||||
$(PYTHON) setup.py build
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
$(PYTHON) setup.py install
|
||||
|
||||
.PHONY: sdist
|
||||
sdist: clean docs
|
||||
$(PYTHON) setup.py sdist
|
||||
|
||||
.PHONY: sdist_upload
|
||||
sdist_upload: clean docs
|
||||
$(PYTHON) setup.py sdist upload 2>&1 |tee upload.log
|
||||
|
||||
.PHONY: rpmcommon
|
||||
rpmcommon: sdist
|
||||
rpmcommon: $(MANPAGES) sdist
|
||||
@mkdir -p rpm-build
|
||||
@cp dist/*.gz rpm-build/
|
||||
@sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}$(REPOTAG)#' $(RPMSPEC) >rpm-build/$(NAME).spec
|
||||
|
||||
.PHONY: mock-srpm
|
||||
mock-srpm: /etc/mock/$(MOCK_CFG).cfg rpmcommon
|
||||
$(MOCK_BIN) -r $(MOCK_CFG) $(MOCK_ARGS) --resultdir rpm-build/ --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build/
|
||||
@echo "#############################################"
|
||||
@ -231,7 +206,6 @@ mock-srpm: /etc/mock/$(MOCK_CFG).cfg rpmcommon
|
||||
@echo rpm-build/*.src.rpm
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: mock-rpm
|
||||
mock-rpm: /etc/mock/$(MOCK_CFG).cfg mock-srpm
|
||||
$(MOCK_BIN) -r $(MOCK_CFG) $(MOCK_ARGS) --resultdir rpm-build/ --rebuild rpm-build/$(NAME)-*.src.rpm
|
||||
@echo "#############################################"
|
||||
@ -239,7 +213,6 @@ mock-rpm: /etc/mock/$(MOCK_CFG).cfg mock-srpm
|
||||
@echo rpm-build/*.noarch.rpm
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: srpm
|
||||
srpm: rpmcommon
|
||||
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
|
||||
--define "_builddir %{_topdir}" \
|
||||
@ -254,7 +227,6 @@ srpm: rpmcommon
|
||||
@echo " rpm-build/$(RPMNVR).src.rpm"
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: rpm
|
||||
rpm: rpmcommon
|
||||
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
|
||||
--define "_builddir %{_topdir}" \
|
||||
@ -271,7 +243,6 @@ rpm: rpmcommon
|
||||
@echo " rpm-build/$(RPMNVR).noarch.rpm"
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: debian
|
||||
debian: sdist
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
mkdir -p deb-build/$${DIST} ; \
|
||||
@ -280,7 +251,6 @@ debian: sdist
|
||||
sed -ie "s|%VERSION%|$(VERSION)|g;s|%RELEASE%|$(DEB_RELEASE)|;s|%DIST%|$${DIST}|g;s|%DATE%|$(DEB_DATE)|g" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
|
||||
done
|
||||
|
||||
.PHONY: deb
|
||||
deb: deb-src
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
PBUILDER_OPTS="$(PBUILDER_OPTS) --distribution $${DIST} --basetgz $(PBUILDER_CACHE_DIR)/$${DIST}-$(PBUILDER_ARCH)-base.tgz --buildresult $(CURDIR)/deb-build/$${DIST}" ; \
|
||||
@ -297,7 +267,6 @@ deb: deb-src
|
||||
|
||||
# Build package outside of pbuilder, with locally installed dependencies.
|
||||
# Install BuildRequires as noted in packaging/debian/control.
|
||||
.PHONY: local_deb
|
||||
local_deb: debian
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -b) ; \
|
||||
@ -309,7 +278,6 @@ local_deb: debian
|
||||
done
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: deb-src
|
||||
deb-src: debian
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -S) ; \
|
||||
@ -321,37 +289,23 @@ deb-src: debian
|
||||
done
|
||||
@echo "#############################################"
|
||||
|
||||
.PHONY: deb-upload
|
||||
deb-upload: deb
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \
|
||||
done
|
||||
|
||||
.PHONY: deb-src-upload
|
||||
deb-src-upload: deb-src
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_source.changes ; \
|
||||
done
|
||||
|
||||
.PHONY: epub
|
||||
epub:
|
||||
(cd docs/docsite/; CPUS=$(CPUS) make epub)
|
||||
|
||||
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
|
||||
.PHONY: webdocs
|
||||
|
||||
webdocs:
|
||||
(cd docs/docsite/; CPUS=$(CPUS) make docs)
|
||||
|
||||
.PHONY: generate_asciidoc
|
||||
generate_asciidoc: lib/ansible/cli/*.py
|
||||
mkdir -p ./docs/man/man1/ ; \
|
||||
PYTHONPATH=./lib $(GENERATE_CLI) --template-file=docs/templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py
|
||||
docs: $(MANPAGES)
|
||||
|
||||
|
||||
docs: generate_asciidoc
|
||||
make $(MANPAGES)
|
||||
|
||||
.PHONY: alldocs
|
||||
alldocs: docs webdocs
|
||||
|
||||
version:
|
||||
|
||||
14
README.md
14
README.md
@ -9,7 +9,11 @@ Ansible is a radically simple IT automation system. It handles configuration-ma
|
||||
|
||||
Read the documentation and more at https://ansible.com/
|
||||
|
||||
You can find installation instructions [here](https://docs.ansible.com/ansible/intro_installation.html) for a variety of platforms. Most users should probably install a released version of Ansible from `pip`, a package manager or our [release repository](https://releases.ansible.com/ansible/). [Officially supported](https://www.ansible.com/ansible-engine) builds of Ansible are also available. Some power users run directly from the development branch - while significant efforts are made to ensure that `devel` is reasonably stable, you're more likely to encounter breaking changes when running Ansible this way.
|
||||
Many users run straight from the development branch (it's generally fine to do so), but you might also wish to consume a release.
|
||||
|
||||
You can find instructions [here](https://docs.ansible.com/intro_getting_started.html) for a variety of platforms.
|
||||
|
||||
If you want to download a tarball of a release, go to [releases.ansible.com](https://releases.ansible.com/ansible), though most users use `yum` (using the EPEL instructions linked above), `apt` (using the PPA instructions linked above), or `pip install ansible`.
|
||||
|
||||
Design Principles
|
||||
=================
|
||||
@ -39,6 +43,7 @@ Branch Info
|
||||
|
||||
* Releases are named after Led Zeppelin songs. (Releases prior to 2.0 were named after Van Halen songs.)
|
||||
* The devel branch corresponds to the release actively under development.
|
||||
* For releases 1.8 - 2.2, modules are kept in different repos, you'll want to follow [core](https://github.com/ansible/ansible-modules-core) and [extras](https://github.com/ansible/ansible-modules-extras)
|
||||
* Various release-X.Y branches exist for previous releases.
|
||||
* We'd love to have your contributions, read [Community Information](https://docs.ansible.com/community.html) for notes on how to get started.
|
||||
|
||||
@ -49,8 +54,9 @@ Ansible was created by [Michael DeHaan](https://github.com/mpdehaan) (michael.de
|
||||
|
||||
Ansible is sponsored by [Ansible, Inc](https://ansible.com)
|
||||
|
||||
License
|
||||
Licence
|
||||
=======
|
||||
GNU General Public License v3.0
|
||||
GNU
|
||||
Click on the [Link](COPYING) to see the full text.
|
||||
|
||||
|
||||
See [COPYING](COPYING) to see the full text.
|
||||
|
||||
152
RELEASES.txt
152
RELEASES.txt
@ -4,80 +4,84 @@ Ansible Releases at a Glance
|
||||
VERSION RELEASE CODE NAME
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
2.4.0 TBD "Dancing Days"
|
||||
2.3.2 2017-08-04 "Ramble On"
|
||||
2.3.1 2017-06-01 "Ramble On"
|
||||
2.3.0 2017-04-12 "Ramble On"
|
||||
2.2.3 2017-05-09 "The Battle of Evermore"
|
||||
2.2.2 2017-03-27 "The Battle of Evermore"
|
||||
2.2.1 2017-01-16 "The Battle of Evermore"
|
||||
2.2.0 2016-11-01 "The Battle of Evermore"
|
||||
2.1.6 2017-06-01 "The Song Remains the Same"
|
||||
2.1.5 2017-03-27 "The Song Remains the Same"
|
||||
2.1.4 2017-01-16 "The Song Remains the Same"
|
||||
2.1.3 2016-11-04 "The Song Remains the Same"
|
||||
2.1.2 2016-09-29 "The Song Remains the Same"
|
||||
2.1.1 2016-07-28 "The Song Remains the Same"
|
||||
2.1.0 2016-05-25 "The Song Remains the Same"
|
||||
2.0.2 2016-04-19 "Over the Hills and Far Away"
|
||||
2.0.1 2016-02-24 "Over the Hills and Far Away"
|
||||
2.0.0 2016-01-12 "Over the Hills and Far Away"
|
||||
1.9.6 2016-04-15 "Dancing In the Streets"
|
||||
1.9.5 2016-03-21 "Dancing In the Streets"
|
||||
1.9.4 2015-10-09 "Dancing In the Streets"
|
||||
1.9.3 2015-09-03 "Dancing In the Streets"
|
||||
1.9.2 2015-06-24 "Dancing In the Streets"
|
||||
1.9.1 2015-04-27 "Dancing In the Streets"
|
||||
1.9.0 2015-03-25 "Dancing In the Streets"
|
||||
1.8.4 2015-02-19 "You Really Got Me"
|
||||
1.8.3 2015-02-17 "You Really Got Me"
|
||||
1.8.2 2014-12-04 "You Really Got Me"
|
||||
1.8.1 2014-11-26 "You Really Got Me"
|
||||
1.8.0 2014-11-25 "You Really Got Me"
|
||||
1.7.2 2014-09-24 "Summer Nights"
|
||||
1.7.1 2014-08-14 "Summer Nights"
|
||||
1.7.0 2014-08-06 "Summer Nights"
|
||||
1.6.10 2014-07-25 "The Cradle Will Rock"
|
||||
1.6.9 2014-07-24 "The Cradle Will Rock"
|
||||
1.6.8 2014-07-22 "The Cradle Will Rock"
|
||||
1.6.7 2014-07-21 "The Cradle Will Rock"
|
||||
1.6.6 2014-07-01 "The Cradle Will Rock"
|
||||
1.6.5 2014-06-25 "The Cradle Will Rock"
|
||||
1.6.4 2014-06-25 "The Cradle Will Rock"
|
||||
1.6.3 2014-06-09 "The Cradle Will Rock"
|
||||
1.6.2 2014-05-23 "The Cradle Will Rock"
|
||||
1.6.1 2014-05-07 "The Cradle Will Rock"
|
||||
1.6.0 2014-05-05 "The Cradle Will Rock"
|
||||
1.5.5 2014-04-18 "Love Walks In"
|
||||
1.5.4 2014-04-01 "Love Walks In"
|
||||
1.5.3 2014-03-13 "Love Walks In"
|
||||
1.5.2 2014-03-11 "Love Walks In"
|
||||
1.5.1 2014-03-10 "Love Walks In"
|
||||
1.5.0 2014-02-28 "Love Walks In"
|
||||
1.4.5 2014-02-12 "Could This Be Magic?"
|
||||
1.4.4 2014-01-06 "Could This Be Magic?"
|
||||
1.4.3 2013-12-20 "Could This Be Magic?"
|
||||
1.4.2 2013-12-18 "Could This Be Magic?"
|
||||
1.4.1 2013-11-27 "Could This Be Magic?"
|
||||
1.4.0 2013-11-21 "Could This Be Magic?"
|
||||
1.3.4 2013-10-29 "Top of the World"
|
||||
1.3.3 2013-10-09 "Top of the World"
|
||||
1.3.2 2013-09-19 "Top of the World"
|
||||
1.3.1 2013-09-16 "Top of the World"
|
||||
1.3.0 2013-09-13 "Top of the World"
|
||||
1.2.3 2013-08-21 "Right Now"
|
||||
1.2.2 2013-07-05 "Right Now"
|
||||
1.2.1 2013-07-04 "Right Now"
|
||||
1.2.0 2013-06-10 "Right Now"
|
||||
1.1.0 2013-04-02 "Mean Street"
|
||||
1.0.0 2013-02-01 "Eruption"
|
||||
0.9.0 2012-11-30 "Dreams"
|
||||
0.8.0 2012-10-19 "Cathedral"
|
||||
0.7.0 2012-09-06 "Panama"
|
||||
0.6.0 2012-08-06 "Cabo"
|
||||
0.5.0 2012-07-04 "Amsterdam"
|
||||
0.4.0 2012-05-23 "Unchained"
|
||||
0.3.0 2012-04-23 "Baluchitherium"
|
||||
2.5.0 TBD "Kashmir"
|
||||
2.4.2 11-29-2017 "Dancing Days"
|
||||
2.4.1 10-25-2017 "Dancing Days"
|
||||
2.4.0 09-19-2017 "Dancing Days"
|
||||
2.3.3 12-20-2017 "Ramble On"
|
||||
2.3.2 08-04-2017 "Ramble On"
|
||||
2.3.1 06-01-2017 "Ramble On"
|
||||
2.3.0 04-12-2017 "Ramble On"
|
||||
2.2.3 05-09-2017 "The Battle of Evermore"
|
||||
2.2.2 03-27-2017 "The Battle of Evermore"
|
||||
2.2.1 01-16-2017 "The Battle of Evermore"
|
||||
2.2.0 11-01-2016 "The Battle of Evermore"
|
||||
2.1.6 06-01-2017 "The Song Remains the Same"
|
||||
2.1.5 03-27-2017 "The Song Remains the Same"
|
||||
2.1.4 01-16-2017 "The Song Remains the Same"
|
||||
2.1.3 11-04-2016 "The Song Remains the Same"
|
||||
2.1.2 09-29-2016 "The Song Remains the Same"
|
||||
2.1.1 07-28-2016 "The Song Remains the Same"
|
||||
2.1.0 05-25-2016 "The Song Remains the Same"
|
||||
2.0.2 04-19-2016 "Over the Hills and Far Away"
|
||||
2.0.1 02-24-2016 "Over the Hills and Far Away"
|
||||
2.0.0 01-12-2016 "Over the Hills and Far Away"
|
||||
1.9.6 04-15-2016 "Dancing In the Streets"
|
||||
1.9.5 03-21-2016 "Dancing In the Streets"
|
||||
1.9.4 10-09-2015 "Dancing In the Streets"
|
||||
1.9.3 09-03-2015 "Dancing In the Streets"
|
||||
1.9.2 06-24-2015 "Dancing In the Streets"
|
||||
1.9.1 04-27-2015 "Dancing In the Streets"
|
||||
1.9.0 03-25-2015 "Dancing In the Streets"
|
||||
1.8.4 02-19-2015 "You Really Got Me"
|
||||
1.8.3 02-17-2015 "You Really Got Me"
|
||||
1.8.2 12-04-2014 "You Really Got Me"
|
||||
1.8.1 11-26-2014 "You Really Got Me"
|
||||
1.8.0 11-25-2014 "You Really Got Me"
|
||||
1.7.2 09-24-2014 "Summer Nights"
|
||||
1.7.1 08-14-2014 "Summer Nights"
|
||||
1.7.0 08-06-2014 "Summer Nights"
|
||||
1.6.10 07-25-2014 "The Cradle Will Rock"
|
||||
1.6.9 07-24-2014 "The Cradle Will Rock"
|
||||
1.6.8 07-22-2014 "The Cradle Will Rock"
|
||||
1.6.7 07-21-2014 "The Cradle Will Rock"
|
||||
1.6.6 07-01-2014 "The Cradle Will Rock"
|
||||
1.6.5 06-25-2014 "The Cradle Will Rock"
|
||||
1.6.4 06-25-2014 "The Cradle Will Rock"
|
||||
1.6.3 06-09-2014 "The Cradle Will Rock"
|
||||
1.6.2 05-23-2014 "The Cradle Will Rock"
|
||||
1.6.1 05-07-2014 "The Cradle Will Rock"
|
||||
1.6.0 05-05-2014 "The Cradle Will Rock"
|
||||
1.5.5 04-18-2014 "Love Walks In"
|
||||
1.5.4 04-01-2014 "Love Walks In"
|
||||
1.5.3 03-13-2014 "Love Walks In"
|
||||
1.5.2 03-11-2014 "Love Walks In"
|
||||
1.5.1 03-10-2014 "Love Walks In"
|
||||
1.5.0 02-28-2014 "Love Walks In"
|
||||
1.4.5 02-12-2014 "Could This Be Magic?"
|
||||
1.4.4 01-06-2014 "Could This Be Magic?"
|
||||
1.4.3 12-20-2013 "Could This Be Magic?"
|
||||
1.4.2 12-18-2013 "Could This Be Magic?"
|
||||
1.4.1 11-27-2013 "Could This Be Magic?"
|
||||
1.4.0 11-21-2013 "Could This Be Magic?"
|
||||
1.3.4 10-29-2013 "Top of the World"
|
||||
1.3.3 10-09-2013 "Top of the World"
|
||||
1.3.2 09-19-2013 "Top of the World"
|
||||
1.3.1 09-16-2013 "Top of the World"
|
||||
1.3.0 09-13-2013 "Top of the World"
|
||||
1.2.3 08-21-2013 "Right Now"
|
||||
1.2.2 07-05-2013 "Right Now"
|
||||
1.2.1 07-04-2013 "Right Now"
|
||||
1.2.0 06-10-2013 "Right Now"
|
||||
1.1.0 04-02-2013 "Mean Street"
|
||||
1.0.0 02-01-2013 "Eruption"
|
||||
0.9.0 11-30-2012 "Dreams"
|
||||
0.8.0 10-19-2012 "Cathedral"
|
||||
0.7.0 09-06-2012 "Panama"
|
||||
0.6.0 08-06-2012 "Cabo"
|
||||
0.5.0 07-04-2012 "Amsterdam"
|
||||
0.4.0 05-23-2012 "Unchained"
|
||||
0.3.0 04-23-2012 "Baluchitherium"
|
||||
0.2.0 ? "Untitled"
|
||||
0.1.0 ? "Untitled"
|
||||
0.0.2 ? "Untitled"
|
||||
|
||||
24
bin/ansible
24
bin/ansible
@ -37,30 +37,30 @@ import shutil
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# for debug
|
||||
from multiprocessing import Lock
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
|
||||
from ansible.utils.display import Display
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
########################################
|
||||
### OUTPUT OF LAST RESORT ###
|
||||
class LastResort(object):
|
||||
# OUTPUT OF LAST RESORT
|
||||
def display(self, msg, log_only=None):
|
||||
def display(self, msg):
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
def error(self, msg, wrap_text=None):
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
########################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
display = LastResort()
|
||||
|
||||
try: # bad ANSIBLE_CONFIG or config options can force ugly stacktrace
|
||||
import ansible.constants as C
|
||||
from ansible.utils.display import Display
|
||||
except AnsibleOptionsError as e:
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
sys.exit(5)
|
||||
|
||||
cli = None
|
||||
me = os.path.basename(sys.argv[0])
|
||||
|
||||
@ -71,7 +71,7 @@ if __name__ == '__main__':
|
||||
sub = None
|
||||
target = me.split('-')
|
||||
if target[-1][0].isdigit():
|
||||
# Remove any version or python version info as downstreams
|
||||
# Remove any version or pthon version info as downstreams
|
||||
# sometimes add that
|
||||
target = target[:-1]
|
||||
|
||||
@ -130,7 +130,7 @@ if __name__ == '__main__':
|
||||
exit_code = 99
|
||||
except Exception as e:
|
||||
have_cli_options = cli is not None and cli.options is not None
|
||||
display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
|
||||
display.error("Unexpected Exception: %s" % to_text(e), wrap_text=False)
|
||||
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
|
||||
log_only = False
|
||||
else:
|
||||
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2017, Ansible, Inc. <support@ansible.com>
|
||||
# (c) 2016, Ansible, Inc. <support@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
@ -33,19 +33,21 @@ import os
|
||||
import shlex
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import syslog
|
||||
import datetime
|
||||
import errno
|
||||
import logging
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.six import PY3
|
||||
from ansible.module_utils.six.moves import cPickle
|
||||
from ansible.module_utils.connection import send_data, recv_data
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.module_utils.six.moves import cPickle, StringIO
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.loader import connection_loader
|
||||
from ansible.plugins import connection_loader
|
||||
from ansible.utils.path import unfrackpath, makedirs_safe
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.utils.display import Display
|
||||
@ -60,9 +62,8 @@ def do_fork():
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
return pid
|
||||
# This is done as a 'good practice' for daemons, but we need to keep the cwd
|
||||
# leaving it here as a note that we KNOW its good practice but are not doing it on purpose.
|
||||
# os.chdir("/")
|
||||
|
||||
#os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
@ -72,11 +73,11 @@ def do_fork():
|
||||
sys.exit(0)
|
||||
|
||||
if C.DEFAULT_LOG_PATH != '':
|
||||
out_file = open(C.DEFAULT_LOG_PATH, 'ab+')
|
||||
err_file = open(C.DEFAULT_LOG_PATH, 'ab+', 0)
|
||||
out_file = file(C.DEFAULT_LOG_PATH, 'a+')
|
||||
err_file = file(C.DEFAULT_LOG_PATH, 'a+', 0)
|
||||
else:
|
||||
out_file = open('/dev/null', 'ab+')
|
||||
err_file = open('/dev/null', 'ab+', 0)
|
||||
out_file = file('/dev/null', 'a+')
|
||||
err_file = file('/dev/null', 'a+', 0)
|
||||
|
||||
os.dup2(out_file.fileno(), sys.stdout.fileno())
|
||||
os.dup2(err_file.fileno(), sys.stderr.fileno())
|
||||
@ -88,11 +89,33 @@ def do_fork():
|
||||
except OSError as e:
|
||||
sys.exit(1)
|
||||
|
||||
def send_data(s, data):
|
||||
packed_len = struct.pack('!Q',len(data))
|
||||
return s.sendall(packed_len + data)
|
||||
|
||||
def recv_data(s):
|
||||
header_len = 8 # size of a packed unsigned long long
|
||||
data = b""
|
||||
while len(data) < header_len:
|
||||
d = s.recv(header_len - len(data))
|
||||
if not d:
|
||||
return None
|
||||
data += d
|
||||
data_len = struct.unpack('!Q',data[:header_len])[0]
|
||||
data = data[header_len:]
|
||||
while len(data) < data_len:
|
||||
d = s.recv(data_len - len(data))
|
||||
if not d:
|
||||
return None
|
||||
data += d
|
||||
return data
|
||||
|
||||
|
||||
class Server():
|
||||
|
||||
def __init__(self, socket_path, play_context):
|
||||
self.socket_path = socket_path
|
||||
def __init__(self, path, play_context):
|
||||
|
||||
self.path = path
|
||||
self.play_context = play_context
|
||||
|
||||
display.display(
|
||||
@ -101,219 +124,178 @@ class Server():
|
||||
log_only=True
|
||||
)
|
||||
|
||||
display.display('control socket path is %s' % socket_path, log_only=True)
|
||||
display.display('control socket path is %s' % path, log_only=True)
|
||||
display.display('current working directory is %s' % os.getcwd(), log_only=True)
|
||||
|
||||
self._start_time = datetime.datetime.now()
|
||||
|
||||
display.display("using connection plugin %s" % self.play_context.connection, log_only=True)
|
||||
|
||||
self.connection = connection_loader.get(play_context.connection, play_context, sys.stdin)
|
||||
self.connection._connect()
|
||||
|
||||
if not self.connection.connected:
|
||||
self.conn = connection_loader.get(play_context.connection, play_context, sys.stdin)
|
||||
self.conn._connect()
|
||||
if not self.conn.connected:
|
||||
raise AnsibleConnectionFailure('unable to connect to remote host %s' % self._play_context.remote_addr)
|
||||
|
||||
connection_time = datetime.datetime.now() - self._start_time
|
||||
display.display('connection established to %s in %s' % (play_context.remote_addr, connection_time), log_only=True)
|
||||
|
||||
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.socket.bind(self.socket_path)
|
||||
self.socket.bind(path)
|
||||
self.socket.listen(1)
|
||||
display.display('local socket is set to listening', log_only=True)
|
||||
|
||||
signal.signal(signal.SIGALRM, self.alarm_handler)
|
||||
|
||||
def dispatch(self, obj, name, *args, **kwargs):
|
||||
meth = getattr(obj, name, None)
|
||||
if meth:
|
||||
return meth(*args, **kwargs)
|
||||
|
||||
def alarm_handler(self, signum, frame):
|
||||
'''
|
||||
Alarm handler
|
||||
'''
|
||||
# FIXME: this should also set internal flags for other
|
||||
# areas of code to check, so they can terminate
|
||||
# earlier than the socket going back to the accept
|
||||
# call and failing there.
|
||||
#
|
||||
# hooks the connection plugin to handle any cleanup
|
||||
self.dispatch(self.conn, 'alarm_handler', signum, frame)
|
||||
self.socket.close()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while True:
|
||||
signal.signal(signal.SIGALRM, self.connect_timeout)
|
||||
signal.signal(signal.SIGTERM, self.handler)
|
||||
# set the alarm, if we don't get an accept before it
|
||||
# goes off we exit (via an exception caused by the socket
|
||||
# getting closed while waiting on accept())
|
||||
# FIXME: is this the best way to exit? as noted above in the
|
||||
# handler we should probably be setting a flag to check
|
||||
# here and in other parts of the code
|
||||
signal.alarm(C.PERSISTENT_CONNECT_TIMEOUT)
|
||||
|
||||
(s, addr) = self.socket.accept()
|
||||
display.display('incoming request accepted on persistent socket', log_only=True)
|
||||
signal.alarm(0)
|
||||
try:
|
||||
(s, addr) = self.socket.accept()
|
||||
display.display('incoming request accepted on persistent socket', log_only=True)
|
||||
# clear the alarm
|
||||
# FIXME: potential race condition here between the accept and
|
||||
# time to this call.
|
||||
signal.alarm(0)
|
||||
except:
|
||||
break
|
||||
|
||||
while True:
|
||||
data = recv_data(s)
|
||||
if not data:
|
||||
break
|
||||
|
||||
signal.signal(signal.SIGALRM, self.command_timeout)
|
||||
signal.alarm(self.play_context.timeout)
|
||||
|
||||
op = to_text(data.split(b':')[0])
|
||||
display.display('socket operation is %s' % op, log_only=True)
|
||||
|
||||
method = getattr(self, 'do_%s' % op, None)
|
||||
|
||||
rc = 255
|
||||
stdout = stderr = ''
|
||||
try:
|
||||
if data.startswith(b'EXEC: '):
|
||||
display.display("socket operation is EXEC", log_only=True)
|
||||
cmd = data.split(b'EXEC: ')[1]
|
||||
(rc, stdout, stderr) = self.conn.exec_command(cmd)
|
||||
elif data.startswith(b'PUT: ') or data.startswith(b'FETCH: '):
|
||||
(op, src, dst) = shlex.split(to_native(data))
|
||||
stdout = stderr = ''
|
||||
try:
|
||||
if op == 'FETCH:':
|
||||
display.display("socket operation is FETCH", log_only=True)
|
||||
self.conn.fetch_file(src, dst)
|
||||
elif op == 'PUT:':
|
||||
display.display("socket operation is PUT", log_only=True)
|
||||
self.conn.put_file(src, dst)
|
||||
rc = 0
|
||||
except:
|
||||
pass
|
||||
elif data.startswith(b'CONTEXT: '):
|
||||
display.display("socket operation is CONTEXT", log_only=True)
|
||||
pc_data = data.split(b'CONTEXT: ')[1]
|
||||
|
||||
if not method:
|
||||
stderr = 'Invalid action specified'
|
||||
else:
|
||||
rc, stdout, stderr = method(data)
|
||||
src = StringIO(pc_data)
|
||||
pc_data = cPickle.load(src)
|
||||
src.close()
|
||||
|
||||
pc = PlayContext()
|
||||
pc.deserialize(pc_data)
|
||||
|
||||
self.dispatch(self.conn, 'update_play_context', pc)
|
||||
continue
|
||||
else:
|
||||
display.display("socket operation is UNKNOWN", log_only=True)
|
||||
stdout = ''
|
||||
stderr = 'Invalid action specified'
|
||||
except:
|
||||
stdout = ''
|
||||
stderr = traceback.format_exc()
|
||||
|
||||
signal.alarm(0)
|
||||
|
||||
display.display('socket operation completed with rc %s' % rc, log_only=True)
|
||||
display.display("socket operation completed with rc %s" % rc, log_only=True)
|
||||
|
||||
send_data(s, to_bytes(rc))
|
||||
send_data(s, to_bytes(str(rc)))
|
||||
send_data(s, to_bytes(stdout))
|
||||
send_data(s, to_bytes(stderr))
|
||||
|
||||
s.close()
|
||||
|
||||
except Exception as e:
|
||||
# socket.accept() will raise EINTR if the socket.close() is called
|
||||
if e.errno != errno.EINTR:
|
||||
display.display(traceback.format_exc(), log_only=True)
|
||||
|
||||
display.display(traceback.format_exec(), log_only=True)
|
||||
finally:
|
||||
# when done, close the connection properly and cleanup
|
||||
# the socket file so it can be recreated
|
||||
self.shutdown()
|
||||
end_time = datetime.datetime.now()
|
||||
delta = end_time - self._start_time
|
||||
display.display('shutdown local socket, connection was active for %s secs' % delta, log_only=True)
|
||||
|
||||
def connect_timeout(self, signum, frame):
|
||||
display.display('persistent connection idle timeout triggered, timeout value is %s secs' % C.PERSISTENT_CONNECT_TIMEOUT, log_only=True)
|
||||
self.shutdown()
|
||||
|
||||
def command_timeout(self, signum, frame):
|
||||
display.display('command timeout triggered, timeout value is %s secs' % self.play_context.timeout, log_only=True)
|
||||
self.shutdown()
|
||||
|
||||
def handler(self, signum, frame):
|
||||
display.display('signal handler called with signal %s' % signum, log_only=True)
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
display.display('shutdown persistent connection requested', log_only=True)
|
||||
|
||||
if not os.path.exists(self.socket_path):
|
||||
display.display('persistent connection is not active', log_only=True)
|
||||
return
|
||||
|
||||
try:
|
||||
if self.socket:
|
||||
display.display('closing local listener', log_only=True)
|
||||
display.display('shutting down control socket, connection was active for %s secs' % delta, log_only=True)
|
||||
try:
|
||||
self.conn.close()
|
||||
self.socket.close()
|
||||
if self.connection:
|
||||
display.display('closing the connection', log_only=True)
|
||||
self.connection.close()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
if os.path.exists(self.socket_path):
|
||||
display.display('removing the local control socket', log_only=True)
|
||||
os.remove(self.socket_path)
|
||||
|
||||
display.display('shutdown complete', log_only=True)
|
||||
|
||||
def do_EXEC(self, data):
|
||||
cmd = data.split(b'EXEC: ')[1]
|
||||
display.display('Command executed: %s' % cmd, log_only=True)
|
||||
return self.connection.exec_command(cmd)
|
||||
|
||||
def do_PUT(self, data):
|
||||
(op, src, dst) = shlex.split(to_native(data))
|
||||
return self.connection.fetch_file(src, dst)
|
||||
|
||||
def do_FETCH(self, data):
|
||||
(op, src, dst) = shlex.split(to_native(data))
|
||||
return self.connection.put_file(src, dst)
|
||||
|
||||
def do_CONTEXT(self, data):
|
||||
pc_data = data.split(b'CONTEXT: ', 1)[1]
|
||||
|
||||
if PY3:
|
||||
pc_data = cPickle.loads(pc_data, encoding='bytes')
|
||||
else:
|
||||
pc_data = cPickle.loads(pc_data)
|
||||
|
||||
pc = PlayContext()
|
||||
pc.deserialize(pc_data)
|
||||
|
||||
try:
|
||||
self.connection.update_play_context(pc)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return (0, 'ok', '')
|
||||
|
||||
def do_RUN(self, data):
|
||||
timeout = self.play_context.timeout
|
||||
while bool(timeout):
|
||||
if os.path.exists(self.socket_path):
|
||||
break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
socket_bytes = to_bytes(self.socket_path, errors='surrogate_or_strict')
|
||||
return 0, b'\n#SOCKET_PATH#: %s\n' % socket_bytes, ''
|
||||
|
||||
|
||||
def communicate(sock, data):
|
||||
send_data(sock, data)
|
||||
rc = int(recv_data(sock), 10)
|
||||
stdout = recv_data(sock)
|
||||
stderr = recv_data(sock)
|
||||
return (rc, stdout, stderr)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
os.remove(self.path)
|
||||
|
||||
def main():
|
||||
# Need stdin as a byte stream
|
||||
if PY3:
|
||||
stdin = sys.stdin.buffer
|
||||
else:
|
||||
stdin = sys.stdin
|
||||
|
||||
try:
|
||||
# read the play context data via stdin, which means depickling it
|
||||
# FIXME: as noted above, we will probably need to deserialize the
|
||||
# connection loader here as well at some point, otherwise this
|
||||
# won't find role- or playbook-based connection plugins
|
||||
cur_line = stdin.readline()
|
||||
init_data = b''
|
||||
while cur_line.strip() != b'#END_INIT#':
|
||||
if cur_line == b'':
|
||||
raise Exception("EOF found before init data was complete")
|
||||
cur_line = sys.stdin.readline()
|
||||
init_data = ''
|
||||
while cur_line.strip() != '#END_INIT#':
|
||||
if cur_line == '':
|
||||
raise Exception("EOL found before init data was complete")
|
||||
init_data += cur_line
|
||||
cur_line = stdin.readline()
|
||||
if PY3:
|
||||
pc_data = cPickle.loads(init_data, encoding='bytes')
|
||||
else:
|
||||
pc_data = cPickle.loads(init_data)
|
||||
cur_line = sys.stdin.readline()
|
||||
src = BytesIO(to_bytes(init_data))
|
||||
pc_data = cPickle.load(src)
|
||||
|
||||
pc = PlayContext()
|
||||
pc.deserialize(pc_data)
|
||||
|
||||
except Exception as e:
|
||||
# FIXME: better error message/handling/logging
|
||||
sys.stderr.write(traceback.format_exc())
|
||||
sys.exit("FAIL: %s" % e)
|
||||
|
||||
ssh = connection_loader.get('ssh', class_only=True)
|
||||
cp = ssh._create_control_path(pc.remote_addr, pc.port, pc.remote_user, pc.connection)
|
||||
m = ssh._create_control_path(pc.remote_addr, pc.port, pc.remote_user)
|
||||
|
||||
# create the persistent connection dir if need be and create the paths
|
||||
# which we will be using later
|
||||
tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR)
|
||||
tmp_path = unfrackpath("$HOME/.ansible/pc")
|
||||
makedirs_safe(tmp_path)
|
||||
lock_path = unfrackpath("%s/.ansible_pc_lock" % tmp_path)
|
||||
socket_path = unfrackpath(cp % dict(directory=tmp_path))
|
||||
lk_path = unfrackpath("%s/.ansible_pc_lock" % tmp_path)
|
||||
sf_path = unfrackpath(m % dict(directory=tmp_path))
|
||||
|
||||
# if the socket file doesn't exist, spin up the daemon process
|
||||
lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
|
||||
lock_fd = os.open(lk_path, os.O_RDWR|os.O_CREAT, 0o600)
|
||||
fcntl.lockf(lock_fd, fcntl.LOCK_EX)
|
||||
|
||||
if not os.path.exists(socket_path):
|
||||
if not os.path.exists(sf_path):
|
||||
pid = do_fork()
|
||||
if pid == 0:
|
||||
rc = 0
|
||||
try:
|
||||
server = Server(socket_path, pc)
|
||||
server = Server(sf_path, pc)
|
||||
except AnsibleConnectionFailure as exc:
|
||||
display.display('connecting to host %s returned an error' % pc.remote_addr, log_only=True)
|
||||
display.display(str(exc), log_only=True)
|
||||
@ -329,57 +311,51 @@ def main():
|
||||
sys.exit(rc)
|
||||
else:
|
||||
display.display('re-using existing socket for %s@%s:%s' % (pc.remote_user, pc.remote_addr, pc.port), log_only=True)
|
||||
|
||||
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
||||
os.close(lock_fd)
|
||||
|
||||
timeout = pc.timeout
|
||||
while bool(timeout):
|
||||
if os.path.exists(socket_path):
|
||||
display.vvvv('connected to local socket in %s' % (pc.timeout - timeout), pc.remote_addr)
|
||||
break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
else:
|
||||
raise AnsibleConnectionFailure('timeout waiting for local socket', pc.remote_addr)
|
||||
|
||||
# now connect to the daemon process
|
||||
# FIXME: if the socket file existed but the daemonized process was killed,
|
||||
# the connection will timeout here. Need to make this more resilient.
|
||||
while True:
|
||||
data = stdin.readline()
|
||||
if data == b'':
|
||||
rc = 0
|
||||
while rc == 0:
|
||||
data = sys.stdin.readline()
|
||||
if data == '':
|
||||
break
|
||||
if data.strip() == b'':
|
||||
if data.strip() == '':
|
||||
continue
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
|
||||
connect_retry_timeout = C.PERSISTENT_CONNECT_RETRY_TIMEOUT
|
||||
while bool(connect_retry_timeout):
|
||||
sf = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
attempts = 1
|
||||
while True:
|
||||
try:
|
||||
sock.connect(socket_path)
|
||||
sf.connect(sf_path)
|
||||
break
|
||||
except socket.error:
|
||||
time.sleep(1)
|
||||
connect_retry_timeout -= 1
|
||||
else:
|
||||
display.display('connect retry timeout expired, unable to connect to control socket', pc.remote_addr, pc.remote_user, log_only=True)
|
||||
display.display('persistent_connect_retry_timeout is %s secs' % (C.PERSISTENT_CONNECT_RETRY_TIMEOUT), pc.remote_addr, pc.remote_user, log_only=True)
|
||||
sys.stderr.write('failed to connect to control socket')
|
||||
sys.exit(255)
|
||||
# FIXME: better error handling/logging/message here
|
||||
time.sleep(C.PERSISTENT_CONNECT_INTERVAL)
|
||||
attempts += 1
|
||||
if attempts > C.PERSISTENT_CONNECT_RETRIES:
|
||||
display.display('number of connection attempts exceeded, unable to connect to control socket', pc.remote_addr, pc.remote_user, log_only=True)
|
||||
display.display('persistent_connect_interval=%s, persistent_connect_retries=%s' % (C.PERSISTENT_CONNECT_INTERVAL, C.PERSISTENT_CONNECT_RETRIES), pc.remote_addr, pc.remote_user, log_only=True)
|
||||
sys.stderr.write('failed to connect to control socket')
|
||||
sys.exit(255)
|
||||
|
||||
# send the play_context back into the connection so the connection
|
||||
# can handle any privilege escalation activities
|
||||
pc_data = b'CONTEXT: %s' % init_data
|
||||
communicate(sock, pc_data)
|
||||
pc_data = 'CONTEXT: %s' % src.getvalue()
|
||||
send_data(sf, to_bytes(pc_data))
|
||||
src.close()
|
||||
|
||||
rc, stdout, stderr = communicate(sock, data.strip())
|
||||
send_data(sf, to_bytes(data.strip()))
|
||||
|
||||
rc = int(recv_data(sf), 10)
|
||||
stdout = recv_data(sf)
|
||||
stderr = recv_data(sf)
|
||||
|
||||
sys.stdout.write(to_native(stdout))
|
||||
sys.stderr.write(to_native(stderr))
|
||||
|
||||
sock.close()
|
||||
sf.close()
|
||||
break
|
||||
|
||||
sys.exit(rc)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
@ -1,26 +1,16 @@
|
||||
contrib
|
||||
-------
|
||||
Files here provide an extension mechanism for Ansible similar to plugins. They are not maintained by the Ansible core team or installed with Ansible.
|
||||
|
||||
|
||||
inventory
|
||||
=========
|
||||
|
||||
Before 2.4 introduced inventory plugins, inventory scripts were the only way to provide sources that were not built into Ansible. Inventory scripts allow you to store your hosts, groups, and variables in any way you like.
|
||||
Inventory scripts allow you to store your hosts, groups, and variables in any way
|
||||
you like. Examples include discovering inventory from EC2 or pulling it from
|
||||
Cobbler. These could also be used to interface with LDAP or database.
|
||||
|
||||
Starting with Ansible version 2.4, they are enabled via the 'script' inventory plugin.
|
||||
Examples of use include discovering inventory from EC2 or pulling it from Cobbler. These could also be used to interface with LDAP or the database.
|
||||
|
||||
`chmod +x` an inventory plugin and either name it `/etc/ansible/hosts` or use `ansible -i /path/to/inventory/script`. You might also need to copy a configuration file with the same name and/or set environment variables. The scripts or configuration files can provide more details.
|
||||
|
||||
vault
|
||||
=====
|
||||
|
||||
If the file passed to `--vault-password-file` has the executable bit set, Ansible will execute it and use the stdout of that execution as 'the secret'.
|
||||
Vault scripts provided here use this facility to retrieve the vault secret from a number of sources.
|
||||
`chmod +x` an inventory plugin and either name it `/etc/ansible/hosts` or use `ansible -i /path/to/inventory/script`. You might also need to copy a configuration
|
||||
file with the same name and/or set environment variables, the scripts or configuration
|
||||
files have more details.
|
||||
|
||||
contributions welcome
|
||||
=====================
|
||||
|
||||
Send in pull requests to add scripts of your own. The sky is the limit!
|
||||
Send in pull requests to add plugins of your own. The sky is the limit!
|
||||
|
||||
|
||||
@ -53,68 +53,64 @@ except ImportError:
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
def api_get(link, config):
|
||||
try:
|
||||
if link is None:
|
||||
url = config.get('api', 'uri') + config.get('api', 'login_path')
|
||||
headers = {"Accept": config.get('api', 'login_type')}
|
||||
url = config.get('api','uri') + config.get('api','login_path')
|
||||
headers = {"Accept": config.get('api','login_type')}
|
||||
else:
|
||||
url = link['href'] + '?limit=0'
|
||||
headers = {"Accept": link['type']}
|
||||
result = open_url(url, headers=headers, url_username=config.get('auth', 'apiuser').replace('\n', ''),
|
||||
url_password=config.get('auth', 'apipass').replace('\n', ''))
|
||||
result = open_url(url, headers=headers, url_username=config.get('auth','apiuser').replace('\n', ''),
|
||||
url_password=config.get('auth','apipass').replace('\n', ''))
|
||||
return json.loads(result.read())
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def save_cache(data, config):
|
||||
''' saves item to cache '''
|
||||
dpath = config.get('cache', 'cache_dir')
|
||||
dpath = config.get('cache','cache_dir')
|
||||
try:
|
||||
cache = open('/'.join([dpath, 'inventory']), 'w')
|
||||
cache = open('/'.join([dpath,'inventory']), 'w')
|
||||
cache.write(json.dumps(data))
|
||||
cache.close()
|
||||
except IOError as e:
|
||||
pass # not really sure what to do here
|
||||
pass # not really sure what to do here
|
||||
|
||||
|
||||
def get_cache(cache_item, config):
|
||||
''' returns cached item '''
|
||||
dpath = config.get('cache', 'cache_dir')
|
||||
dpath = config.get('cache','cache_dir')
|
||||
inv = {}
|
||||
try:
|
||||
cache = open('/'.join([dpath, 'inventory']), 'r')
|
||||
cache = open('/'.join([dpath,'inventory']), 'r')
|
||||
inv = cache.read()
|
||||
cache.close()
|
||||
except IOError as e:
|
||||
pass # not really sure what to do here
|
||||
pass # not really sure what to do here
|
||||
|
||||
return inv
|
||||
|
||||
|
||||
def cache_available(config):
|
||||
''' checks if we have a 'fresh' cache available for item requested '''
|
||||
|
||||
if config.has_option('cache', 'cache_dir'):
|
||||
dpath = config.get('cache', 'cache_dir')
|
||||
if config.has_option('cache','cache_dir'):
|
||||
dpath = config.get('cache','cache_dir')
|
||||
|
||||
try:
|
||||
existing = os.stat('/'.join([dpath, 'inventory']))
|
||||
existing = os.stat( '/'.join([dpath,'inventory']))
|
||||
except:
|
||||
# cache doesn't exist or isn't accessible
|
||||
return False
|
||||
|
||||
if config.has_option('cache', 'cache_max_age'):
|
||||
maxage = config.get('cache', 'cache_max_age')
|
||||
if (int(time.time()) - int(existing.st_mtime)) <= int(maxage):
|
||||
if ((int(time.time()) - int(existing.st_mtime)) <= int(maxage)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def generate_inv_from_api(enterprise_entity, config):
|
||||
def generate_inv_from_api(enterprise_entity,config):
|
||||
try:
|
||||
inventory['all'] = {}
|
||||
inventory['all']['children'] = []
|
||||
@ -122,22 +118,22 @@ def generate_inv_from_api(enterprise_entity, config):
|
||||
inventory['_meta'] = {}
|
||||
inventory['_meta']['hostvars'] = {}
|
||||
|
||||
enterprise = api_get(enterprise_entity, config)
|
||||
vms_entity = next(link for link in enterprise['links'] if link['rel'] == 'virtualmachines')
|
||||
vms = api_get(vms_entity, config)
|
||||
enterprise = api_get(enterprise_entity,config)
|
||||
vms_entity = next(link for link in (enterprise['links']) if (link['rel']=='virtualmachines'))
|
||||
vms = api_get(vms_entity,config)
|
||||
for vmcollection in vms['collection']:
|
||||
for link in vmcollection['links']:
|
||||
if link['rel'] == 'virtualappliance':
|
||||
vm_vapp = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
|
||||
vm_vapp = link['title'].replace('[','').replace(']','').replace(' ','_')
|
||||
elif link['rel'] == 'virtualdatacenter':
|
||||
vm_vdc = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
|
||||
vm_vdc = link['title'].replace('[','').replace(']','').replace(' ','_')
|
||||
elif link['rel'] == 'virtualmachinetemplate':
|
||||
vm_template = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
|
||||
vm_template = link['title'].replace('[','').replace(']','').replace(' ','_')
|
||||
|
||||
# From abiquo.ini: Only adding to inventory VMs with public IP
|
||||
if config.getboolean('defaults', 'public_ip_only') is True:
|
||||
for link in vmcollection['links']:
|
||||
if link['type'] == 'application/vnd.abiquo.publicip+json' and link['rel'] == 'ip':
|
||||
if (link['type']=='application/vnd.abiquo.publicip+json' and link['rel']=='ip'):
|
||||
vm_nic = link['title']
|
||||
break
|
||||
else:
|
||||
@ -170,10 +166,10 @@ def generate_inv_from_api(enterprise_entity, config):
|
||||
inventory[vm_template]['children'] = []
|
||||
inventory[vm_template]['hosts'] = []
|
||||
if config.getboolean('defaults', 'get_metadata') is True:
|
||||
meta_entity = next(link for link in vmcollection['links'] if link['rel'] == 'metadata')
|
||||
meta_entity = next(link for link in (vmcollection['links']) if (link['rel']=='metadata'))
|
||||
try:
|
||||
metadata = api_get(meta_entity, config)
|
||||
if (config.getfloat("api", "version") >= 3.0):
|
||||
metadata = api_get(meta_entity,config)
|
||||
if (config.getfloat("api","version") >= 3.0):
|
||||
vm_metadata = metadata['metadata']
|
||||
else:
|
||||
vm_metadata = metadata['metadata']['metadata']
|
||||
@ -191,8 +187,7 @@ def generate_inv_from_api(enterprise_entity, config):
|
||||
return inventory
|
||||
except Exception as e:
|
||||
# Return empty hosts output
|
||||
return {'all': {'hosts': []}, '_meta': {'hostvars': {}}}
|
||||
|
||||
return { 'all': {'hosts': []}, '_meta': { 'hostvars': {} } }
|
||||
|
||||
def get_inventory(enterprise, config):
|
||||
''' Reads the inventory from cache or Abiquo api '''
|
||||
@ -202,12 +197,11 @@ def get_inventory(enterprise, config):
|
||||
else:
|
||||
default_group = os.path.basename(sys.argv[0]).rstrip('.py')
|
||||
# MAKE ABIQUO API CALLS #
|
||||
inv = generate_inv_from_api(enterprise, config)
|
||||
inv = generate_inv_from_api(enterprise,config)
|
||||
|
||||
save_cache(inv, config)
|
||||
return json.dumps(inv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
inventory = {}
|
||||
enterprise = {}
|
||||
@ -220,8 +214,8 @@ if __name__ == '__main__':
|
||||
break
|
||||
|
||||
try:
|
||||
login = api_get(None, config)
|
||||
enterprise = next(link for link in login['links'] if link['rel'] == 'enterprise')
|
||||
login = api_get(None,config)
|
||||
enterprise = next(link for link in (login['links']) if (link['rel']=='enterprise'))
|
||||
except Exception as e:
|
||||
enterprise = None
|
||||
|
||||
|
||||
@ -82,6 +82,7 @@ class LibcloudInventory(object):
|
||||
|
||||
print(data_to_print)
|
||||
|
||||
|
||||
def is_cache_valid(self):
|
||||
''' Determines if the cache files have expired, or if it is still valid '''
|
||||
|
||||
@ -94,6 +95,7 @@ class LibcloudInventory(object):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
''' Reads the settings from the libcloud.ini file '''
|
||||
|
||||
@ -106,17 +108,17 @@ class LibcloudInventory(object):
|
||||
raise ValueError('libcloud.ini file must contain a [driver] section')
|
||||
|
||||
if config.has_option('driver', 'provider'):
|
||||
self.provider = config.get('driver', 'provider')
|
||||
self.provider = config.get('driver','provider')
|
||||
else:
|
||||
raise ValueError('libcloud.ini does not have a provider defined')
|
||||
|
||||
if config.has_option('driver', 'key'):
|
||||
self.key = config.get('driver', 'key')
|
||||
self.key = config.get('driver','key')
|
||||
else:
|
||||
raise ValueError('libcloud.ini does not have a key defined')
|
||||
|
||||
if config.has_option('driver', 'secret'):
|
||||
self.secret = config.get('driver', 'secret')
|
||||
self.secret = config.get('driver','secret')
|
||||
else:
|
||||
raise ValueError('libcloud.ini does not have a secret defined')
|
||||
|
||||
@ -144,6 +146,7 @@ class LibcloudInventory(object):
|
||||
self.cache_path_index = cache_path + "/ansible-libcloud.index"
|
||||
self.cache_max_age = config.getint('cache', 'cache_max_age')
|
||||
|
||||
|
||||
def parse_cli_args(self):
|
||||
'''
|
||||
Command line argument processing
|
||||
@ -151,13 +154,14 @@ class LibcloudInventory(object):
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on libcloud supported providers')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific instance')
|
||||
help='Get all the variables about a specific instance')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to libcloud supported providers (default: False - use cache files)')
|
||||
help='Force refresh of cache by making API requests to libcloud supported providers (default: False - use cache files)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
|
||||
def do_api_calls_update_cache(self):
|
||||
'''
|
||||
Do API calls to a location, and save data in cache files
|
||||
@ -168,6 +172,7 @@ class LibcloudInventory(object):
|
||||
self.write_to_cache(self.inventory, self.cache_path_cache)
|
||||
self.write_to_cache(self.index, self.cache_path_index)
|
||||
|
||||
|
||||
def get_nodes(self):
|
||||
'''
|
||||
Gets the list of all nodes
|
||||
@ -176,6 +181,7 @@ class LibcloudInventory(object):
|
||||
for node in self.conn.list_nodes():
|
||||
self.add_node(node)
|
||||
|
||||
|
||||
def get_node(self, node_id):
|
||||
'''
|
||||
Gets details about a specific node
|
||||
@ -183,6 +189,7 @@ class LibcloudInventory(object):
|
||||
|
||||
return [node for node in self.conn.list_nodes() if node.id == node_id][0]
|
||||
|
||||
|
||||
def add_node(self, node):
|
||||
'''
|
||||
Adds a node to the inventory and index, as long as it is
|
||||
@ -237,10 +244,10 @@ class LibcloudInventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_index_from_cache()
|
||||
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# try updating the cache
|
||||
self.do_api_calls_update_cache()
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# host migh not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
@ -248,7 +255,8 @@ class LibcloudInventory(object):
|
||||
|
||||
node = self.get_node(node_id)
|
||||
instance_vars = {}
|
||||
for key, value in vars(node).items():
|
||||
for key in vars(instance):
|
||||
value = getattr(instance, key)
|
||||
key = self.to_safe('ec2_' + key)
|
||||
|
||||
# Handle complex types
|
||||
@ -275,12 +283,13 @@ class LibcloudInventory(object):
|
||||
else:
|
||||
pass
|
||||
# TODO Product codes if someone finds them useful
|
||||
# print(key)
|
||||
# print(type(value))
|
||||
# print(value)
|
||||
#print(key)
|
||||
#print(type(value))
|
||||
#print(value)
|
||||
|
||||
return self.json_format_dict(instance_vars, True)
|
||||
|
||||
|
||||
def push(self, my_dict, key, element):
|
||||
'''
|
||||
Pushed an element onto an array that may not have been defined in
|
||||
@ -292,6 +301,7 @@ class LibcloudInventory(object):
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
|
||||
def get_inventory_from_cache(self):
|
||||
'''
|
||||
Reads the inventory from the cache file and returns it as a JSON
|
||||
@ -302,6 +312,7 @@ class LibcloudInventory(object):
|
||||
json_inventory = cache.read()
|
||||
return json_inventory
|
||||
|
||||
|
||||
def load_index_from_cache(self):
|
||||
'''
|
||||
Reads the index from the cache file sets self.index
|
||||
@ -311,6 +322,7 @@ class LibcloudInventory(object):
|
||||
json_index = cache.read()
|
||||
self.index = json.loads(json_index)
|
||||
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
'''
|
||||
Writes data in JSON format to a file
|
||||
@ -321,6 +333,7 @@ class LibcloudInventory(object):
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
|
||||
def to_safe(self, word):
|
||||
'''
|
||||
Converts 'bad' characters in a string to underscores so they can be
|
||||
@ -329,6 +342,7 @@ class LibcloudInventory(object):
|
||||
|
||||
return re.sub("[^A-Za-z0-9\-]", "_", word)
|
||||
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
'''
|
||||
Converts a dict to a JSON object and dumps it as a formatted
|
||||
@ -340,7 +354,6 @@ class LibcloudInventory(object):
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
def main():
|
||||
LibcloudInventory()
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ of hosts, groups the hosts are in, and even variables to assign to each host.
|
||||
|
||||
To use this:
|
||||
- copy this file over /etc/ansible/hosts and chmod +x the file.
|
||||
- Copy both files (.py and .ini) in your preferred directory
|
||||
- Copy both files (.py and .ini) in your prefered directory
|
||||
|
||||
More information about Ansible Dynamic Inventory here
|
||||
http://unix.stackexchange.com/questions/205479/in-ansible-dynamic-inventory-json-can-i-render-hostvars-based-on-the-hostname
|
||||
@ -49,25 +49,23 @@ This script has been inspired by the cobbler.py inventory. thanks
|
||||
Author: Damien Garros (@dgarros)
|
||||
Version: 0.2.0
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
import argparse
|
||||
HAS_ARGPARSE = True
|
||||
except ImportError:
|
||||
HAS_ARGPARSE = False
|
||||
from ansible.compat.six.moves import configparser
|
||||
|
||||
try:
|
||||
from apstra.aosom.session import Session
|
||||
|
||||
HAS_AOS_PYEZ = True
|
||||
except ImportError:
|
||||
HAS_AOS_PYEZ = False
|
||||
|
||||
from ansible.module_utils.six.moves import configparser
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
"""
|
||||
##
|
||||
@ -280,12 +278,10 @@ Expected output format in Device mode
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def fail(msg):
|
||||
sys.stderr.write("%s\n" % msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class AosInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -294,8 +290,6 @@ class AosInventory(object):
|
||||
|
||||
if not HAS_AOS_PYEZ:
|
||||
raise Exception('aos-pyez is not installed. Please see details here: https://github.com/Apstra/aos-pyez')
|
||||
if not HAS_ARGPARSE:
|
||||
raise Exception('argparse is not installed. Please install the argparse library or upgrade to python-2.7')
|
||||
|
||||
# Initialize inventory
|
||||
self.inventory = dict() # A list of groups and the hosts in that group
|
||||
@ -309,10 +303,10 @@ class AosInventory(object):
|
||||
# ----------------------------------------------------
|
||||
# Open session to AOS
|
||||
# ----------------------------------------------------
|
||||
aos = Session(server=self.aos_server,
|
||||
port=self.aos_server_port,
|
||||
user=self.aos_username,
|
||||
passwd=self.aos_password)
|
||||
aos = Session( server=self.aos_server,
|
||||
port=self.aos_server_port,
|
||||
user=self.aos_username,
|
||||
passwd=self.aos_password)
|
||||
|
||||
aos.login()
|
||||
|
||||
@ -320,10 +314,10 @@ class AosInventory(object):
|
||||
self.add_var_to_group('all', 'aos_session', aos.session)
|
||||
|
||||
# Add the AOS server itself in the inventory
|
||||
self.add_host_to_group("all", 'aos')
|
||||
self.add_var_to_host("aos", "ansible_ssh_host", self.aos_server)
|
||||
self.add_var_to_host("aos", "ansible_ssh_pass", self.aos_password)
|
||||
self.add_var_to_host("aos", "ansible_ssh_user", self.aos_username)
|
||||
self.add_host_to_group("all", 'aos' )
|
||||
self.add_var_to_host("aos", "ansible_ssh_host", self.aos_server )
|
||||
self.add_var_to_host("aos", "ansible_ssh_pass", self.aos_password )
|
||||
self.add_var_to_host("aos", "ansible_ssh_user", self.aos_username )
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Build the inventory
|
||||
@ -342,7 +336,7 @@ class AosInventory(object):
|
||||
for dev_name, dev_id in bp.params['devices'].value.items():
|
||||
|
||||
self.add_host_to_group('all', dev_name)
|
||||
device = aos.Devices.find(uid=dev_id)
|
||||
device = aos.Devices.find( uid=dev_id)
|
||||
|
||||
if 'facts' in device.value.keys():
|
||||
self.add_device_facts_to_var(dev_name, device)
|
||||
@ -350,7 +344,7 @@ class AosInventory(object):
|
||||
# Define admin State and Status
|
||||
if 'user_config' in device.value.keys():
|
||||
if 'admin_state' in device.value['user_config'].keys():
|
||||
self.add_var_to_host(dev_name, 'admin_state', device.value['user_config']['admin_state'])
|
||||
self.add_var_to_host(dev_name, 'admin_state', device.value['user_config']['admin_state'] )
|
||||
|
||||
self.add_device_status_to_var(dev_name, device)
|
||||
|
||||
@ -466,30 +460,35 @@ class AosInventory(object):
|
||||
except:
|
||||
if 'AOS_SERVER' in os.environ.keys():
|
||||
self.aos_server = os.environ['AOS_SERVER']
|
||||
pass
|
||||
|
||||
try:
|
||||
self.aos_server_port = config.get('aos', 'port')
|
||||
except:
|
||||
if 'AOS_PORT' in os.environ.keys():
|
||||
self.aos_server_port = os.environ['AOS_PORT']
|
||||
pass
|
||||
|
||||
try:
|
||||
self.aos_username = config.get('aos', 'username')
|
||||
except:
|
||||
if 'AOS_USERNAME' in os.environ.keys():
|
||||
self.aos_username = os.environ['AOS_USERNAME']
|
||||
pass
|
||||
|
||||
try:
|
||||
self.aos_password = config.get('aos', 'password')
|
||||
except:
|
||||
if 'AOS_PASSWORD' in os.environ.keys():
|
||||
self.aos_password = os.environ['AOS_PASSWORD']
|
||||
pass
|
||||
|
||||
try:
|
||||
self.aos_blueprint = config.get('aos', 'blueprint')
|
||||
except:
|
||||
if 'AOS_BLUEPRINT' in os.environ.keys():
|
||||
self.aos_blueprint = os.environ['AOS_BLUEPRINT']
|
||||
pass
|
||||
|
||||
try:
|
||||
if config.get('aos', 'blueprint_interface') in ['false', 'no']:
|
||||
@ -497,6 +496,7 @@ class AosInventory(object):
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def parse_cli_args(self):
|
||||
""" Command line argument processing """
|
||||
|
||||
@ -554,7 +554,7 @@ class AosInventory(object):
|
||||
'ansible_ssh_host',
|
||||
device.value['facts']['mgmt_ipaddr'])
|
||||
|
||||
self.add_var_to_host(device_name, 'id', device.id)
|
||||
self.add_var_to_host(device_name,'id', device.id)
|
||||
|
||||
# self.add_host_to_group('all', device.name)
|
||||
for key, value in device.value['facts'].items():
|
||||
|
||||
@ -49,7 +49,6 @@ Command line arguments:
|
||||
- tenant
|
||||
- ad_user
|
||||
- password
|
||||
- cloud_environment
|
||||
|
||||
Environment variables:
|
||||
- AZURE_PROFILE
|
||||
@ -59,7 +58,6 @@ Environment variables:
|
||||
- AZURE_TENANT
|
||||
- AZURE_AD_USER
|
||||
- AZURE_PASSWORD
|
||||
- AZURE_CLOUD_ENVIRONMENT
|
||||
|
||||
Run for Specific Host
|
||||
-----------------------
|
||||
@ -192,27 +190,22 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
import traceback
|
||||
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from os.path import expanduser
|
||||
import ansible.module_utils.six.moves.urllib.parse as urlparse
|
||||
|
||||
HAS_AZURE = True
|
||||
HAS_AZURE_EXC = None
|
||||
|
||||
try:
|
||||
from msrestazure.azure_exceptions import CloudError
|
||||
from msrestazure import azure_cloud
|
||||
from azure.mgmt.compute import __version__ as azure_compute_version
|
||||
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
||||
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
||||
from azure.mgmt.network import NetworkManagementClient
|
||||
from azure.mgmt.resource.resources import ResourceManagementClient
|
||||
from azure.mgmt.compute import ComputeManagementClient
|
||||
from azure.mgmt.network.network_management_client import NetworkManagementClient
|
||||
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient
|
||||
from azure.mgmt.compute.compute_management_client import ComputeManagementClient
|
||||
except ImportError as exc:
|
||||
HAS_AZURE_EXC = exc
|
||||
HAS_AZURE = False
|
||||
@ -225,8 +218,7 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict(
|
||||
secret='AZURE_SECRET',
|
||||
tenant='AZURE_TENANT',
|
||||
ad_user='AZURE_AD_USER',
|
||||
password='AZURE_PASSWORD',
|
||||
cloud_environment='AZURE_CLOUD_ENVIRONMENT',
|
||||
password='AZURE_PASSWORD'
|
||||
)
|
||||
|
||||
AZURE_CONFIG_SETTINGS = dict(
|
||||
@ -240,7 +232,7 @@ AZURE_CONFIG_SETTINGS = dict(
|
||||
group_by_tag='AZURE_GROUP_BY_TAG'
|
||||
)
|
||||
|
||||
AZURE_MIN_VERSION = "2.0.0"
|
||||
AZURE_MIN_VERSION = "0.30.0rc5"
|
||||
|
||||
|
||||
def azure_id_to_dict(id):
|
||||
@ -257,7 +249,6 @@ class AzureRM(object):
|
||||
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self._cloud_environment = None
|
||||
self._compute_client = None
|
||||
self._resource_client = None
|
||||
self._network_client = None
|
||||
@ -271,26 +262,6 @@ class AzureRM(object):
|
||||
self.fail("Failed to get credentials. Either pass as parameters, set environment variables, "
|
||||
"or define a profile in ~/.azure/credentials.")
|
||||
|
||||
# if cloud_environment specified, look up/build Cloud object
|
||||
raw_cloud_env = self.credentials.get('cloud_environment')
|
||||
if not raw_cloud_env:
|
||||
self._cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD # SDK default
|
||||
else:
|
||||
# try to look up "well-known" values via the name attribute on azure_cloud members
|
||||
all_clouds = [x[1] for x in inspect.getmembers(azure_cloud) if isinstance(x[1], azure_cloud.Cloud)]
|
||||
matched_clouds = [x for x in all_clouds if x.name == raw_cloud_env]
|
||||
if len(matched_clouds) == 1:
|
||||
self._cloud_environment = matched_clouds[0]
|
||||
elif len(matched_clouds) > 1:
|
||||
self.fail("Azure SDK failure: more than one cloud matched for cloud_environment name '{0}'".format(raw_cloud_env))
|
||||
else:
|
||||
if not urlparse.urlparse(raw_cloud_env).scheme:
|
||||
self.fail("cloud_environment must be an endpoint discovery URL or one of {0}".format([x.name for x in all_clouds]))
|
||||
try:
|
||||
self._cloud_environment = azure_cloud.get_cloud_from_metadata_endpoint(raw_cloud_env)
|
||||
except Exception as e:
|
||||
self.fail("cloud_environment {0} could not be resolved: {1}".format(raw_cloud_env, e.message))
|
||||
|
||||
if self.credentials.get('subscription_id', None) is None:
|
||||
self.fail("Credentials did not include a subscription_id value.")
|
||||
self.log("setting subscription_id")
|
||||
@ -301,23 +272,16 @@ class AzureRM(object):
|
||||
self.credentials.get('tenant') is not None:
|
||||
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
|
||||
secret=self.credentials['secret'],
|
||||
tenant=self.credentials['tenant'],
|
||||
cloud_environment=self._cloud_environment)
|
||||
tenant=self.credentials['tenant'])
|
||||
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
||||
tenant = self.credentials.get('tenant')
|
||||
if not tenant:
|
||||
tenant = 'common'
|
||||
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'],
|
||||
self.credentials['password'],
|
||||
tenant=tenant,
|
||||
cloud_environment=self._cloud_environment)
|
||||
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'], self.credentials['password'])
|
||||
else:
|
||||
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
|
||||
"Credentials must include client_id, secret and tenant or ad_user and password.")
|
||||
|
||||
def log(self, msg):
|
||||
if self.debug:
|
||||
print(msg + u'\n')
|
||||
print (msg + u'\n')
|
||||
|
||||
def fail(self, msg):
|
||||
raise Exception(msg)
|
||||
@ -377,10 +341,6 @@ class AzureRM(object):
|
||||
self.log('Received credentials from parameters.')
|
||||
return arg_credentials
|
||||
|
||||
if arg_credentials['ad_user'] is not None:
|
||||
self.log('Received credentials from parameters.')
|
||||
return arg_credentials
|
||||
|
||||
# try environment
|
||||
env_credentials = self._get_env_credentials()
|
||||
if env_credentials:
|
||||
@ -412,12 +372,7 @@ class AzureRM(object):
|
||||
def network_client(self):
|
||||
self.log('Getting network client')
|
||||
if not self._network_client:
|
||||
self._network_client = NetworkManagementClient(
|
||||
self.azure_credentials,
|
||||
self.subscription_id,
|
||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||
api_version='2017-06-01'
|
||||
)
|
||||
self._network_client = NetworkManagementClient(self.azure_credentials, self.subscription_id)
|
||||
self._register('Microsoft.Network')
|
||||
return self._network_client
|
||||
|
||||
@ -425,24 +380,14 @@ class AzureRM(object):
|
||||
def rm_client(self):
|
||||
self.log('Getting resource manager client')
|
||||
if not self._resource_client:
|
||||
self._resource_client = ResourceManagementClient(
|
||||
self.azure_credentials,
|
||||
self.subscription_id,
|
||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||
api_version='2017-05-10'
|
||||
)
|
||||
self._resource_client = ResourceManagementClient(self.azure_credentials, self.subscription_id)
|
||||
return self._resource_client
|
||||
|
||||
@property
|
||||
def compute_client(self):
|
||||
self.log('Getting compute client')
|
||||
if not self._compute_client:
|
||||
self._compute_client = ComputeManagementClient(
|
||||
self.azure_credentials,
|
||||
self.subscription_id,
|
||||
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||
api_version='2017-03-30'
|
||||
)
|
||||
self._compute_client = ComputeManagementClient(self.azure_credentials, self.subscription_id)
|
||||
self._register('Microsoft.Compute')
|
||||
return self._compute_client
|
||||
|
||||
@ -495,7 +440,7 @@ class AzureInventory(object):
|
||||
self.include_powerstate = False
|
||||
|
||||
self.get_inventory()
|
||||
print(self._json_format_dict(pretty=self._args.pretty))
|
||||
print (self._json_format_dict(pretty=self._args.pretty))
|
||||
sys.exit(0)
|
||||
|
||||
def _parse_cli_args(self):
|
||||
@ -503,13 +448,13 @@ class AzureInventory(object):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce an Ansible Inventory file for an Azure subscription')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--debug', action='store_true', default=False,
|
||||
help='Send debug messages to STDOUT')
|
||||
help='Send debug messages to STDOUT')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all information about an instance')
|
||||
help='Get all information about an instance')
|
||||
parser.add_argument('--pretty', action='store_true', default=False,
|
||||
help='Pretty print JSON output(default: False)')
|
||||
help='Pretty print JSON output(default: False)')
|
||||
parser.add_argument('--profile', action='store',
|
||||
help='Azure profile contained in ~/.azure/credentials')
|
||||
parser.add_argument('--subscription_id', action='store',
|
||||
@ -520,12 +465,10 @@ class AzureInventory(object):
|
||||
help='Azure Client Secret')
|
||||
parser.add_argument('--tenant', action='store',
|
||||
help='Azure Tenant Id')
|
||||
parser.add_argument('--ad_user', action='store',
|
||||
parser.add_argument('--ad-user', action='store',
|
||||
help='Active Directory User')
|
||||
parser.add_argument('--password', action='store',
|
||||
help='password')
|
||||
parser.add_argument('--cloud_environment', action='store',
|
||||
help='Azure Cloud Environment name or metadata discovery URL')
|
||||
parser.add_argument('--resource-groups', action='store',
|
||||
help='Return inventory for comma separated list of resource group names')
|
||||
parser.add_argument('--tags', action='store',
|
||||
@ -543,7 +486,8 @@ class AzureInventory(object):
|
||||
try:
|
||||
virtual_machines = self._compute_client.virtual_machines.list(resource_group)
|
||||
except Exception as exc:
|
||||
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc)))
|
||||
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group,
|
||||
str(exc)))
|
||||
if self._args.host or self.tags:
|
||||
selected_machines = self._selected_machines(virtual_machines)
|
||||
self._load_machines(selected_machines)
|
||||
@ -566,7 +510,7 @@ class AzureInventory(object):
|
||||
for machine in machines:
|
||||
id_dict = azure_id_to_dict(machine.id)
|
||||
|
||||
# TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets
|
||||
#TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets
|
||||
# fixed, we should remove the .lower(). Opened Issue
|
||||
# #574: https://github.com/Azure/azure-sdk-for-python/issues/574
|
||||
resource_group = id_dict['resourceGroups'].lower()
|
||||
@ -594,7 +538,7 @@ class AzureInventory(object):
|
||||
mac_address=None,
|
||||
plan=(machine.plan.name if machine.plan else None),
|
||||
virtual_machine_size=machine.hardware_profile.vm_size,
|
||||
computer_name=(machine.os_profile.computer_name if machine.os_profile else None),
|
||||
computer_name=machine.os_profile.computer_name,
|
||||
provisioning_state=machine.provisioning_state,
|
||||
)
|
||||
|
||||
@ -615,7 +559,7 @@ class AzureInventory(object):
|
||||
)
|
||||
|
||||
# Add windows details
|
||||
if machine.os_profile is not None and machine.os_profile.windows_configuration is not None:
|
||||
if machine.os_profile.windows_configuration is not None:
|
||||
host_vars['windows_auto_updates_enabled'] = \
|
||||
machine.os_profile.windows_configuration.enable_automatic_updates
|
||||
host_vars['windows_timezone'] = machine.os_profile.windows_configuration.time_zone
|
||||
@ -846,10 +790,13 @@ class AzureInventory(object):
|
||||
|
||||
def main():
|
||||
if not HAS_AZURE:
|
||||
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>={0}' --upgrade`) - {1}".format(AZURE_MIN_VERSION, HAS_AZURE_EXC))
|
||||
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>=2.0.0rc5' --upgrade`) - {0}".format(HAS_AZURE_EXC))
|
||||
|
||||
if Version(azure_compute_version) < Version(AZURE_MIN_VERSION):
|
||||
sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} "
|
||||
"Do you have Azure >= 2.0.0rc5 installed? (try `pip install 'azure>=2.0.0rc5' --upgrade`)".format(AZURE_MIN_VERSION, azure_compute_version))
|
||||
|
||||
AzureInventory()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -32,13 +32,11 @@ included, provided the requesting user belongs to it.
|
||||
|
||||
The following variables are established for every host. They can be retrieved from the hostvars
|
||||
dictionary.
|
||||
- brook_pid: str
|
||||
- brook_name: str
|
||||
- brook_description: str
|
||||
- brook_project: str
|
||||
- brook_template: str
|
||||
- brook_region: str
|
||||
- brook_zone: str
|
||||
- brook_status: str
|
||||
- brook_tags: list(str)
|
||||
- brook_internal_ips: list(str)
|
||||
@ -72,7 +70,7 @@ Support:
|
||||
This script is tested on Python 2.7 and 3.4. It may work on other versions though.
|
||||
|
||||
Author: Francisco Ros <fjros@doalitic.com>
|
||||
Version: 0.2
|
||||
Version: 0.1
|
||||
"""
|
||||
|
||||
|
||||
@ -124,8 +122,9 @@ class BrookInventory:
|
||||
self.project_id = config.get('brook', 'project_id')
|
||||
|
||||
if not self.api_token:
|
||||
sys.exit('You must provide (at least) your Brook.io API token to generate the dynamic '
|
||||
'inventory.')
|
||||
print('You must provide (at least) your Brook.io API token to generate the dynamic '
|
||||
'inventory.')
|
||||
sys.exit(1)
|
||||
|
||||
def get_api_client(self):
|
||||
"""Authenticate user via the provided credentials and return the corresponding API client.
|
||||
@ -169,8 +168,8 @@ class BrookInventory:
|
||||
for project_id in projects:
|
||||
project = projects_api.show_project(project_id=project_id)
|
||||
for instance in instances_api.index_instances(project_id=project_id):
|
||||
# Get template used for this instance if known
|
||||
template = templates_api.show_template(template_id=instance.template) if instance.template else None
|
||||
# Get template used for this instance
|
||||
template = templates_api.show_template(template_id=instance.template)
|
||||
|
||||
# Update hostvars
|
||||
try:
|
||||
@ -213,13 +212,11 @@ class BrookInventory:
|
||||
"""
|
||||
|
||||
hostvars = instance.to_dict()
|
||||
hostvars['brook_pid'] = hostvars.pop('pid')
|
||||
hostvars['brook_name'] = hostvars.pop('name')
|
||||
hostvars['brook_description'] = hostvars.pop('description')
|
||||
hostvars['brook_project'] = hostvars.pop('project')
|
||||
hostvars['brook_template'] = hostvars.pop('template')
|
||||
hostvars['brook_region'] = hostvars.pop('region')
|
||||
hostvars['brook_zone'] = hostvars.pop('zone')
|
||||
hostvars['brook_created_at'] = hostvars.pop('created_at')
|
||||
hostvars['brook_updated_at'] = hostvars.pop('updated_at')
|
||||
del hostvars['id']
|
||||
@ -230,7 +227,7 @@ class BrookInventory:
|
||||
# Substitute identifiers for names
|
||||
#
|
||||
hostvars['brook_project'] = project.name
|
||||
hostvars['brook_template'] = template.name if template else None
|
||||
hostvars['brook_template'] = template.name
|
||||
|
||||
# Retrieve instance state
|
||||
#
|
||||
|
||||
@ -110,9 +110,9 @@ class CobblerInventory(object):
|
||||
if self.args.host:
|
||||
data_to_print += self.get_host_info()
|
||||
else:
|
||||
self.inventory['_meta'] = {'hostvars': {}}
|
||||
self.inventory['_meta'] = { 'hostvars': {} }
|
||||
for hostname in self.cache:
|
||||
self.inventory['_meta']['hostvars'][hostname] = {'cobbler': self.cache[hostname]}
|
||||
self.inventory['_meta']['hostvars'][hostname] = {'cobbler': self.cache[hostname] }
|
||||
data_to_print += self.json_format_dict(self.inventory, True)
|
||||
|
||||
print(data_to_print)
|
||||
@ -179,7 +179,7 @@ class CobblerInventory(object):
|
||||
|
||||
for host in data:
|
||||
# Get the FQDN for the host and add it to the right groups
|
||||
dns_name = host['hostname'] # None
|
||||
dns_name = host['hostname'] #None
|
||||
ksmeta = None
|
||||
interfaces = host['interfaces']
|
||||
# hostname is often empty for non-static IP hosts
|
||||
@ -190,7 +190,7 @@ class CobblerInventory(object):
|
||||
if this_dns_name is not None and this_dns_name is not "":
|
||||
dns_name = this_dns_name
|
||||
|
||||
if dns_name == '' or dns_name is None:
|
||||
if dns_name == '':
|
||||
continue
|
||||
|
||||
status = host['status']
|
||||
@ -229,11 +229,11 @@ class CobblerInventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_cache_from_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
if not self.args.host in self.cache:
|
||||
# try updating the cache
|
||||
self.update_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
if not self.args.host in self.cache:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ import re
|
||||
import sys
|
||||
from time import time
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
try:
|
||||
import json
|
||||
@ -81,11 +82,9 @@ except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from six import iteritems
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
class CollinsDefaults(object):
|
||||
ASSETS_API_ENDPOINT = '%s/api/assets'
|
||||
SPECIAL_ATTRIBUTES = set([
|
||||
@ -118,7 +117,7 @@ class CollinsInventory(object):
|
||||
self.parse_cli_args()
|
||||
|
||||
logging.basicConfig(format=CollinsDefaults.LOG_FORMAT,
|
||||
filename=self.log_location)
|
||||
filename=self.log_location)
|
||||
self.log = logging.getLogger('CollinsInventory')
|
||||
|
||||
def _asset_get_attribute(self, asset, attrib):
|
||||
@ -169,14 +168,14 @@ class CollinsInventory(object):
|
||||
print(data_to_print)
|
||||
return successful
|
||||
|
||||
def find_assets(self, attributes=None, operation='AND'):
|
||||
def find_assets(self, attributes = {}, operation = 'AND'):
|
||||
""" Obtains Collins assets matching the provided attributes. """
|
||||
attributes = {} if attributes is None else attributes
|
||||
|
||||
# Formats asset search query to locate assets matching attributes, using
|
||||
# the CQL search feature as described here:
|
||||
# http://tumblr.github.io/collins/recipes.html
|
||||
attributes_query = ['='.join(attr_pair) for attr_pair in iteritems(attributes)]
|
||||
attributes_query = [ '='.join(attr_pair)
|
||||
for attr_pair in iteritems(attributes) ]
|
||||
query_parameters = {
|
||||
'details': ['True'],
|
||||
'operation': [operation],
|
||||
@ -191,18 +190,19 @@ class CollinsInventory(object):
|
||||
# Locates all assets matching the provided query, exhausting pagination.
|
||||
while True:
|
||||
if num_retries == self.collins_max_retries:
|
||||
raise MaxRetriesError("Maximum of %s retries reached; giving up" % self.collins_max_retries)
|
||||
raise MaxRetriesError("Maximum of %s retries reached; giving up" % \
|
||||
self.collins_max_retries)
|
||||
query_parameters['page'] = cur_page
|
||||
query_url = "%s?%s" % (
|
||||
(CollinsDefaults.ASSETS_API_ENDPOINT % self.collins_host),
|
||||
urlencode(query_parameters, doseq=True)
|
||||
urllib.urlencode(query_parameters, doseq=True)
|
||||
)
|
||||
try:
|
||||
response = open_url(query_url,
|
||||
timeout=self.collins_timeout_secs,
|
||||
url_username=self.collins_username,
|
||||
url_password=self.collins_password,
|
||||
force_basic_auth=True)
|
||||
timeout=self.collins_timeout_secs,
|
||||
url_username=self.collins_username,
|
||||
url_password=self.collins_password,
|
||||
force_basic_auth=True)
|
||||
json_response = json.loads(response.read())
|
||||
# Adds any assets found to the array of assets.
|
||||
assets += json_response['data']['Data']
|
||||
@ -212,7 +212,8 @@ class CollinsInventory(object):
|
||||
cur_page += 1
|
||||
num_retries = 0
|
||||
except:
|
||||
self.log.error("Error while communicating with Collins, retrying:\n%s", traceback.format_exc())
|
||||
self.log.error("Error while communicating with Collins, retrying:\n%s",
|
||||
traceback.format_exc())
|
||||
num_retries += 1
|
||||
return assets
|
||||
|
||||
@ -231,15 +232,19 @@ class CollinsInventory(object):
|
||||
def read_settings(self):
|
||||
""" Reads the settings from the collins.ini file """
|
||||
|
||||
config_loc = os.getenv('COLLINS_CONFIG', os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
|
||||
config_loc = os.getenv('COLLINS_CONFIG',
|
||||
os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
|
||||
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
|
||||
|
||||
self.collins_host = config.get('collins', 'host')
|
||||
self.collins_username = os.getenv('COLLINS_USERNAME', config.get('collins', 'username'))
|
||||
self.collins_password = os.getenv('COLLINS_PASSWORD', config.get('collins', 'password'))
|
||||
self.collins_asset_type = os.getenv('COLLINS_ASSET_TYPE', config.get('collins', 'asset_type'))
|
||||
self.collins_username = os.getenv('COLLINS_USERNAME',
|
||||
config.get('collins', 'username'))
|
||||
self.collins_password = os.getenv('COLLINS_PASSWORD',
|
||||
config.get('collins', 'password'))
|
||||
self.collins_asset_type = os.getenv('COLLINS_ASSET_TYPE',
|
||||
config.get('collins', 'asset_type'))
|
||||
self.collins_timeout_secs = config.getint('collins', 'timeout_secs')
|
||||
self.collins_max_retries = config.getint('collins', 'max_retries')
|
||||
|
||||
@ -263,12 +268,16 @@ class CollinsInventory(object):
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produces an Ansible Inventory file based on Collins')
|
||||
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to Collins '
|
||||
'(default: False - use cache files)')
|
||||
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty print all JSON output')
|
||||
parser.add_argument('--list',
|
||||
action='store_true', default=True, help='List instances (default: True)')
|
||||
parser.add_argument('--host',
|
||||
action='store', help='Get all the variables about a specific instance')
|
||||
parser.add_argument('--refresh-cache',
|
||||
action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to Collins ' \
|
||||
'(default: False - use cache files)')
|
||||
parser.add_argument('--pretty',
|
||||
action='store_true', default=False, help='Pretty print all JSON output')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def update_cache(self):
|
||||
@ -281,7 +290,8 @@ class CollinsInventory(object):
|
||||
try:
|
||||
server_assets = self.find_assets()
|
||||
except:
|
||||
self.log.error("Error while locating assets from Collins:\n%s", traceback.format_exc())
|
||||
self.log.error("Error while locating assets from Collins:\n%s",
|
||||
traceback.format_exc())
|
||||
return False
|
||||
|
||||
for asset in server_assets:
|
||||
@ -305,7 +315,8 @@ class CollinsInventory(object):
|
||||
if self.prefer_hostnames and self._asset_has_attribute(asset, 'HOSTNAME'):
|
||||
asset_identifier = self._asset_get_attribute(asset, 'HOSTNAME')
|
||||
elif 'ADDRESSES' not in asset:
|
||||
self.log.warning("No IP addresses found for asset '%s', skipping", asset)
|
||||
self.log.warning("No IP addresses found for asset '%s', skipping",
|
||||
asset)
|
||||
continue
|
||||
elif len(asset['ADDRESSES']) < ip_index + 1:
|
||||
self.log.warning(
|
||||
@ -373,11 +384,11 @@ class CollinsInventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_cache_from_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
if not self.args.host in self.cache:
|
||||
# try updating the cache
|
||||
self.update_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
if not self.args.host in self.cache:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, self.args.pretty)
|
||||
|
||||
@ -393,7 +404,7 @@ class CollinsInventory(object):
|
||||
return True
|
||||
except:
|
||||
self.log.error("Error while loading inventory:\n%s",
|
||||
traceback.format_exc())
|
||||
traceback.format_exc())
|
||||
self.inventory = {}
|
||||
return False
|
||||
|
||||
@ -407,7 +418,7 @@ class CollinsInventory(object):
|
||||
return True
|
||||
except:
|
||||
self.log.error("Error while loading host cache:\n%s",
|
||||
traceback.format_exc())
|
||||
traceback.format_exc())
|
||||
self.cache = {}
|
||||
return False
|
||||
|
||||
|
||||
37
contrib/inventory/consul.ini
Normal file
37
contrib/inventory/consul.ini
Normal file
@ -0,0 +1,37 @@
|
||||
# Ansible Consul external inventory script settings.
|
||||
|
||||
[consul]
|
||||
|
||||
# restrict included nodes to those from this datacenter
|
||||
#datacenter = nyc1
|
||||
|
||||
# url of the the consul cluster to query
|
||||
#url = http://demo.consul.io
|
||||
url = http://localhost:8500
|
||||
|
||||
# suffix added to each service to create a group name e.g Service of 'redis' and
|
||||
# a suffix of '_servers' will add each address to the group name 'redis_servers'
|
||||
servers_suffix = _servers
|
||||
|
||||
# if specified then the inventory will generate domain names that will resolve
|
||||
# via Consul's inbuilt DNS.
|
||||
#domain=consul
|
||||
|
||||
# make groups from service tags. the name of the group is derived from the
|
||||
# service name and the tag name e.g. a service named nginx with tags ['master', 'v1']
|
||||
# will create groups nginx_master and nginx_v1
|
||||
tags = true
|
||||
|
||||
# looks up the node name at the given path for a list of groups to which the
|
||||
# node should be added.
|
||||
kv_groups=ansible/groups
|
||||
|
||||
# looks up the node name at the given path for a json dictionary of metadata that
|
||||
# should be attached as metadata for the node
|
||||
kv_metadata=ansible/metadata
|
||||
|
||||
# looks up the health of each service and adds the node to 'up' and 'down' groups
|
||||
# based on the service availibility
|
||||
availability = true
|
||||
available_suffix = _up
|
||||
unavailable_suffix = _down
|
||||
@ -1,37 +0,0 @@
|
||||
# Ansible Consul external inventory script settings.
|
||||
|
||||
[consul]
|
||||
|
||||
# restrict included nodes to those from this datacenter
|
||||
#datacenter = nyc1
|
||||
|
||||
# url of the consul cluster to query
|
||||
#url = http://demo.consul.io
|
||||
url = http://localhost:8500
|
||||
|
||||
# suffix added to each service to create a group name e.g Service of 'redis' and
|
||||
# a suffix of '_servers' will add each address to the group name 'redis_servers'
|
||||
servers_suffix = _servers
|
||||
|
||||
# if specified then the inventory will generate domain names that will resolve
|
||||
# via Consul's inbuilt DNS.
|
||||
#domain=consul
|
||||
|
||||
# make groups from service tags. the name of the group is derived from the
|
||||
# service name and the tag name e.g. a service named nginx with tags ['master', 'v1']
|
||||
# will create groups nginx_master and nginx_v1
|
||||
tags = true
|
||||
|
||||
# looks up the node name at the given path for a list of groups to which the
|
||||
# node should be added.
|
||||
kv_groups=ansible/groups
|
||||
|
||||
# looks up the node name at the given path for a json dictionary of metadata that
|
||||
# should be attached as metadata for the node
|
||||
kv_metadata=ansible/metadata
|
||||
|
||||
# looks up the health of each service and adds the node to 'up' and 'down' groups
|
||||
# based on the service availibility
|
||||
availability = true
|
||||
available_suffix = _up
|
||||
unavailable_suffix = _down
|
||||
@ -37,7 +37,7 @@ This script can be run with the switches
|
||||
--datacenter, to restrict the nodes to a single datacenter
|
||||
--host to restrict the inventory to a single named node. (requires datacenter config)
|
||||
|
||||
The configuration for this plugin is read from a consul_io.ini file located in the
|
||||
The configuration for this plugin is read from a consul.ini file located in the
|
||||
same directory as this inventory script. All config options in the config file
|
||||
are optional except the host and port, which must point to a valid agent or
|
||||
server running the http api. For more information on enabling the endpoint see.
|
||||
@ -133,11 +133,7 @@ import os
|
||||
import re
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
import ConfigParser
|
||||
|
||||
|
||||
def get_log_filename():
|
||||
@ -243,6 +239,7 @@ class ConsulInventory(object):
|
||||
self.current_dc = datacenter
|
||||
self.load_data_for_datacenter(datacenter)
|
||||
|
||||
|
||||
def load_availability_groups(self, node, datacenter):
|
||||
'''check the health of each service on a node and add add the node to either
|
||||
an 'available' or 'unavailable' grouping. The suffix for each group can be
|
||||
@ -260,7 +257,8 @@ class ConsulInventory(object):
|
||||
suffix = self.config.get_availability_suffix(
|
||||
'unavailable_suffix', '_unavailable')
|
||||
self.add_node_to_map(self.nodes_by_availability,
|
||||
service_name + suffix, node['Node'])
|
||||
service_name + suffix, node['Node'])
|
||||
|
||||
|
||||
def load_data_for_datacenter(self, datacenter):
|
||||
'''processes all the nodes in a particular datacenter'''
|
||||
@ -288,7 +286,7 @@ class ConsulInventory(object):
|
||||
|
||||
def load_node_metadata_from_kv(self, node_data):
|
||||
''' load the json dict at the metadata path defined by the kv_metadata value
|
||||
and the node name add each entry in the dictionary to the node's
|
||||
and the node name add each entry in the dictionary to the the node's
|
||||
metadata '''
|
||||
node = node_data['Node']
|
||||
if self.config.has_config('kv_metadata'):
|
||||
@ -297,7 +295,7 @@ class ConsulInventory(object):
|
||||
if metadata and metadata['Value']:
|
||||
try:
|
||||
metadata = json.loads(metadata['Value'])
|
||||
for k, v in metadata.items():
|
||||
for k,v in metadata.items():
|
||||
self.add_metadata(node_data, k, v)
|
||||
except:
|
||||
pass
|
||||
@ -339,19 +337,19 @@ class ConsulInventory(object):
|
||||
tags = service['Tags']
|
||||
self.add_metadata(node_data, "consul_%s_tags" % service_name, tags)
|
||||
for tag in service['Tags']:
|
||||
tagname = service_name + '_' + tag
|
||||
tagname = service_name +'_'+tag
|
||||
self.add_node_to_map(self.nodes_by_tag, tagname, node_data['Node'])
|
||||
|
||||
def combine_all_results(self):
|
||||
'''prunes and sorts all groupings for combination into the final map'''
|
||||
self.inventory = {"_meta": {"hostvars": self.node_metadata}}
|
||||
self.inventory = {"_meta": { "hostvars" : self.node_metadata}}
|
||||
groupings = [self.nodes, self.nodes_by_datacenter, self.nodes_by_service,
|
||||
self.nodes_by_tag, self.nodes_by_kv, self.nodes_by_availability]
|
||||
self.nodes_by_tag, self.nodes_by_kv, self.nodes_by_availability]
|
||||
for grouping in groupings:
|
||||
for name, addresses in grouping.items():
|
||||
self.inventory[name] = sorted(list(set(addresses)))
|
||||
|
||||
def add_metadata(self, node_data, key, value, is_list=False):
|
||||
def add_metadata(self, node_data, key, value, is_list = False):
|
||||
''' Pushed an element onto a metadata dict for the node, creating
|
||||
the dict if it doesn't exist '''
|
||||
key = self.to_safe(key)
|
||||
@ -373,15 +371,16 @@ class ConsulInventory(object):
|
||||
if domain:
|
||||
node_name = node_data['Node']
|
||||
if self.current_dc:
|
||||
return '%s.node.%s.%s' % (node_name, self.current_dc, domain)
|
||||
return '%s.node.%s.%s' % ( node_name, self.current_dc, domain)
|
||||
else:
|
||||
return '%s.node.%s' % (node_name, domain)
|
||||
return '%s.node.%s' % ( node_name, domain)
|
||||
else:
|
||||
return node_data['Address']
|
||||
|
||||
def add_node_to_map(self, map, name, node):
|
||||
self.push(map, name, self.get_inventory_name(node))
|
||||
|
||||
|
||||
def push(self, my_dict, key, element):
|
||||
''' Pushed an element onto an array that may not have been defined in the
|
||||
dict '''
|
||||
@ -424,12 +423,9 @@ class ConsulConfig(dict):
|
||||
return False
|
||||
|
||||
def read_settings(self):
|
||||
''' Reads the settings from the consul_io.ini file (or consul.ini for backwards compatibility)'''
|
||||
config = configparser.SafeConfigParser()
|
||||
if os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + '/consul_io.ini'):
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul_io.ini')
|
||||
else:
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul.ini')
|
||||
''' Reads the settings from the consul.ini file '''
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul.ini')
|
||||
|
||||
config_options = ['host', 'token', 'datacenter', 'servers_suffix',
|
||||
'tags', 'kv_metadata', 'kv_groups', 'availability',
|
||||
@ -443,15 +439,16 @@ class ConsulConfig(dict):
|
||||
|
||||
def read_cli_args(self):
|
||||
''' Command line argument processing '''
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based nodes in a Consul cluster')
|
||||
parser = argparse.ArgumentParser(description=
|
||||
'Produce an Ansible Inventory file based nodes in a Consul cluster')
|
||||
|
||||
parser.add_argument('--list', action='store_true',
|
||||
help='Get all inventory variables from all nodes in the consul cluster')
|
||||
help='Get all inventory variables from all nodes in the consul cluster')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all inventory variables about a specific consul node,'
|
||||
'requires datacenter set in consul.ini.')
|
||||
help='Get all inventory variables about a specific consul node, \
|
||||
requires datacenter set in consul.ini.')
|
||||
parser.add_argument('--datacenter', action='store',
|
||||
help='Get all inventory about a specific consul datacenter')
|
||||
help='Get all inventory about a specific consul datacenter')
|
||||
|
||||
args = parser.parse_args()
|
||||
arg_names = ['host', 'datacenter']
|
||||
@ -465,18 +462,16 @@ class ConsulConfig(dict):
|
||||
return self.has_config(suffix)
|
||||
return default
|
||||
|
||||
|
||||
def get_consul_api(self):
|
||||
'''get an instance of the api based on the supplied configuration'''
|
||||
host = 'localhost'
|
||||
port = 8500
|
||||
port = 8500
|
||||
token = None
|
||||
scheme = 'http'
|
||||
|
||||
if hasattr(self, 'url'):
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from urlparse import urlparse
|
||||
o = urlparse(self.url)
|
||||
if o.hostname:
|
||||
host = o.hostname
|
||||
|
||||
@ -22,7 +22,7 @@ found. You can force this script to use the cache with --force-cache.
|
||||
|
||||
----
|
||||
Configuration is read from `digital_ocean.ini`, then from environment variables,
|
||||
and then from command-line arguments.
|
||||
then and command-line arguments.
|
||||
|
||||
Most notably, the DigitalOcean API Token must be specified. It can be specified
|
||||
in the INI file or with the following environment variables:
|
||||
@ -153,7 +153,8 @@ except ImportError:
|
||||
try:
|
||||
from dopy.manager import DoManager
|
||||
except ImportError as e:
|
||||
sys.exit("failed=True msg={}".format(e.message))
|
||||
sys.exit("failed=True msg='`dopy` library required for this script'")
|
||||
|
||||
|
||||
|
||||
class DigitalOceanInventory(object):
|
||||
@ -166,8 +167,8 @@ class DigitalOceanInventory(object):
|
||||
''' Main execution path '''
|
||||
|
||||
# DigitalOceanInventory data
|
||||
self.data = {} # All DigitalOcean data
|
||||
self.inventory = {} # Ansible Inventory
|
||||
self.data = {} # All DigitalOcean data
|
||||
self.inventory = {} # Ansible Inventory
|
||||
|
||||
# Define defaults
|
||||
self.cache_path = '.'
|
||||
@ -196,7 +197,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
self.cache_filename = self.cache_path + "/ansible-digital_ocean.cache"
|
||||
self.cache_refreshed = False
|
||||
|
||||
if self.is_cache_valid():
|
||||
if self.is_cache_valid:
|
||||
self.load_from_cache()
|
||||
if len(self.data) == 0:
|
||||
if self.args.force_cache:
|
||||
@ -243,6 +244,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
print(json.dumps(json_data))
|
||||
# That's all she wrote...
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Script configuration
|
||||
###########################################################################
|
||||
@ -278,6 +280,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
if os.getenv("DO_API_KEY"):
|
||||
self.api_token = os.getenv("DO_API_KEY")
|
||||
|
||||
|
||||
def read_cli_args(self):
|
||||
''' Command line argument processing '''
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on DigitalOcean credentials')
|
||||
@ -286,23 +289,23 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
parser.add_argument('--host', action='store', help='Get all Ansible inventory variables about a specific Droplet')
|
||||
|
||||
parser.add_argument('--all', action='store_true', help='List all DigitalOcean information as JSON')
|
||||
parser.add_argument('--droplets', '-d', action='store_true', help='List Droplets as JSON')
|
||||
parser.add_argument('--droplets','-d', action='store_true', help='List Droplets as JSON')
|
||||
parser.add_argument('--regions', action='store_true', help='List Regions as JSON')
|
||||
parser.add_argument('--images', action='store_true', help='List Images as JSON')
|
||||
parser.add_argument('--sizes', action='store_true', help='List Sizes as JSON')
|
||||
parser.add_argument('--ssh-keys', action='store_true', help='List SSH keys as JSON')
|
||||
parser.add_argument('--domains', action='store_true', help='List Domains as JSON')
|
||||
parser.add_argument('--domains', action='store_true',help='List Domains as JSON')
|
||||
|
||||
parser.add_argument('--pretty', '-p', action='store_true', help='Pretty-print results')
|
||||
parser.add_argument('--pretty','-p', action='store_true', help='Pretty-print results')
|
||||
|
||||
parser.add_argument('--cache-path', action='store', help='Path to the cache files (default: .)')
|
||||
parser.add_argument('--cache-max_age', action='store', help='Maximum age of the cached items (default: 0)')
|
||||
parser.add_argument('--force-cache', action='store_true', default=False, help='Only use data from the cache')
|
||||
parser.add_argument('--refresh-cache', '-r', action='store_true', default=False,
|
||||
parser.add_argument('--refresh-cache','-r', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to DigitalOcean (default: False - use cache files)')
|
||||
|
||||
parser.add_argument('--env', '-e', action='store_true', help='Display DO_API_TOKEN')
|
||||
parser.add_argument('--api-token', '-a', action='store', help='DigitalOcean API Token')
|
||||
parser.add_argument('--env','-e', action='store_true', help='Display DO_API_TOKEN')
|
||||
parser.add_argument('--api-token','-a', action='store', help='DigitalOcean API Token')
|
||||
|
||||
self.args = parser.parse_args()
|
||||
|
||||
@ -316,19 +319,20 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
not self.args.all and not self.args.host):
|
||||
self.args.list = True
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Data Management
|
||||
###########################################################################
|
||||
|
||||
def load_from_digital_ocean(self, resource=None):
|
||||
'''Get JSON from DigitalOcean API'''
|
||||
if self.args.force_cache and os.path.isfile(self.cache_filename):
|
||||
if self.args.force_cache:
|
||||
return
|
||||
# We always get fresh droplets
|
||||
if self.is_cache_valid() and not (resource == 'droplets' or resource is None):
|
||||
if self.is_cache_valid() and not (resource=='droplets' or resource is None):
|
||||
return
|
||||
if self.args.refresh_cache:
|
||||
resource = None
|
||||
resource=None
|
||||
|
||||
if resource == 'droplets' or resource is None:
|
||||
self.data['droplets'] = self.manager.all_active_droplets()
|
||||
@ -349,23 +353,24 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
self.data['domains'] = self.manager.all_domains()
|
||||
self.cache_refreshed = True
|
||||
|
||||
|
||||
def build_inventory(self):
|
||||
'''Build Ansible inventory of droplets'''
|
||||
self.inventory = {
|
||||
'all': {
|
||||
'hosts': [],
|
||||
'vars': self.group_variables
|
||||
},
|
||||
},
|
||||
'_meta': {'hostvars': {}}
|
||||
}
|
||||
}
|
||||
|
||||
# add all droplets by id and name
|
||||
for droplet in self.data['droplets']:
|
||||
# when using private_networking, the API reports the private one in "ip_address".
|
||||
#when using private_networking, the API reports the private one in "ip_address".
|
||||
if 'private_networking' in droplet['features'] and not self.use_private_network:
|
||||
for net in droplet['networks']['v4']:
|
||||
if net['type'] == 'public':
|
||||
dest = net['ip_address']
|
||||
if net['type']=='public':
|
||||
dest=net['ip_address']
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
@ -383,7 +388,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
'distro_' + self.to_safe(droplet['image']['distribution']),
|
||||
'status_' + droplet['status']):
|
||||
if group not in self.inventory:
|
||||
self.inventory[group] = {'hosts': [], 'vars': {}}
|
||||
self.inventory[group] = { 'hosts': [ ], 'vars': {} }
|
||||
self.inventory[group]['hosts'].append(dest)
|
||||
|
||||
# groups that are not always present
|
||||
@ -392,19 +397,20 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
if group:
|
||||
image = 'image_' + self.to_safe(group)
|
||||
if image not in self.inventory:
|
||||
self.inventory[image] = {'hosts': [], 'vars': {}}
|
||||
self.inventory[image] = { 'hosts': [ ], 'vars': {} }
|
||||
self.inventory[image]['hosts'].append(dest)
|
||||
|
||||
if droplet['tags']:
|
||||
for tag in droplet['tags']:
|
||||
if tag not in self.inventory:
|
||||
self.inventory[tag] = {'hosts': [], 'vars': {}}
|
||||
self.inventory[tag] = { 'hosts': [ ], 'vars': {} }
|
||||
self.inventory[tag]['hosts'].append(dest)
|
||||
|
||||
# hostvars
|
||||
info = self.do_namespace(droplet)
|
||||
self.inventory['_meta']['hostvars'][dest] = info
|
||||
|
||||
|
||||
def load_droplet_variables_for_host(self):
|
||||
'''Generate a JSON response to a --host call'''
|
||||
host = int(self.args.host)
|
||||
@ -412,6 +418,8 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
info = self.do_namespace(droplet)
|
||||
return {'droplet': info}
|
||||
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Cache Management
|
||||
###########################################################################
|
||||
@ -425,6 +433,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_from_cache(self):
|
||||
''' Reads the data from the cache file and assigns it to member variables as Python Objects'''
|
||||
try:
|
||||
@ -438,15 +447,17 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
self.data = data['data']
|
||||
self.inventory = data['inventory']
|
||||
|
||||
|
||||
def write_to_cache(self):
|
||||
''' Writes data in JSON format to a file '''
|
||||
data = {'data': self.data, 'inventory': self.inventory}
|
||||
data = { 'data': self.data, 'inventory': self.inventory }
|
||||
json_data = json.dumps(data, sort_keys=True, indent=2)
|
||||
|
||||
cache = open(self.cache_filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Utilities
|
||||
###########################################################################
|
||||
@ -458,6 +469,7 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
|
||||
def to_safe(self, word):
|
||||
''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
|
||||
return re.sub("[^A-Za-z0-9\-\.]", "_", word)
|
||||
@ -466,10 +478,11 @@ or environment variables (DO_API_TOKEN)\n''')
|
||||
''' Returns a copy of the dictionary with all the keys put in a 'do_' namespace '''
|
||||
info = {}
|
||||
for k, v in data.items():
|
||||
info['do_' + k] = v
|
||||
info['do_'+k] = v
|
||||
return info
|
||||
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Run the script
|
||||
DigitalOceanInventory()
|
||||
|
||||
@ -371,6 +371,7 @@ HAS_DOCKER_PY = True
|
||||
HAS_DOCKER_ERROR = False
|
||||
|
||||
try:
|
||||
from docker import Client
|
||||
from docker.errors import APIError, TLSParameterError
|
||||
from docker.tls import TLSConfig
|
||||
from docker.constants import DEFAULT_TIMEOUT_SECONDS, DEFAULT_DOCKER_API_VERSION
|
||||
@ -378,19 +379,6 @@ except ImportError as exc:
|
||||
HAS_DOCKER_ERROR = str(exc)
|
||||
HAS_DOCKER_PY = False
|
||||
|
||||
# Client has recently been split into DockerClient and APIClient
|
||||
try:
|
||||
from docker import Client
|
||||
except ImportError as exc:
|
||||
try:
|
||||
from docker import APIClient as Client
|
||||
except ImportError as exc:
|
||||
HAS_DOCKER_ERROR = str(exc)
|
||||
HAS_DOCKER_PY = False
|
||||
|
||||
class Client:
|
||||
pass
|
||||
|
||||
DEFAULT_DOCKER_HOST = 'unix://var/run/docker.sock'
|
||||
DEFAULT_TLS = False
|
||||
DEFAULT_TLS_VERIFY = False
|
||||
@ -791,10 +779,6 @@ class DockerInventory(object):
|
||||
if config_path:
|
||||
try:
|
||||
config_file = os.path.abspath(config_path)
|
||||
# default config path is docker.yml in same directory as this script
|
||||
# old behaviour is docker.yml in current directory. Handle both.
|
||||
if not os.path.exists(config_file):
|
||||
config_file = os.path.abspath(os.path.basename(config_path))
|
||||
except:
|
||||
config_file = None
|
||||
|
||||
@ -829,30 +813,30 @@ class DockerInventory(object):
|
||||
# Parse command line arguments
|
||||
|
||||
basename = os.path.splitext(os.path.basename(__file__))[0]
|
||||
default_config = os.path.join(os.path.dirname(__file__), basename + '.yml')
|
||||
default_config = basename + '.yml'
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Return Ansible inventory for one or more Docker hosts.')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List all containers (default: True)')
|
||||
help='List all containers (default: True)')
|
||||
parser.add_argument('--debug', action='store_true', default=False,
|
||||
help='Send debug messages to STDOUT')
|
||||
help='Send debug messages to STDOUT')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Only get information for a specific container.')
|
||||
parser.add_argument('--pretty', action='store_true', default=False,
|
||||
help='Pretty print JSON output(default: False)')
|
||||
help='Pretty print JSON output(default: False)')
|
||||
parser.add_argument('--config-file', action='store', default=default_config,
|
||||
help="Name of the config file to use. Default is %s" % (default_config))
|
||||
parser.add_argument('--docker-host', action='store', default=None,
|
||||
help="The base url or Unix sock path to connect to the docker daemon. Defaults to %s"
|
||||
% (DEFAULT_DOCKER_HOST))
|
||||
% (DEFAULT_DOCKER_HOST))
|
||||
parser.add_argument('--tls-hostname', action='store', default='localhost',
|
||||
help="Host name to expect in TLS certs. Defaults to 'localhost'")
|
||||
parser.add_argument('--api-version', action='store', default=None,
|
||||
help="Docker daemon API version. Defaults to %s" % (DEFAULT_DOCKER_API_VERSION))
|
||||
parser.add_argument('--timeout', action='store', default=None,
|
||||
help="Docker connection timeout in seconds. Defaults to %s"
|
||||
% (DEFAULT_TIMEOUT_SECONDS))
|
||||
% (DEFAULT_TIMEOUT_SECONDS))
|
||||
parser.add_argument('--cacert-path', action='store', default=None,
|
||||
help="Path to the TLS certificate authority pem file.")
|
||||
parser.add_argument('--cert-path', action='store', default=None,
|
||||
|
||||
@ -10,9 +10,8 @@
|
||||
|
||||
# AWS regions to make calls to. Set this to 'all' to make request to all regions
|
||||
# in AWS and merge the results together. Alternatively, set this to a comma
|
||||
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' and do not
|
||||
# provide the 'regions_exclude' option. If this is set to 'auto', AWS_REGION or
|
||||
# AWS_DEFAULT_REGION environment variable will be read to determine the region.
|
||||
# separated list of regions. E.g. 'us-east-1, us-west-1, us-west-2'
|
||||
# 'auto' is AWS_REGION or AWS_DEFAULT_REGION environment variable.
|
||||
regions = all
|
||||
regions_exclude = us-gov-west-1, cn-north-1
|
||||
|
||||
@ -135,7 +134,6 @@ group_by_aws_account = False
|
||||
group_by_ami_id = True
|
||||
group_by_instance_type = True
|
||||
group_by_instance_state = False
|
||||
group_by_platform = True
|
||||
group_by_key_pair = True
|
||||
group_by_vpc_id = True
|
||||
group_by_security_group = True
|
||||
@ -159,9 +157,7 @@ group_by_elasticache_replication_group = True
|
||||
# inventory. For the full list of possible filters, please read the EC2 API
|
||||
# docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters
|
||||
# Filters are key/value pairs separated by '=', to list multiple filters use
|
||||
# a list separated by commas. To "AND" criteria together, use "&". Note that
|
||||
# the "AND" is not useful along with stack_filters and so such usage is not allowed.
|
||||
# See examples below.
|
||||
# a list separated by commas. See examples below.
|
||||
|
||||
# If you want to apply multiple filters simultaneously, set stack_filters to
|
||||
# True. Default behaviour is to combine the results of all filters. Stacking
|
||||
@ -183,18 +179,6 @@ stack_filters = False
|
||||
# (ex. webservers15, webservers1a, webservers123 etc)
|
||||
# instance_filters = tag:Name=webservers1*
|
||||
|
||||
# Retrieve only instances of type t1.micro that also have tag env=stage
|
||||
# instance_filters = instance-type=t1.micro&tag:env=stage
|
||||
|
||||
# Retrieve instances of type t1.micro AND tag env=stage, as well as any instance
|
||||
# that are of type m3.large, regardless of env tag
|
||||
# instance_filters = instance-type=t1.micro&tag:env=stage,instance-type=m3.large
|
||||
|
||||
# An IAM role can be assumed, so all requests are run as that role.
|
||||
# This can be useful for connecting across different accounts, or to limit user
|
||||
# access
|
||||
# iam_role = role-arn
|
||||
|
||||
# A boto configuration profile may be used to separate out credentials
|
||||
# see http://boto.readthedocs.org/en/latest/boto_config_tut.html
|
||||
# boto_profile = some-boto-profile-name
|
||||
|
||||
@ -12,7 +12,7 @@ variables needed for Boto have already been set:
|
||||
export AWS_ACCESS_KEY_ID='AK123'
|
||||
export AWS_SECRET_ACCESS_KEY='abc123'
|
||||
|
||||
optional region environment variable if region is 'auto'
|
||||
optional region environement variable if region is 'auto'
|
||||
|
||||
This script also assumes there is an ec2.ini file alongside it. To specify a
|
||||
different path to ec2.ini, define the EC2_INI_PATH environment variable:
|
||||
@ -132,14 +132,13 @@ from boto import ec2
|
||||
from boto import rds
|
||||
from boto import elasticache
|
||||
from boto import route53
|
||||
from boto import sts
|
||||
import six
|
||||
|
||||
from ansible.module_utils import ec2 as ec2_utils
|
||||
|
||||
HAS_BOTO3 = False
|
||||
try:
|
||||
import boto3 # noqa
|
||||
import boto3
|
||||
HAS_BOTO3 = True
|
||||
except ImportError:
|
||||
pass
|
||||
@ -152,65 +151,11 @@ try:
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
DEFAULTS = {
|
||||
'all_elasticache_clusters': 'False',
|
||||
'all_elasticache_nodes': 'False',
|
||||
'all_elasticache_replication_groups': 'False',
|
||||
'all_instances': 'False',
|
||||
'all_rds_instances': 'False',
|
||||
'aws_access_key_id': None,
|
||||
'aws_secret_access_key': None,
|
||||
'aws_security_token': None,
|
||||
'boto_profile': None,
|
||||
'cache_max_age': '300',
|
||||
'cache_path': '~/.ansible/tmp',
|
||||
'destination_variable': 'public_dns_name',
|
||||
'elasticache': 'True',
|
||||
'eucalyptus': 'False',
|
||||
'eucalyptus_host': None,
|
||||
'expand_csv_tags': 'False',
|
||||
'group_by_ami_id': 'True',
|
||||
'group_by_availability_zone': 'True',
|
||||
'group_by_aws_account': 'False',
|
||||
'group_by_elasticache_cluster': 'True',
|
||||
'group_by_elasticache_engine': 'True',
|
||||
'group_by_elasticache_parameter_group': 'True',
|
||||
'group_by_elasticache_replication_group': 'True',
|
||||
'group_by_instance_id': 'True',
|
||||
'group_by_instance_state': 'False',
|
||||
'group_by_instance_type': 'True',
|
||||
'group_by_key_pair': 'True',
|
||||
'group_by_platform': 'True',
|
||||
'group_by_rds_engine': 'True',
|
||||
'group_by_rds_parameter_group': 'True',
|
||||
'group_by_region': 'True',
|
||||
'group_by_route53_names': 'True',
|
||||
'group_by_security_group': 'True',
|
||||
'group_by_tag_keys': 'True',
|
||||
'group_by_tag_none': 'True',
|
||||
'group_by_vpc_id': 'True',
|
||||
'hostname_variable': None,
|
||||
'iam_role': None,
|
||||
'include_rds_clusters': 'False',
|
||||
'nested_groups': 'False',
|
||||
'pattern_exclude': None,
|
||||
'pattern_include': None,
|
||||
'rds': 'False',
|
||||
'regions': 'all',
|
||||
'regions_exclude': 'us-gov-west-1, cn-north-1',
|
||||
'replace_dash_in_groups': 'True',
|
||||
'route53': 'False',
|
||||
'route53_excluded_zones': '',
|
||||
'route53_hostnames': None,
|
||||
'stack_filters': 'False',
|
||||
'vpc_destination_variable': 'ip_address'
|
||||
}
|
||||
|
||||
|
||||
class Ec2Inventory(object):
|
||||
|
||||
def _empty_inventory(self):
|
||||
return {"_meta": {"hostvars": {}}}
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
def __init__(self):
|
||||
''' Main execution path '''
|
||||
@ -259,6 +204,7 @@ class Ec2Inventory(object):
|
||||
|
||||
print(data_to_print)
|
||||
|
||||
|
||||
def is_cache_valid(self):
|
||||
''' Determines if the cache files have expired, or if it is still valid '''
|
||||
|
||||
@ -271,6 +217,7 @@ class Ec2Inventory(object):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
''' Reads the settings from the ec2.ini file '''
|
||||
|
||||
@ -278,50 +225,35 @@ class Ec2Inventory(object):
|
||||
scriptbasename = os.path.basename(scriptbasename)
|
||||
scriptbasename = scriptbasename.replace('.py', '')
|
||||
|
||||
defaults = {
|
||||
'ec2': {
|
||||
'ini_fallback': os.path.join(os.path.dirname(__file__), 'ec2.ini'),
|
||||
'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename)
|
||||
defaults = {'ec2': {
|
||||
'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename)
|
||||
}
|
||||
}
|
||||
|
||||
if six.PY3:
|
||||
config = configparser.ConfigParser(DEFAULTS)
|
||||
config = configparser.ConfigParser()
|
||||
else:
|
||||
config = configparser.SafeConfigParser(DEFAULTS)
|
||||
config = configparser.SafeConfigParser()
|
||||
ec2_ini_path = os.environ.get('EC2_INI_PATH', defaults['ec2']['ini_path'])
|
||||
ec2_ini_path = os.path.expanduser(os.path.expandvars(ec2_ini_path))
|
||||
|
||||
if not os.path.isfile(ec2_ini_path):
|
||||
ec2_ini_path = os.path.expanduser(defaults['ec2']['ini_fallback'])
|
||||
|
||||
if os.path.isfile(ec2_ini_path):
|
||||
config.read(ec2_ini_path)
|
||||
|
||||
# Add empty sections if they don't exist
|
||||
try:
|
||||
config.add_section('ec2')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
|
||||
try:
|
||||
config.add_section('credentials')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
config.read(ec2_ini_path)
|
||||
|
||||
# is eucalyptus?
|
||||
self.eucalyptus = config.getboolean('ec2', 'eucalyptus')
|
||||
self.eucalyptus_host = config.get('ec2', 'eucalyptus_host')
|
||||
self.eucalyptus_host = None
|
||||
self.eucalyptus = False
|
||||
if config.has_option('ec2', 'eucalyptus'):
|
||||
self.eucalyptus = config.getboolean('ec2', 'eucalyptus')
|
||||
if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'):
|
||||
self.eucalyptus_host = config.get('ec2', 'eucalyptus_host')
|
||||
|
||||
# Regions
|
||||
self.regions = []
|
||||
configRegions = config.get('ec2', 'regions')
|
||||
configRegions_exclude = config.get('ec2', 'regions_exclude')
|
||||
if (configRegions == 'all'):
|
||||
if self.eucalyptus_host:
|
||||
self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name, **self.credentials)
|
||||
else:
|
||||
configRegions_exclude = config.get('ec2', 'regions_exclude')
|
||||
|
||||
for regionInfo in ec2.regions():
|
||||
if regionInfo.name not in configRegions_exclude:
|
||||
self.regions.append(regionInfo.name)
|
||||
@ -331,12 +263,16 @@ class Ec2Inventory(object):
|
||||
env_region = os.environ.get('AWS_REGION')
|
||||
if env_region is None:
|
||||
env_region = os.environ.get('AWS_DEFAULT_REGION')
|
||||
self.regions = [env_region]
|
||||
self.regions = [ env_region ]
|
||||
|
||||
# Destination addresses
|
||||
self.destination_variable = config.get('ec2', 'destination_variable')
|
||||
self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable')
|
||||
self.hostname_variable = config.get('ec2', 'hostname_variable')
|
||||
|
||||
if config.has_option('ec2', 'hostname_variable'):
|
||||
self.hostname_variable = config.get('ec2', 'hostname_variable')
|
||||
else:
|
||||
self.hostname_variable = None
|
||||
|
||||
if config.has_option('ec2', 'destination_format') and \
|
||||
config.has_option('ec2', 'destination_format_tags'):
|
||||
@ -348,22 +284,36 @@ class Ec2Inventory(object):
|
||||
|
||||
# Route53
|
||||
self.route53_enabled = config.getboolean('ec2', 'route53')
|
||||
self.route53_hostnames = config.get('ec2', 'route53_hostnames')
|
||||
|
||||
if config.has_option('ec2', 'route53_hostnames'):
|
||||
self.route53_hostnames = config.get('ec2', 'route53_hostnames')
|
||||
else:
|
||||
self.route53_hostnames = None
|
||||
self.route53_excluded_zones = []
|
||||
self.route53_excluded_zones = [a for a in config.get('ec2', 'route53_excluded_zones').split(',') if a]
|
||||
if config.has_option('ec2', 'route53_excluded_zones'):
|
||||
self.route53_excluded_zones.extend(
|
||||
config.get('ec2', 'route53_excluded_zones', '').split(','))
|
||||
|
||||
# Include RDS instances?
|
||||
self.rds_enabled = config.getboolean('ec2', 'rds')
|
||||
self.rds_enabled = True
|
||||
if config.has_option('ec2', 'rds'):
|
||||
self.rds_enabled = config.getboolean('ec2', 'rds')
|
||||
|
||||
# Include RDS cluster instances?
|
||||
self.include_rds_clusters = config.getboolean('ec2', 'include_rds_clusters')
|
||||
if config.has_option('ec2', 'include_rds_clusters'):
|
||||
self.include_rds_clusters = config.getboolean('ec2', 'include_rds_clusters')
|
||||
else:
|
||||
self.include_rds_clusters = False
|
||||
|
||||
# Include ElastiCache instances?
|
||||
self.elasticache_enabled = config.getboolean('ec2', 'elasticache')
|
||||
self.elasticache_enabled = True
|
||||
if config.has_option('ec2', 'elasticache'):
|
||||
self.elasticache_enabled = config.getboolean('ec2', 'elasticache')
|
||||
|
||||
# Return all EC2 instances?
|
||||
self.all_instances = config.getboolean('ec2', 'all_instances')
|
||||
if config.has_option('ec2', 'all_instances'):
|
||||
self.all_instances = config.getboolean('ec2', 'all_instances')
|
||||
else:
|
||||
self.all_instances = False
|
||||
|
||||
# Instance states to be gathered in inventory. Default is 'running'.
|
||||
# Setting 'all_instances' to 'yes' overrides this option.
|
||||
@ -388,30 +338,49 @@ class Ec2Inventory(object):
|
||||
self.ec2_instance_states = ['running']
|
||||
|
||||
# Return all RDS instances? (if RDS is enabled)
|
||||
self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
|
||||
if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
|
||||
self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
|
||||
else:
|
||||
self.all_rds_instances = False
|
||||
|
||||
# Return all ElastiCache replication groups? (if ElastiCache is enabled)
|
||||
self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups')
|
||||
if config.has_option('ec2', 'all_elasticache_replication_groups') and self.elasticache_enabled:
|
||||
self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups')
|
||||
else:
|
||||
self.all_elasticache_replication_groups = False
|
||||
|
||||
# Return all ElastiCache clusters? (if ElastiCache is enabled)
|
||||
self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters')
|
||||
if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled:
|
||||
self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters')
|
||||
else:
|
||||
self.all_elasticache_clusters = False
|
||||
|
||||
# Return all ElastiCache nodes? (if ElastiCache is enabled)
|
||||
self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes')
|
||||
if config.has_option('ec2', 'all_elasticache_nodes') and self.elasticache_enabled:
|
||||
self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes')
|
||||
else:
|
||||
self.all_elasticache_nodes = False
|
||||
|
||||
# boto configuration profile (prefer CLI argument then environment variables then config file)
|
||||
self.boto_profile = self.args.boto_profile or \
|
||||
os.environ.get('AWS_PROFILE') or \
|
||||
config.get('ec2', 'boto_profile')
|
||||
self.boto_profile = self.args.boto_profile or os.environ.get('AWS_PROFILE')
|
||||
if config.has_option('ec2', 'boto_profile') and not self.boto_profile:
|
||||
self.boto_profile = config.get('ec2', 'boto_profile')
|
||||
|
||||
# AWS credentials (prefer environment variables)
|
||||
if not (self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID') or
|
||||
os.environ.get('AWS_PROFILE')):
|
||||
|
||||
aws_access_key_id = config.get('credentials', 'aws_access_key_id')
|
||||
aws_secret_access_key = config.get('credentials', 'aws_secret_access_key')
|
||||
aws_security_token = config.get('credentials', 'aws_security_token')
|
||||
|
||||
if config.has_option('credentials', 'aws_access_key_id'):
|
||||
aws_access_key_id = config.get('credentials', 'aws_access_key_id')
|
||||
else:
|
||||
aws_access_key_id = None
|
||||
if config.has_option('credentials', 'aws_secret_access_key'):
|
||||
aws_secret_access_key = config.get('credentials', 'aws_secret_access_key')
|
||||
else:
|
||||
aws_secret_access_key = None
|
||||
if config.has_option('credentials', 'aws_security_token'):
|
||||
aws_security_token = config.get('credentials', 'aws_security_token')
|
||||
else:
|
||||
aws_security_token = None
|
||||
if aws_access_key_id:
|
||||
self.credentials = {
|
||||
'aws_access_key_id': aws_access_key_id,
|
||||
@ -435,74 +404,107 @@ class Ec2Inventory(object):
|
||||
self.cache_path_index = os.path.join(cache_dir, "%s.index" % cache_name)
|
||||
self.cache_max_age = config.getint('ec2', 'cache_max_age')
|
||||
|
||||
self.expand_csv_tags = config.getboolean('ec2', 'expand_csv_tags')
|
||||
if config.has_option('ec2', 'expand_csv_tags'):
|
||||
self.expand_csv_tags = config.getboolean('ec2', 'expand_csv_tags')
|
||||
else:
|
||||
self.expand_csv_tags = False
|
||||
|
||||
# Configure nested groups instead of flat namespace.
|
||||
self.nested_groups = config.getboolean('ec2', 'nested_groups')
|
||||
if config.has_option('ec2', 'nested_groups'):
|
||||
self.nested_groups = config.getboolean('ec2', 'nested_groups')
|
||||
else:
|
||||
self.nested_groups = False
|
||||
|
||||
# Replace dash or not in group names
|
||||
self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups')
|
||||
|
||||
# IAM role to assume for connection
|
||||
self.iam_role = config.get('ec2', 'iam_role')
|
||||
if config.has_option('ec2', 'replace_dash_in_groups'):
|
||||
self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups')
|
||||
else:
|
||||
self.replace_dash_in_groups = True
|
||||
|
||||
# Configure which groups should be created.
|
||||
|
||||
group_by_options = [a for a in DEFAULTS if a.startswith('group_by')]
|
||||
group_by_options = [
|
||||
'group_by_instance_id',
|
||||
'group_by_region',
|
||||
'group_by_availability_zone',
|
||||
'group_by_ami_id',
|
||||
'group_by_instance_type',
|
||||
'group_by_instance_state',
|
||||
'group_by_key_pair',
|
||||
'group_by_vpc_id',
|
||||
'group_by_security_group',
|
||||
'group_by_tag_keys',
|
||||
'group_by_tag_none',
|
||||
'group_by_route53_names',
|
||||
'group_by_rds_engine',
|
||||
'group_by_rds_parameter_group',
|
||||
'group_by_elasticache_engine',
|
||||
'group_by_elasticache_cluster',
|
||||
'group_by_elasticache_parameter_group',
|
||||
'group_by_elasticache_replication_group',
|
||||
'group_by_aws_account',
|
||||
]
|
||||
for option in group_by_options:
|
||||
setattr(self, option, config.getboolean('ec2', option))
|
||||
if config.has_option('ec2', option):
|
||||
setattr(self, option, config.getboolean('ec2', option))
|
||||
else:
|
||||
setattr(self, option, True)
|
||||
|
||||
# Do we need to just include hosts that match a pattern?
|
||||
self.pattern_include = config.get('ec2', 'pattern_include')
|
||||
if self.pattern_include:
|
||||
self.pattern_include = re.compile(self.pattern_include)
|
||||
try:
|
||||
pattern_include = config.get('ec2', 'pattern_include')
|
||||
if pattern_include and len(pattern_include) > 0:
|
||||
self.pattern_include = re.compile(pattern_include)
|
||||
else:
|
||||
self.pattern_include = None
|
||||
except configparser.NoOptionError:
|
||||
self.pattern_include = None
|
||||
|
||||
# Do we need to exclude hosts that match a pattern?
|
||||
self.pattern_exclude = config.get('ec2', 'pattern_exclude')
|
||||
if self.pattern_exclude:
|
||||
self.pattern_exclude = re.compile(self.pattern_exclude)
|
||||
try:
|
||||
pattern_exclude = config.get('ec2', 'pattern_exclude')
|
||||
if pattern_exclude and len(pattern_exclude) > 0:
|
||||
self.pattern_exclude = re.compile(pattern_exclude)
|
||||
else:
|
||||
self.pattern_exclude = None
|
||||
except configparser.NoOptionError:
|
||||
self.pattern_exclude = None
|
||||
|
||||
# Do we want to stack multiple filters?
|
||||
self.stack_filters = config.getboolean('ec2', 'stack_filters')
|
||||
if config.has_option('ec2', 'stack_filters'):
|
||||
self.stack_filters = config.getboolean('ec2', 'stack_filters')
|
||||
else:
|
||||
self.stack_filters = False
|
||||
|
||||
# Instance filters (see boto and EC2 API docs). Ignore invalid filters.
|
||||
self.ec2_instance_filters = []
|
||||
|
||||
self.ec2_instance_filters = defaultdict(list)
|
||||
if config.has_option('ec2', 'instance_filters'):
|
||||
filters = config.get('ec2', 'instance_filters')
|
||||
|
||||
if self.stack_filters and '&' in filters:
|
||||
self.fail_with_error("AND filters along with stack_filter enabled is not supported.\n")
|
||||
filters = [f for f in config.get('ec2', 'instance_filters').split(',') if f]
|
||||
|
||||
filter_sets = [f for f in filters.split(',') if f]
|
||||
|
||||
for filter_set in filter_sets:
|
||||
filters = {}
|
||||
filter_set = filter_set.strip()
|
||||
for instance_filter in filter_set.split("&"):
|
||||
instance_filter = instance_filter.strip()
|
||||
if not instance_filter or '=' not in instance_filter:
|
||||
continue
|
||||
filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)]
|
||||
if not filter_key:
|
||||
continue
|
||||
filters[filter_key] = filter_value
|
||||
self.ec2_instance_filters.append(filters.copy())
|
||||
for instance_filter in filters:
|
||||
instance_filter = instance_filter.strip()
|
||||
if not instance_filter or '=' not in instance_filter:
|
||||
continue
|
||||
filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)]
|
||||
if not filter_key:
|
||||
continue
|
||||
self.ec2_instance_filters[filter_key].append(filter_value)
|
||||
|
||||
def parse_cli_args(self):
|
||||
''' Command line argument processing '''
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific instance')
|
||||
help='Get all the variables about a specific instance')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
|
||||
help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
|
||||
parser.add_argument('--profile', '--boto-profile', action='store', dest='boto_profile',
|
||||
help='Use boto profile for connections to EC2')
|
||||
help='Use boto profile for connections to EC2')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
|
||||
def do_api_calls_update_cache(self):
|
||||
''' Do API calls to each region, and save data in cache files '''
|
||||
|
||||
@ -546,13 +548,6 @@ class Ec2Inventory(object):
|
||||
connect_args['profile_name'] = self.boto_profile
|
||||
self.boto_fix_security_token_in_profile(connect_args)
|
||||
|
||||
if self.iam_role:
|
||||
sts_conn = sts.connect_to_region(region, **connect_args)
|
||||
role = sts_conn.assume_role(self.iam_role, 'ansible_dynamic_inventory')
|
||||
connect_args['aws_access_key_id'] = role.credentials.access_key
|
||||
connect_args['aws_secret_access_key'] = role.credentials.secret_key
|
||||
connect_args['security_token'] = role.credentials.session_token
|
||||
|
||||
conn = module.connect_to_region(region, **connect_args)
|
||||
# connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
|
||||
if conn is None:
|
||||
@ -569,12 +564,12 @@ class Ec2Inventory(object):
|
||||
if self.ec2_instance_filters:
|
||||
if self.stack_filters:
|
||||
filters_dict = {}
|
||||
for filters in self.ec2_instance_filters:
|
||||
filters_dict.update(filters)
|
||||
reservations.extend(conn.get_all_instances(filters=filters_dict))
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
filters_dict[filter_key] = filter_values
|
||||
reservations.extend(conn.get_all_instances(filters = filters_dict))
|
||||
else:
|
||||
for filters in self.ec2_instance_filters:
|
||||
reservations.extend(conn.get_all_instances(filters=filters))
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
|
||||
else:
|
||||
reservations = conn.get_all_instances()
|
||||
|
||||
@ -588,7 +583,7 @@ class Ec2Inventory(object):
|
||||
max_filter_value = 199
|
||||
tags = []
|
||||
for i in range(0, len(instance_ids), max_filter_value):
|
||||
tags.extend(conn.get_all_tags(filters={'resource-type': 'instance', 'resource-id': instance_ids[i:i + max_filter_value]}))
|
||||
tags.extend(conn.get_all_tags(filters={'resource-type': 'instance', 'resource-id': instance_ids[i:i+max_filter_value]}))
|
||||
|
||||
tags_by_instance_id = defaultdict(dict)
|
||||
for tag in tags:
|
||||
@ -610,44 +605,10 @@ class Ec2Inventory(object):
|
||||
error = "Error connecting to %s backend.\n%s" % (backend, e.message)
|
||||
self.fail_with_error(error, 'getting EC2 instances')
|
||||
|
||||
def tags_match_filters(self, tags):
|
||||
''' return True if given tags match configured filters '''
|
||||
if not self.ec2_instance_filters:
|
||||
return True
|
||||
|
||||
for filters in self.ec2_instance_filters:
|
||||
for filter_name, filter_value in filters.items():
|
||||
if filter_name[:4] != 'tag:':
|
||||
continue
|
||||
filter_name = filter_name[4:]
|
||||
if filter_name not in tags:
|
||||
if self.stack_filters:
|
||||
return False
|
||||
continue
|
||||
if isinstance(filter_value, list):
|
||||
if self.stack_filters and tags[filter_name] not in filter_value:
|
||||
return False
|
||||
if not self.stack_filters and tags[filter_name] in filter_value:
|
||||
return True
|
||||
if isinstance(filter_value, six.string_types):
|
||||
if self.stack_filters and tags[filter_name] != filter_value:
|
||||
return False
|
||||
if not self.stack_filters and tags[filter_name] == filter_value:
|
||||
return True
|
||||
|
||||
return self.stack_filters
|
||||
|
||||
def get_rds_instances_by_region(self, region):
|
||||
''' Makes an AWS API call to the list of RDS instances in a particular
|
||||
region '''
|
||||
|
||||
if not HAS_BOTO3:
|
||||
self.fail_with_error("Working with RDS instances requires boto3 - please install boto3 and try again",
|
||||
"getting RDS instances")
|
||||
|
||||
client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials)
|
||||
db_instances = client.describe_db_instances()
|
||||
|
||||
try:
|
||||
conn = self.connect_to_aws(rds, region)
|
||||
if conn:
|
||||
@ -655,15 +616,8 @@ class Ec2Inventory(object):
|
||||
while True:
|
||||
instances = conn.get_all_dbinstances(marker=marker)
|
||||
marker = instances.marker
|
||||
for index, instance in enumerate(instances):
|
||||
# Add tags to instances.
|
||||
instance.arn = db_instances['DBInstances'][index]['DBInstanceArn']
|
||||
tags = client.list_tags_for_resource(ResourceName=instance.arn)['TagList']
|
||||
instance.tags = {}
|
||||
for tag in tags:
|
||||
instance.tags[tag['Key']] = tag['Value']
|
||||
if self.tags_match_filters(instance.tags):
|
||||
self.add_rds_instance(instance, region)
|
||||
for instance in instances:
|
||||
self.add_rds_instance(instance, region)
|
||||
if not marker:
|
||||
break
|
||||
except boto.exception.BotoServerError as e:
|
||||
@ -671,11 +625,7 @@ class Ec2Inventory(object):
|
||||
|
||||
if e.error_code == 'AuthFailure':
|
||||
error = self.get_auth_error_message()
|
||||
elif e.error_code == "OptInRequired":
|
||||
error = "RDS hasn't been enabled for this account yet. " \
|
||||
"You must either log in to the RDS service through the AWS console to enable it, " \
|
||||
"or set 'rds = False' in ec2.ini"
|
||||
elif not e.reason == "Forbidden":
|
||||
if not e.reason == "Forbidden":
|
||||
error = "Looks like AWS RDS is down:\n%s" % e.message
|
||||
self.fail_with_error(error, 'getting RDS instances')
|
||||
|
||||
@ -702,7 +652,7 @@ class Ec2Inventory(object):
|
||||
if 'LatestRestorableTime' in c:
|
||||
del c['LatestRestorableTime']
|
||||
|
||||
if not self.ec2_instance_filters:
|
||||
if self.ec2_instance_filters == {}:
|
||||
matches_filter = True
|
||||
else:
|
||||
matches_filter = False
|
||||
@ -714,18 +664,14 @@ class Ec2Inventory(object):
|
||||
c['Tags'] = tags['TagList']
|
||||
|
||||
if self.ec2_instance_filters:
|
||||
for filters in self.ec2_instance_filters:
|
||||
for filter_key, filter_values in filters.items():
|
||||
# get AWS tag key e.g. tag:env will be 'env'
|
||||
tag_name = filter_key.split(":", 1)[1]
|
||||
# Filter values is a list (if you put multiple values for the same tag name)
|
||||
matches_filter = any(d['Key'] == tag_name and d['Value'] in filter_values for d in c['Tags'])
|
||||
|
||||
if matches_filter:
|
||||
# it matches a filter, so stop looking for further matches
|
||||
break
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
# get AWS tag key e.g. tag:env will be 'env'
|
||||
tag_name = filter_key.split(":", 1)[1]
|
||||
# Filter values is a list (if you put multiple values for the same tag name)
|
||||
matches_filter = any(d['Key'] == tag_name and d['Value'] in filter_values for d in c['Tags'])
|
||||
|
||||
if matches_filter:
|
||||
# it matches a filter, so stop looking for further matches
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
@ -746,7 +692,7 @@ class Ec2Inventory(object):
|
||||
''' Makes an AWS API call to the list of ElastiCache clusters (with
|
||||
nodes' info) in a particular region.'''
|
||||
|
||||
# ElastiCache boto module doesn't provide a get_all_instances method,
|
||||
# ElastiCache boto module doesn't provide a get_all_intances method,
|
||||
# that's why we need to call describe directly (it would be called by
|
||||
# the shorthand method anyway...)
|
||||
try:
|
||||
@ -761,11 +707,7 @@ class Ec2Inventory(object):
|
||||
|
||||
if e.error_code == 'AuthFailure':
|
||||
error = self.get_auth_error_message()
|
||||
elif e.error_code == "OptInRequired":
|
||||
error = "ElastiCache hasn't been enabled for this account yet. " \
|
||||
"You must either log in to the ElastiCache service through the AWS console to enable it, " \
|
||||
"or set 'elasticache = False' in ec2.ini"
|
||||
elif not e.reason == "Forbidden":
|
||||
if not e.reason == "Forbidden":
|
||||
error = "Looks like AWS ElastiCache is down:\n%s" % e.message
|
||||
self.fail_with_error(error, 'getting ElastiCache clusters')
|
||||
|
||||
@ -786,7 +728,7 @@ class Ec2Inventory(object):
|
||||
''' Makes an AWS API call to the list of ElastiCache replication groups
|
||||
in a particular region.'''
|
||||
|
||||
# ElastiCache boto module doesn't provide a get_all_instances method,
|
||||
# ElastiCache boto module doesn't provide a get_all_intances method,
|
||||
# that's why we need to call describe directly (it would be called by
|
||||
# the shorthand method anyway...)
|
||||
try:
|
||||
@ -825,7 +767,7 @@ class Ec2Inventory(object):
|
||||
errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct')
|
||||
|
||||
boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials']
|
||||
boto_config_found = [p for p in boto_paths if os.path.isfile(os.path.expanduser(p))]
|
||||
boto_config_found = list(p for p in boto_paths if os.path.isfile(os.path.expanduser(p)))
|
||||
if len(boto_config_found) > 0:
|
||||
errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found))
|
||||
else:
|
||||
@ -859,7 +801,7 @@ class Ec2Inventory(object):
|
||||
|
||||
# Select the best destination address
|
||||
if self.destination_format and self.destination_format_tags:
|
||||
dest = self.destination_format.format(*[getattr(instance, 'tags').get(tag, '') for tag in self.destination_format_tags])
|
||||
dest = self.destination_format.format(*[ getattr(instance, 'tags').get(tag, '') for tag in self.destination_format_tags ])
|
||||
elif instance.subnet_id:
|
||||
dest = getattr(instance, self.vpc_destination_variable, None)
|
||||
if dest is None:
|
||||
@ -949,16 +891,6 @@ class Ec2Inventory(object):
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'instance_states', state_name)
|
||||
|
||||
# Inventory: Group by platform
|
||||
if self.group_by_platform:
|
||||
if instance.platform:
|
||||
platform = self.to_safe('platform_' + instance.platform)
|
||||
else:
|
||||
platform = self.to_safe('platform_undefined')
|
||||
self.push(self.inventory, platform, hostname)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'platforms', platform)
|
||||
|
||||
# Inventory: Group by key pair
|
||||
if self.group_by_key_pair and instance.key_name:
|
||||
key_name = self.to_safe('key_' + instance.key_name)
|
||||
@ -983,7 +915,7 @@ class Ec2Inventory(object):
|
||||
self.push_group(self.inventory, 'security_groups', key)
|
||||
except AttributeError:
|
||||
self.fail_with_error('\n'.join(['Package boto seems a bit older.',
|
||||
'Please upgrade boto >= 2.3.0.']))
|
||||
'Please upgrade boto >= 2.3.0.']))
|
||||
|
||||
# Inventory: Group by AWS account ID
|
||||
if self.group_by_aws_account:
|
||||
@ -1028,7 +960,8 @@ class Ec2Inventory(object):
|
||||
self.push(self.inventory, 'ec2', hostname)
|
||||
|
||||
self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance)
|
||||
self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest
|
||||
self.inventory["_meta"]["hostvars"][hostname]['ansible_ssh_host'] = dest
|
||||
|
||||
|
||||
def add_rds_instance(self, instance, region):
|
||||
''' Adds an RDS instance to the inventory and index, as long as it is
|
||||
@ -1107,25 +1040,8 @@ class Ec2Inventory(object):
|
||||
|
||||
except AttributeError:
|
||||
self.fail_with_error('\n'.join(['Package boto seems a bit older.',
|
||||
'Please upgrade boto >= 2.3.0.']))
|
||||
# Inventory: Group by tag keys
|
||||
if self.group_by_tag_keys:
|
||||
for k, v in instance.tags.items():
|
||||
if self.expand_csv_tags and v and ',' in v:
|
||||
values = map(lambda x: x.strip(), v.split(','))
|
||||
else:
|
||||
values = [v]
|
||||
'Please upgrade boto >= 2.3.0.']))
|
||||
|
||||
for v in values:
|
||||
if v:
|
||||
key = self.to_safe("tag_" + k + "=" + v)
|
||||
else:
|
||||
key = self.to_safe("tag_" + k)
|
||||
self.push(self.inventory, key, hostname)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
|
||||
if v:
|
||||
self.push_group(self.inventory, self.to_safe("tag_" + k), key)
|
||||
|
||||
# Inventory: Group by engine
|
||||
if self.group_by_rds_engine:
|
||||
@ -1139,17 +1055,11 @@ class Ec2Inventory(object):
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name))
|
||||
|
||||
# Global Tag: instances without tags
|
||||
if self.group_by_tag_none and len(instance.tags) == 0:
|
||||
self.push(self.inventory, 'tag_none', hostname)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'tags', 'tag_none')
|
||||
|
||||
# Global Tag: all RDS instances
|
||||
self.push(self.inventory, 'rds', hostname)
|
||||
|
||||
self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance)
|
||||
self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest
|
||||
self.inventory["_meta"]["hostvars"][hostname]['ansible_ssh_host'] = dest
|
||||
|
||||
def add_elasticache_cluster(self, cluster, region):
|
||||
''' Adds an ElastiCache cluster to the inventory and index, as long as
|
||||
@ -1400,7 +1310,8 @@ class Ec2Inventory(object):
|
||||
r53_conn = route53.Route53Connection()
|
||||
all_zones = r53_conn.get_zones()
|
||||
|
||||
route53_zones = [zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones]
|
||||
route53_zones = [ zone for zone in all_zones if zone.name[:-1]
|
||||
not in self.route53_excluded_zones ]
|
||||
|
||||
self.route53_records = {}
|
||||
|
||||
@ -1417,13 +1328,14 @@ class Ec2Inventory(object):
|
||||
self.route53_records.setdefault(resource, set())
|
||||
self.route53_records[resource].add(record_name)
|
||||
|
||||
|
||||
def get_instance_route53_names(self, instance):
|
||||
''' Check if an instance is referenced in the records we have from
|
||||
Route53. If it is, return the list of domain names pointing to said
|
||||
instance. If nothing points to it, return an empty list. '''
|
||||
|
||||
instance_attributes = ['public_dns_name', 'private_dns_name',
|
||||
'ip_address', 'private_ip_address']
|
||||
instance_attributes = [ 'public_dns_name', 'private_dns_name',
|
||||
'ip_address', 'private_ip_address' ]
|
||||
|
||||
name_list = set()
|
||||
|
||||
@ -1452,7 +1364,7 @@ class Ec2Inventory(object):
|
||||
elif key == 'ec2__previous_state':
|
||||
instance_vars['ec2_previous_state'] = instance.previous_state or ''
|
||||
instance_vars['ec2_previous_state_code'] = instance.previous_state_code
|
||||
elif isinstance(value, (int, bool)):
|
||||
elif type(value) in [int, bool]:
|
||||
instance_vars[key] = value
|
||||
elif isinstance(value, six.string_types):
|
||||
instance_vars[key] = value.strip()
|
||||
@ -1479,13 +1391,13 @@ class Ec2Inventory(object):
|
||||
elif key == 'ec2_block_device_mapping':
|
||||
instance_vars["ec2_block_devices"] = {}
|
||||
for k, v in value.items():
|
||||
instance_vars["ec2_block_devices"][os.path.basename(k)] = v.volume_id
|
||||
instance_vars["ec2_block_devices"][ os.path.basename(k) ] = v.volume_id
|
||||
else:
|
||||
pass
|
||||
# TODO Product codes if someone finds them useful
|
||||
# print key
|
||||
# print type(value)
|
||||
# print value
|
||||
#print key
|
||||
#print type(value)
|
||||
#print value
|
||||
|
||||
instance_vars[self.to_safe('ec2_account_id')] = self.aws_account_id
|
||||
|
||||
@ -1529,9 +1441,9 @@ class Ec2Inventory(object):
|
||||
host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port']
|
||||
host_info['ec2_primary_cluster_id'] = node['CacheClusterId']
|
||||
elif node['CurrentRole'] == 'replica':
|
||||
host_info['ec2_replica_cluster_address_' + str(replica_count)] = node['ReadEndpoint']['Address']
|
||||
host_info['ec2_replica_cluster_port_' + str(replica_count)] = node['ReadEndpoint']['Port']
|
||||
host_info['ec2_replica_cluster_id_' + str(replica_count)] = node['CacheClusterId']
|
||||
host_info['ec2_replica_cluster_address_'+ str(replica_count)] = node['ReadEndpoint']['Address']
|
||||
host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port']
|
||||
host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId']
|
||||
replica_count += 1
|
||||
|
||||
# Target: Redis Replication Groups
|
||||
@ -1557,7 +1469,7 @@ class Ec2Inventory(object):
|
||||
|
||||
# Target: Everything
|
||||
# Preserve booleans and integers
|
||||
elif isinstance(value, (int, bool)):
|
||||
elif type(value) in [int, bool]:
|
||||
host_info[key] = value
|
||||
|
||||
# Target: Everything
|
||||
@ -1583,10 +1495,10 @@ class Ec2Inventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_index_from_cache()
|
||||
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# try updating the cache
|
||||
self.do_api_calls_update_cache()
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ except:
|
||||
import simplejson as json
|
||||
|
||||
# Options
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
|
||||
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
|
||||
parser.add_option('--list', default=False, dest="list", action="store_true",
|
||||
@ -48,7 +48,6 @@ parser.add_option('--host', default=None, dest="host",
|
||||
# helper functions
|
||||
#
|
||||
|
||||
|
||||
def get_ssh_config():
|
||||
configs = []
|
||||
for box in list_running_boxes():
|
||||
@ -56,8 +55,7 @@ def get_ssh_config():
|
||||
configs.append(config)
|
||||
return configs
|
||||
|
||||
|
||||
# list all the running instances in the fleet
|
||||
#list all the running instances in the fleet
|
||||
def list_running_boxes():
|
||||
boxes = []
|
||||
for line in subprocess.check_output(["fleetctl", "list-machines"]).split('\n'):
|
||||
@ -67,7 +65,6 @@ def list_running_boxes():
|
||||
|
||||
return boxes
|
||||
|
||||
|
||||
def get_a_ssh_config(box_name):
|
||||
config = {}
|
||||
config['Host'] = box_name
|
||||
@ -75,12 +72,11 @@ def get_a_ssh_config(box_name):
|
||||
config['ansible_python_interpreter'] = '/opt/bin/python'
|
||||
return config
|
||||
|
||||
|
||||
# List out servers that vagrant has running
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
if options.list:
|
||||
ssh_config = get_ssh_config()
|
||||
hosts = {'coreos': []}
|
||||
hosts = { 'coreos': []}
|
||||
|
||||
for data in ssh_config:
|
||||
hosts['coreos'].append(data['Host'])
|
||||
@ -89,14 +85,14 @@ if options.list:
|
||||
sys.exit(1)
|
||||
|
||||
# Get out the host details
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
elif options.host:
|
||||
result = {}
|
||||
ssh_config = get_ssh_config()
|
||||
|
||||
details = filter(lambda x: (x['Host'] == options.host), ssh_config)
|
||||
if len(details) > 0:
|
||||
# pass through the port, in case it's non standard.
|
||||
#pass through the port, in case it's non standard.
|
||||
result = details[0]
|
||||
|
||||
print(json.dumps(result))
|
||||
@ -104,7 +100,7 @@ elif options.host:
|
||||
|
||||
|
||||
# Print out help
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
@ -68,21 +68,6 @@
|
||||
#
|
||||
# foreman_hostgroup_myapp_webtier_datacenter1
|
||||
#
|
||||
# If the parameter want_hostcollections is set to true, the
|
||||
# collections each host is in are created as Ansible groups with a
|
||||
# foreman_hostcollection prefix, all lowercase and problematic
|
||||
# parameters removed. So e.g. the Foreman host collection
|
||||
#
|
||||
# Patch Window Thursday
|
||||
#
|
||||
# would turn into the Ansible group:
|
||||
#
|
||||
# foreman_hostcollection_patchwindowthursday
|
||||
#
|
||||
# If the parameter host_filters is set, it will be used as the
|
||||
# "search" parameter for the /api/v2/hosts call. This can be used to
|
||||
# restrict the list of returned host, as shown below.
|
||||
#
|
||||
# Furthermore Ansible groups can be created on the fly using the
|
||||
# *group_patterns* variable in *foreman.ini* so that you can build up
|
||||
# hierarchies using parameters on the hostgroup and host variables.
|
||||
@ -123,35 +108,15 @@ user = foreman
|
||||
password = secret
|
||||
ssl_verify = True
|
||||
|
||||
# Retrieve only hosts from the organization "Web Engineering".
|
||||
# host_filters = organization="Web Engineering"
|
||||
|
||||
# Retrieve only hosts from the organization "Web Engineering" that are
|
||||
# also in the host collection "Apache Servers".
|
||||
# host_filters = organization="Web Engineering" and host_collection="Apache Servers"
|
||||
|
||||
[ansible]
|
||||
group_patterns = ["{app}-{tier}-{color}",
|
||||
"{app}-{color}",
|
||||
"{app}",
|
||||
"{tier}"]
|
||||
group_prefix = foreman_
|
||||
|
||||
# Whether to fetch facts from Foreman and store them on the host
|
||||
want_facts = True
|
||||
|
||||
# Whether to create Ansible groups for host collections. Only tested
|
||||
# with Katello (Red Hat Satellite). Disabled by default to not break
|
||||
# the script for stand-alone Foreman.
|
||||
want_hostcollections = False
|
||||
|
||||
# Whether to interpret global parameters value as JSON (if possible, else
|
||||
# take as is). Only tested with Katello (Red Hat Satellite).
|
||||
# This allows to define lists and dictionaries (and more complicated structures)
|
||||
# variables by entering them as JSON string in Foreman parameters.
|
||||
# Disabled by default as the change would else not be backward compatible.
|
||||
rich_params = False
|
||||
|
||||
[cache]
|
||||
path = .
|
||||
max_age = 60
|
||||
|
||||
@ -46,7 +46,6 @@ if LooseVersion(requests.__version__) < LooseVersion('1.1.0'):
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
|
||||
def json_format_dict(data, pretty=False):
|
||||
"""Converts a dict to a JSON object and dumps it as a formatted string"""
|
||||
|
||||
@ -55,7 +54,6 @@ def json_format_dict(data, pretty=False):
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
class ForemanInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -64,7 +62,6 @@ class ForemanInventory(object):
|
||||
self.params = dict() # Params of each host
|
||||
self.facts = dict() # Facts of each host
|
||||
self.hostgroups = dict() # host groups
|
||||
self.hostcollections = dict() # host collections
|
||||
self.session = None # Requests session
|
||||
self.config_paths = [
|
||||
"/etc/ansible/foreman.ini",
|
||||
@ -108,22 +105,6 @@ class ForemanInventory(object):
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
self.want_facts = True
|
||||
|
||||
try:
|
||||
self.want_hostcollections = config.getboolean('ansible', 'want_hostcollections')
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
self.want_hostcollections = False
|
||||
|
||||
# Do we want parameters to be interpreted if possible as JSON? (no by default)
|
||||
try:
|
||||
self.rich_params = config.getboolean('ansible', 'rich_params')
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
self.rich_params = False
|
||||
|
||||
try:
|
||||
self.host_filters = config.get('foreman', 'host_filters')
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
self.host_filters = None
|
||||
|
||||
# Cache related
|
||||
try:
|
||||
cache_path = os.path.expanduser(config.get('cache', 'path'))
|
||||
@ -134,12 +115,10 @@ class ForemanInventory(object):
|
||||
self.cache_path_inventory = cache_path + "/%s.index" % script
|
||||
self.cache_path_params = cache_path + "/%s.params" % script
|
||||
self.cache_path_facts = cache_path + "/%s.facts" % script
|
||||
self.cache_path_hostcollections = cache_path + "/%s.hostcollections" % script
|
||||
try:
|
||||
self.cache_max_age = config.getint('cache', 'max_age')
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
self.cache_max_age = 60
|
||||
|
||||
return True
|
||||
|
||||
def parse_cli_args(self):
|
||||
@ -159,17 +138,12 @@ class ForemanInventory(object):
|
||||
self.session.verify = self.foreman_ssl_verify
|
||||
return self.session
|
||||
|
||||
def _get_json(self, url, ignore_errors=None, params=None):
|
||||
if params is None:
|
||||
params = {}
|
||||
params['per_page'] = 250
|
||||
|
||||
def _get_json(self, url, ignore_errors=None):
|
||||
page = 1
|
||||
results = []
|
||||
s = self._get_session()
|
||||
while True:
|
||||
params['page'] = page
|
||||
ret = s.get(url, params=params)
|
||||
ret = s.get(url, params={'page': page, 'per_page': 250})
|
||||
if ignore_errors and ret.status_code in ignore_errors:
|
||||
break
|
||||
ret.raise_for_status()
|
||||
@ -182,7 +156,7 @@ class ForemanInventory(object):
|
||||
return json['results']
|
||||
# List of all hosts is returned paginaged
|
||||
results = results + json['results']
|
||||
if len(results) >= json['subtotal']:
|
||||
if len(results) >= json['total']:
|
||||
break
|
||||
page += 1
|
||||
if len(json['results']) == 0:
|
||||
@ -193,35 +167,22 @@ class ForemanInventory(object):
|
||||
return results
|
||||
|
||||
def _get_hosts(self):
|
||||
url = "%s/api/v2/hosts" % self.foreman_url
|
||||
return self._get_json("%s/api/v2/hosts" % self.foreman_url)
|
||||
|
||||
params = {}
|
||||
if self.host_filters:
|
||||
params['search'] = self.host_filters
|
||||
|
||||
return self._get_json(url, params=params)
|
||||
|
||||
def _get_host_data_by_id(self, hid):
|
||||
def _get_all_params_by_id(self, hid):
|
||||
url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid)
|
||||
return self._get_json(url)
|
||||
ret = self._get_json(url, [404])
|
||||
if ret == []:
|
||||
ret = {}
|
||||
return ret.get('all_parameters', {})
|
||||
|
||||
def _get_facts_by_id(self, hid):
|
||||
url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
|
||||
return self._get_json(url)
|
||||
|
||||
def _resolve_params(self, host_params):
|
||||
"""Convert host params to dict"""
|
||||
def _resolve_params(self, host):
|
||||
"""Fetch host params and convert to dict"""
|
||||
params = {}
|
||||
|
||||
for param in host_params:
|
||||
for param in self._get_all_params_by_id(host['id']):
|
||||
name = param['name']
|
||||
if self.rich_params:
|
||||
try:
|
||||
params[name] = json.loads(param['value'])
|
||||
except ValueError:
|
||||
params[name] = param['value']
|
||||
else:
|
||||
params[name] = param['value']
|
||||
params[name] = param['value']
|
||||
|
||||
return params
|
||||
|
||||
@ -255,7 +216,6 @@ class ForemanInventory(object):
|
||||
self.write_to_cache(self.inventory, self.cache_path_inventory)
|
||||
self.write_to_cache(self.params, self.cache_path_params)
|
||||
self.write_to_cache(self.facts, self.cache_path_facts)
|
||||
self.write_to_cache(self.hostcollections, self.cache_path_hostcollections)
|
||||
|
||||
def to_safe(self, word):
|
||||
'''Converts 'bad' characters in a string to underscores
|
||||
@ -276,9 +236,6 @@ class ForemanInventory(object):
|
||||
for host in self._get_hosts():
|
||||
dns_name = host['name']
|
||||
|
||||
host_data = self._get_host_data_by_id(host['id'])
|
||||
host_params = host_data.get('all_parameters', {})
|
||||
|
||||
# Create ansible groups for hostgroup
|
||||
group = 'hostgroup'
|
||||
val = host.get('%s_title' % group) or host.get('%s_name' % group)
|
||||
@ -299,13 +256,16 @@ class ForemanInventory(object):
|
||||
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
|
||||
self.inventory[safe_key].append(dns_name)
|
||||
|
||||
params = self._resolve_params(host_params)
|
||||
params = self._resolve_params(host)
|
||||
|
||||
# Ansible groups by parameters in host groups and Foreman host
|
||||
# attributes.
|
||||
groupby = dict()
|
||||
for k, v in params.items():
|
||||
groupby[k] = self.to_safe(str(v))
|
||||
groupby = copy.copy(params)
|
||||
for k, v in host.items():
|
||||
if isinstance(v, str):
|
||||
groupby[k] = self.to_safe(v)
|
||||
elif isinstance(v, int):
|
||||
groupby[k] = v
|
||||
|
||||
# The name of the ansible groups is given by group_patterns:
|
||||
for pattern in self.group_patterns:
|
||||
@ -315,17 +275,6 @@ class ForemanInventory(object):
|
||||
except KeyError:
|
||||
pass # Host not part of this group
|
||||
|
||||
if self.want_hostcollections:
|
||||
hostcollections = host_data.get('host_collections')
|
||||
|
||||
if hostcollections:
|
||||
# Create Ansible groups for host collections
|
||||
for hostcollection in hostcollections:
|
||||
safe_key = self.to_safe('%shostcollection_%s' % (self.group_prefix, hostcollection['name'].lower()))
|
||||
self.inventory[safe_key].append(dns_name)
|
||||
|
||||
self.hostcollections[dns_name] = hostcollections
|
||||
|
||||
self.cache[dns_name] = host
|
||||
self.params[dns_name] = params
|
||||
self.facts[dns_name] = self._get_facts(host)
|
||||
@ -347,36 +296,31 @@ class ForemanInventory(object):
|
||||
def load_inventory_from_cache(self):
|
||||
"""Read the index from the cache file sets self.index"""
|
||||
|
||||
with open(self.cache_path_inventory, 'r') as fp:
|
||||
self.inventory = json.load(fp)
|
||||
cache = open(self.cache_path_inventory, 'r')
|
||||
json_inventory = cache.read()
|
||||
self.inventory = json.loads(json_inventory)
|
||||
|
||||
def load_params_from_cache(self):
|
||||
"""Read the index from the cache file sets self.index"""
|
||||
|
||||
with open(self.cache_path_params, 'r') as fp:
|
||||
self.params = json.load(fp)
|
||||
cache = open(self.cache_path_params, 'r')
|
||||
json_params = cache.read()
|
||||
self.params = json.loads(json_params)
|
||||
|
||||
def load_facts_from_cache(self):
|
||||
"""Read the index from the cache file sets self.facts"""
|
||||
|
||||
if not self.want_facts:
|
||||
return
|
||||
with open(self.cache_path_facts, 'r') as fp:
|
||||
self.facts = json.load(fp)
|
||||
|
||||
def load_hostcollections_from_cache(self):
|
||||
"""Read the index from the cache file sets self.hostcollections"""
|
||||
|
||||
if not self.want_hostcollections:
|
||||
return
|
||||
with open(self.cache_path_hostcollections, 'r') as fp:
|
||||
self.hostcollections = json.load(fp)
|
||||
cache = open(self.cache_path_facts, 'r')
|
||||
json_facts = cache.read()
|
||||
self.facts = json.loads(json_facts)
|
||||
|
||||
def load_cache_from_cache(self):
|
||||
"""Read the cache from the cache file sets self.cache"""
|
||||
|
||||
with open(self.cache_path_cache, 'r') as fp:
|
||||
self.cache = json.load(fp)
|
||||
cache = open(self.cache_path_cache, 'r')
|
||||
json_cache = cache.read()
|
||||
self.cache = json.loads(json_cache)
|
||||
|
||||
def get_inventory(self):
|
||||
if self.args.refresh_cache or not self.is_cache_valid():
|
||||
@ -385,7 +329,6 @@ class ForemanInventory(object):
|
||||
self.load_inventory_from_cache()
|
||||
self.load_params_from_cache()
|
||||
self.load_facts_from_cache()
|
||||
self.load_hostcollections_from_cache()
|
||||
self.load_cache_from_cache()
|
||||
|
||||
def get_host_info(self):
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
import argparse
|
||||
from ipalib import api
|
||||
import json
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
|
||||
def initialize():
|
||||
'''
|
||||
@ -20,12 +16,11 @@ def initialize():
|
||||
try:
|
||||
api.Backend.rpcclient.connect()
|
||||
except AttributeError:
|
||||
# FreeIPA < 4.0 compatibility
|
||||
#FreeIPA < 4.0 compatibility
|
||||
api.Backend.xmlclient.connect()
|
||||
|
||||
return api
|
||||
|
||||
|
||||
def list_groups(api):
|
||||
'''
|
||||
This function prints a list of all host groups. This function requires
|
||||
@ -33,18 +28,14 @@ def list_groups(api):
|
||||
'''
|
||||
|
||||
inventory = {}
|
||||
hostvars = {}
|
||||
hostvars={}
|
||||
meta={}
|
||||
|
||||
ipa_version = api.Command.env()['result']['version']
|
||||
result = api.Command.hostgroup_find()['result']
|
||||
|
||||
for hostgroup in result:
|
||||
# Get direct and indirect members (nested hostgroups) of hostgroup
|
||||
members = []
|
||||
if StrictVersion(ipa_version) >= StrictVersion('4.0.0'):
|
||||
hostgroup_name = hostgroup['cn'][0]
|
||||
hostgroup = api.Command.hostgroup_show(hostgroup_name)['result']
|
||||
|
||||
if 'member_host' in hostgroup:
|
||||
members = [host for host in hostgroup['member_host']]
|
||||
if 'memberindirect_host' in hostgroup:
|
||||
@ -60,7 +51,6 @@ def list_groups(api):
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_args():
|
||||
'''
|
||||
This function parses the arguments that were passed in via the command line.
|
||||
@ -76,7 +66,6 @@ def parse_args():
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def print_host(host):
|
||||
'''
|
||||
This function is really a stub, it could return variables to be used in
|
||||
@ -90,7 +79,6 @@ def print_host(host):
|
||||
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_args()
|
||||
|
||||
|
||||
@ -37,14 +37,13 @@
|
||||
# statement in the inventory script. However, you can specify an absolute
|
||||
# path to the secrets.py file with 'libcloud_secrets' parameter.
|
||||
# This option will be deprecated in a future release.
|
||||
libcloud_secrets =
|
||||
libcloud_secrets =
|
||||
|
||||
# If you are not going to use a 'secrets.py' file, you can set the necessary
|
||||
# authorization parameters here.
|
||||
gce_service_account_email_address =
|
||||
gce_service_account_pem_file_path =
|
||||
gce_project_id =
|
||||
gce_zone =
|
||||
gce_service_account_email_address =
|
||||
gce_service_account_pem_file_path =
|
||||
gce_project_id =
|
||||
|
||||
# Filter inventory based on on state. Leave undefined to return instances regardless of state.
|
||||
# example: Uncomment to only return inventory in the running or provisioning state
|
||||
|
||||
@ -40,7 +40,6 @@ based on the data obtained from the libcloud Node object:
|
||||
- gce_tags
|
||||
- gce_metadata
|
||||
- gce_network
|
||||
- gce_subnetwork
|
||||
|
||||
When run in --list mode, instances are grouped by the following categories:
|
||||
- zone:
|
||||
@ -74,6 +73,7 @@ Contributors: Matt Hite <mhite@hotmail.com>, Tom Melendez <supertom@google.com>
|
||||
Version: 0.0.3
|
||||
'''
|
||||
|
||||
__requires__ = ['pycrypto>=2.6']
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
@ -83,8 +83,8 @@ except ImportError:
|
||||
# library is used.
|
||||
pass
|
||||
|
||||
USER_AGENT_PRODUCT = "Ansible-gce_inventory_plugin"
|
||||
USER_AGENT_VERSION = "v2"
|
||||
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
|
||||
USER_AGENT_VERSION="v2"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@ -92,10 +92,7 @@ import argparse
|
||||
|
||||
from time import time
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
import configparser
|
||||
else:
|
||||
import ConfigParser as configparser
|
||||
import ConfigParser
|
||||
|
||||
import logging
|
||||
logging.getLogger('libcloud.common.google').addHandler(logging.NullHandler())
|
||||
@ -216,11 +213,10 @@ class GceInventory(object):
|
||||
# This provides empty defaults to each key, so that environment
|
||||
# variable configuration (as opposed to INI configuration) is able
|
||||
# to work.
|
||||
config = configparser.SafeConfigParser(defaults={
|
||||
config = ConfigParser.SafeConfigParser(defaults={
|
||||
'gce_service_account_email_address': '',
|
||||
'gce_service_account_pem_file_path': '',
|
||||
'gce_project_id': '',
|
||||
'gce_zone': '',
|
||||
'libcloud_secrets': '',
|
||||
'inventory_ip_type': '',
|
||||
'cache_path': '~/.ansible/tmp',
|
||||
@ -274,11 +270,10 @@ class GceInventory(object):
|
||||
# exists.
|
||||
secrets_path = self.config.get('gce', 'libcloud_secrets')
|
||||
secrets_found = False
|
||||
|
||||
try:
|
||||
import secrets
|
||||
args = list(secrets.GCE_PARAMS)
|
||||
kwargs = secrets.GCE_KEYWORD_PARAMS
|
||||
args = list(getattr(secrets, 'GCE_PARAMS', []))
|
||||
kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
secrets_found = True
|
||||
except:
|
||||
pass
|
||||
@ -296,23 +291,18 @@ class GceInventory(object):
|
||||
secrets_found = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if not secrets_found:
|
||||
args = [
|
||||
self.config.get('gce', 'gce_service_account_email_address'),
|
||||
self.config.get('gce', 'gce_service_account_pem_file_path')
|
||||
self.config.get('gce','gce_service_account_email_address'),
|
||||
self.config.get('gce','gce_service_account_pem_file_path')
|
||||
]
|
||||
kwargs = {'project': self.config.get('gce', 'gce_project_id'),
|
||||
'datacenter': self.config.get('gce', 'gce_zone')}
|
||||
kwargs = {'project': self.config.get('gce', 'gce_project_id')}
|
||||
|
||||
# If the appropriate environment variables are set, they override
|
||||
# other configuration; process those into our args and kwargs.
|
||||
args[0] = os.environ.get('GCE_EMAIL', args[0])
|
||||
args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1])
|
||||
args[1] = os.environ.get('GCE_CREDENTIALS_FILE_PATH', args[1])
|
||||
|
||||
kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project'])
|
||||
kwargs['datacenter'] = os.environ.get('GCE_ZONE', kwargs['datacenter'])
|
||||
|
||||
# Retrieve and return the GCE driver.
|
||||
gce = get_driver(Provider.GCE)(*args, **kwargs)
|
||||
@ -325,7 +315,7 @@ class GceInventory(object):
|
||||
'''returns a list of comma separated zones parsed from the GCE_ZONE environment variable.
|
||||
If provided, this will be used to filter the results of the grouped_instances call'''
|
||||
import csv
|
||||
reader = csv.reader([os.environ.get('GCE_ZONE', "")], skipinitialspace=True)
|
||||
reader = csv.reader([os.environ.get('GCE_ZONE',"")], skipinitialspace=True)
|
||||
zones = [r for r in reader]
|
||||
return [z for z in zones[0]]
|
||||
|
||||
@ -335,16 +325,17 @@ class GceInventory(object):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce an Ansible Inventory file based on GCE')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all information about an instance')
|
||||
help='Get all information about an instance')
|
||||
parser.add_argument('--pretty', action='store_true', default=False,
|
||||
help='Pretty format (default: False)')
|
||||
help='Pretty format (default: False)')
|
||||
parser.add_argument(
|
||||
'--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests (default: False - use cache files)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
|
||||
def node_to_dict(self, inst):
|
||||
md = {}
|
||||
|
||||
@ -356,9 +347,6 @@ class GceInventory(object):
|
||||
md[entry['key']] = entry['value']
|
||||
|
||||
net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||
subnet = None
|
||||
if 'subnetwork' in inst.extra['networkInterfaces'][0]:
|
||||
subnet = inst.extra['networkInterfaces'][0]['subnetwork'].split('/')[-1]
|
||||
# default to exernal IP unless user has specified they prefer internal
|
||||
if self.ip_type == 'internal':
|
||||
ssh_host = inst.private_ips[0]
|
||||
@ -379,7 +367,6 @@ class GceInventory(object):
|
||||
'gce_tags': inst.extra['tags'],
|
||||
'gce_metadata': md,
|
||||
'gce_network': net,
|
||||
'gce_subnetwork': subnet,
|
||||
# Hosts don't have a public name, so we add an IP
|
||||
'ansible_ssh_host': ssh_host
|
||||
}
|
||||
@ -407,7 +394,7 @@ class GceInventory(object):
|
||||
all_nodes = []
|
||||
params, more_results = {'maxResults': 500}, True
|
||||
while more_results:
|
||||
self.driver.connection.gce_params = params
|
||||
self.driver.connection.gce_params=params
|
||||
all_nodes.extend(self.driver.list_nodes())
|
||||
more_results = 'pageToken' in params
|
||||
return all_nodes
|
||||
@ -483,13 +470,6 @@ class GceInventory(object):
|
||||
else:
|
||||
groups[stat] = [name]
|
||||
|
||||
for private_ip in node.private_ips:
|
||||
groups[private_ip] = [name]
|
||||
|
||||
if len(node.public_ips) >= 1:
|
||||
for public_ip in node.public_ips:
|
||||
groups[public_ip] = [name]
|
||||
|
||||
groups["_meta"] = meta
|
||||
|
||||
return groups
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen,PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen,PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
@ -113,15 +113,11 @@ load_chube_config()
|
||||
# Imports for ansible
|
||||
import ConfigParser
|
||||
|
||||
|
||||
class LinodeInventory(object):
|
||||
def _empty_inventory(self):
|
||||
return {"_meta": {"hostvars": {}}}
|
||||
|
||||
def __init__(self):
|
||||
"""Main execution path."""
|
||||
# Inventory grouped by display group
|
||||
self.inventory = self._empty_inventory()
|
||||
self.inventory = {}
|
||||
# Index of label to Linode ID
|
||||
self.index = {}
|
||||
# Local cache of Datacenter objects populated by populate_datacenter_cache()
|
||||
@ -142,7 +138,7 @@ class LinodeInventory(object):
|
||||
data_to_print = self.get_host_info()
|
||||
elif self.args.list:
|
||||
# Display list of nodes for inventory
|
||||
if len(self.inventory) == 1:
|
||||
if len(self.inventory) == 0:
|
||||
data_to_print = self.get_inventory_from_cache()
|
||||
else:
|
||||
data_to_print = self.json_format_dict(self.inventory, True)
|
||||
@ -175,11 +171,11 @@ class LinodeInventory(object):
|
||||
"""Command line argument processing"""
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Linode')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List nodes (default: True)')
|
||||
help='List nodes (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific node')
|
||||
help='Get all the variables about a specific node')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to Linode (default: False - use cache files)')
|
||||
help='Force refresh of cache by making API requests to Linode (default: False - use cache files)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def do_api_calls_update_cache(self):
|
||||
@ -235,15 +231,9 @@ class LinodeInventory(object):
|
||||
# Inventory: Group by datacenter city
|
||||
self.push(self.inventory, self.get_datacenter_city(node), dest)
|
||||
|
||||
# Inventory: Group by display group
|
||||
# Inventory: Group by dipslay group
|
||||
self.push(self.inventory, node.display_group, dest)
|
||||
|
||||
# Inventory: Add a "linode" global tag group
|
||||
self.push(self.inventory, "linode", dest)
|
||||
|
||||
# Add host info to hostvars
|
||||
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info(node)
|
||||
|
||||
def get_node_public_ip(self, node):
|
||||
"""Returns a the public IP address of the node"""
|
||||
return [addr.address for addr in node.ipaddresses if addr.is_public][0]
|
||||
@ -255,19 +245,16 @@ class LinodeInventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_index_from_cache()
|
||||
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# try updating the cache
|
||||
self.do_api_calls_update_cache()
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
node_id = self.index[self.args.host]
|
||||
|
||||
node = self.get_node(node_id)
|
||||
|
||||
return self.json_format_dict(self.get_host_info(node), True)
|
||||
|
||||
def get_host_info(self, node):
|
||||
node_vars = {}
|
||||
for direct_attr in [
|
||||
"api_id",
|
||||
@ -308,7 +295,7 @@ class LinodeInventory(object):
|
||||
if private_ips:
|
||||
node_vars["private_ip"] = private_ips[0]
|
||||
|
||||
return node_vars
|
||||
return self.json_format_dict(node_vars, True)
|
||||
|
||||
def push(self, my_dict, key, element):
|
||||
"""Pushed an element onto an array that may not have been defined in the dict."""
|
||||
|
||||
@ -35,7 +35,6 @@ import sys
|
||||
import lxc
|
||||
import json
|
||||
|
||||
|
||||
def build_dict():
|
||||
"""Returns a dictionary keyed to the defined LXC groups. All
|
||||
containers, including the ones not in any group, are included in the
|
||||
@ -52,8 +51,7 @@ def build_dict():
|
||||
|
||||
# Create a dictionary for each group (including the 'all' group
|
||||
return dict([(g, {'hosts': [k for k, v in containers.items() if g in v],
|
||||
'vars': {'ansible_connection': 'lxc'}}) for g in groups])
|
||||
|
||||
'vars': {'ansible_connection':'lxc'}}) for g in groups])
|
||||
|
||||
def main(argv):
|
||||
"""Returns a JSON dictionary as expected by Ansible"""
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
# LXD external inventory script settings
|
||||
|
||||
[lxd]
|
||||
|
||||
# The default resource
|
||||
#resource = local:
|
||||
|
||||
# The group name to add the hosts to
|
||||
#group = lxd
|
||||
|
||||
# The connection type to return for these hosts - lxd hasn't been tested yet
|
||||
#connection = lxd
|
||||
connection = smart
|
||||
@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2013, Michael Scherer <misc@zarb.org>
|
||||
# (c) 2014, Hiroaki Nakamura <hnakamur@gmail.com>
|
||||
# (c) 2016, Andew Clarke <andrew@oscailte.org>
|
||||
#
|
||||
# This file is based on https://github.com/ansible/ansible/blob/devel/plugins/inventory/libvirt_lxc.py which is part of Ansible,
|
||||
# and https://github.com/hnakamur/lxc-ansible-playbooks/blob/master/provisioning/inventory-lxc.py
|
||||
#
|
||||
# NOTE, this file has some obvious limitations, improvements welcome
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from subprocess import Popen, PIPE
|
||||
import distutils.spawn
|
||||
import sys
|
||||
import json
|
||||
try:
|
||||
import configparser
|
||||
except:
|
||||
from six.moves import configparser
|
||||
|
||||
# Set up defaults
|
||||
resource = 'local:'
|
||||
group = 'lxd'
|
||||
connection = 'lxd'
|
||||
hosts = {}
|
||||
result = {}
|
||||
|
||||
# Read the settings from the lxd.ini file
|
||||
config = configparser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/lxd.ini')
|
||||
if config.has_option('lxd', 'resource'):
|
||||
resource = config.get('lxd', 'resource')
|
||||
if config.has_option('lxd', 'group'):
|
||||
group = config.get('lxd', 'group')
|
||||
if config.has_option('lxd', 'connection'):
|
||||
connection = config.get('lxd', 'connection')
|
||||
|
||||
# Ensure executable exists
|
||||
if distutils.spawn.find_executable('lxc'):
|
||||
|
||||
# Set up containers result and hosts array
|
||||
result[group] = {}
|
||||
result[group]['hosts'] = []
|
||||
|
||||
# Run the command and load json result
|
||||
pipe = Popen(['lxc', 'list', resource, '--format', 'json'], stdout=PIPE, universal_newlines=True)
|
||||
lxdjson = json.load(pipe.stdout)
|
||||
|
||||
# Iterate the json lxd output
|
||||
for item in lxdjson:
|
||||
|
||||
# Check state and network
|
||||
if 'state' in item and item['state'] is not None and 'network' in item['state']:
|
||||
network = item['state']['network']
|
||||
|
||||
# Check for eth0 and addresses
|
||||
if 'eth0' in network and 'addresses' in network['eth0']:
|
||||
addresses = network['eth0']['addresses']
|
||||
|
||||
# Iterate addresses
|
||||
for address in addresses:
|
||||
|
||||
# Only return inet family addresses
|
||||
if 'family' in address and address['family'] == 'inet':
|
||||
if 'address' in address:
|
||||
ip = address['address']
|
||||
name = item['name']
|
||||
|
||||
# Add the host to the results and the host array
|
||||
result[group]['hosts'].append(name)
|
||||
hosts[name] = ip
|
||||
|
||||
# Set the other containers result values
|
||||
result[group]['vars'] = {}
|
||||
result[group]['vars']['ansible_connection'] = connection
|
||||
|
||||
# Process arguments
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '--list':
|
||||
print(json.dumps(result))
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
|
||||
if sys.argv[2] == 'localhost':
|
||||
print(json.dumps({'ansible_connection': 'local'}))
|
||||
else:
|
||||
if connection == 'lxd':
|
||||
print(json.dumps({'ansible_connection': connection}))
|
||||
else:
|
||||
print(json.dumps({'ansible_connection': connection, 'ansible_host': hosts[sys.argv[2]]}))
|
||||
else:
|
||||
print("Need an argument, either --list or --host <host>")
|
||||
@ -32,7 +32,6 @@ try:
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
|
||||
class MDTInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -96,7 +95,7 @@ class MDTInventory(object):
|
||||
'''
|
||||
Create empty inventory dictionary
|
||||
'''
|
||||
return {"_meta": {"hostvars": {}}}
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
def read_settings(self):
|
||||
'''
|
||||
@ -120,6 +119,7 @@ class MDTInventory(object):
|
||||
if config.has_option('tower', 'groupname'):
|
||||
self.mdt_groupname = config.get('tower', 'groupname')
|
||||
|
||||
|
||||
def parse_cli_args(self):
|
||||
'''
|
||||
Command line argument processing
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
# default field group for host: groups
|
||||
# Uncomment to override:
|
||||
# group_field=state
|
||||
# default fields retrieved: address, alias, display_name, children, parents
|
||||
# default fields retrieved: address, alias, display_name, childs, parents
|
||||
# To override, uncomment the following line
|
||||
# fields_to_retrieve=address,alias,display_name
|
||||
#
|
||||
|
||||
@ -47,7 +47,6 @@ except ImportError:
|
||||
print("Error: mk_livestatus is needed. Try something like: pip install python-mk-livestatus")
|
||||
exit(1)
|
||||
|
||||
|
||||
class NagiosLivestatusInventory(object):
|
||||
|
||||
def parse_ini_file(self):
|
||||
@ -81,19 +80,19 @@ class NagiosLivestatusInventory(object):
|
||||
# Local unix socket
|
||||
unix_match = re.match('unix:(.*)', livestatus_uri)
|
||||
if unix_match is not None:
|
||||
backend_definition = {'connection': unix_match.group(1)}
|
||||
backend_definition = { 'connection': unix_match.group(1) }
|
||||
|
||||
# Remote tcp connection
|
||||
tcp_match = re.match('tcp:(.*):([^:]*)', livestatus_uri)
|
||||
if tcp_match is not None:
|
||||
backend_definition = {'connection': (tcp_match.group(1), int(tcp_match.group(2)))}
|
||||
backend_definition = { 'connection': (tcp_match.group(1), int(tcp_match.group(2))) }
|
||||
|
||||
# No valid livestatus_uri => exiting
|
||||
if backend_definition is None:
|
||||
raise Exception('livestatus_uri field is invalid (%s). Expected: unix:/path/to/live or tcp:host:port' % livestatus_uri)
|
||||
|
||||
# Updating backend_definition with current value
|
||||
backend_definition['name'] = section
|
||||
backend_definition['name'] = section
|
||||
backend_definition['fields'] = fields_to_retrieve
|
||||
for key, value in section_values.items():
|
||||
backend_definition[key] = value
|
||||
@ -102,8 +101,8 @@ class NagiosLivestatusInventory(object):
|
||||
|
||||
def parse_options(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host', nargs=1)
|
||||
parser.add_argument('--list', action='store_true')
|
||||
parser.add_argument('--host', nargs=1)
|
||||
parser.add_argument('--list', action='store_true')
|
||||
parser.add_argument('--pretty', action='store_true')
|
||||
self.options = parser.parse_args()
|
||||
|
||||
@ -114,7 +113,7 @@ class NagiosLivestatusInventory(object):
|
||||
if hostname not in self.result[group]['hosts']:
|
||||
self.result[group]['hosts'].append(hostname)
|
||||
|
||||
def query_backend(self, backend, host=None):
|
||||
def query_backend(self, backend, host = None):
|
||||
'''Query a livestatus backend'''
|
||||
hosts_request = Socket(backend['connection']).hosts.columns(backend['host_field'], backend['group_field'])
|
||||
|
||||
@ -128,10 +127,10 @@ class NagiosLivestatusInventory(object):
|
||||
|
||||
hosts = hosts_request.call()
|
||||
for host in hosts:
|
||||
hostname = host[backend['host_field']]
|
||||
hostname = host[backend['host_field']]
|
||||
hostgroups = host[backend['group_field']]
|
||||
if not isinstance(hostgroups, list):
|
||||
hostgroups = [hostgroups]
|
||||
hostgroups = [ hostgroups ]
|
||||
self.add_host(hostname, 'all')
|
||||
self.add_host(hostname, backend['name'])
|
||||
for group in hostgroups:
|
||||
@ -167,9 +166,9 @@ class NagiosLivestatusInventory(object):
|
||||
self.query_backend(backend, self.options.host)
|
||||
|
||||
if self.options.host:
|
||||
print(json.dumps(self.result['_meta']['hostvars'][self.options.host[0]], indent=self.json_indent))
|
||||
print(json.dumps(self.result['_meta']['hostvars'][self.options.host[0]], indent = self.json_indent))
|
||||
elif self.options.list:
|
||||
print(json.dumps(self.result, indent=self.json_indent))
|
||||
print(json.dumps(self.result, indent = self.json_indent))
|
||||
else:
|
||||
print("usage: --list or --host HOSTNAME [--pretty]")
|
||||
exit(1)
|
||||
|
||||
@ -42,7 +42,6 @@ except ImportError:
|
||||
print("Error: SQLAlchemy is needed. Try something like: pip install sqlalchemy")
|
||||
exit(1)
|
||||
|
||||
|
||||
class NagiosNDOInventory(object):
|
||||
|
||||
def read_settings(self):
|
||||
|
||||
@ -142,7 +142,7 @@ def get_metadata(server):
|
||||
key = 'os_' + re.sub(r"[^A-Za-z0-9\-]", "_", key).lower()
|
||||
|
||||
# Att value to instance result (exclude manager class)
|
||||
# TODO: maybe use value.__class__ or similar inside of key_name
|
||||
#TODO: maybe use value.__class__ or similar inside of key_name
|
||||
if key != 'os_manager':
|
||||
results[key] = value
|
||||
return results
|
||||
|
||||
@ -150,7 +150,6 @@ from click.exceptions import UsageError
|
||||
|
||||
from six import string_types
|
||||
|
||||
|
||||
def warning(*objs):
|
||||
print("WARNING: ", *objs, file=sys.stderr)
|
||||
|
||||
|
||||
@ -29,11 +29,8 @@
|
||||
# - /etc/openstack/clouds.yaml
|
||||
# - /etc/ansible/openstack.yml
|
||||
# The clouds.yaml file can contain entries for multiple clouds and multiple
|
||||
# regions of those clouds. If it does, this inventory module will by default
|
||||
# connect to all of them and present them as one contiguous inventory. You
|
||||
# can limit to one cloud by passing the `--cloud` parameter, or use the
|
||||
# OS_CLOUD environment variable. If caching is enabled, and a cloud is
|
||||
# selected, then per-cloud cache folders will be used.
|
||||
# regions of those clouds. If it does, this inventory module will connect to
|
||||
# all of them and present them as one contiguous inventory.
|
||||
#
|
||||
# See the adjacent openstack.yml file for an example config file
|
||||
# There are two ansible inventory specific options that can be set in
|
||||
@ -47,9 +44,6 @@
|
||||
# has failed (for example, bad credentials or being offline).
|
||||
# When set to False, the inventory will return hosts from
|
||||
# whichever other clouds it can contact. (Default: True)
|
||||
#
|
||||
# Also it is possible to pass the correct user by setting an ansible_user: $myuser
|
||||
# metadata attribute.
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
@ -114,8 +108,8 @@ def get_groups_from_server(server_vars, namegroup=True):
|
||||
return groups
|
||||
|
||||
|
||||
def get_host_groups(inventory, refresh=False, cloud=None):
|
||||
(cache_file, cache_expiration_time) = get_cache_settings(cloud)
|
||||
def get_host_groups(inventory, refresh=False):
|
||||
(cache_file, cache_expiration_time) = get_cache_settings()
|
||||
if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh):
|
||||
groups = to_json(get_host_groups_from_cloud(inventory))
|
||||
open(cache_file, 'w').write(groups)
|
||||
@ -129,11 +123,6 @@ def append_hostvars(hostvars, groups, key, server, namegroup=False):
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
ansible_host=server['interface_ip'],
|
||||
openstack=server)
|
||||
|
||||
metadata = server.get('metadata', {})
|
||||
if 'ansible_user' in metadata:
|
||||
hostvars[key]['ansible_user'] = metadata['ansible_user']
|
||||
|
||||
for group in get_groups_from_server(server, namegroup=namegroup):
|
||||
groups[group].append(key)
|
||||
|
||||
@ -188,14 +177,12 @@ def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
|
||||
return True
|
||||
|
||||
|
||||
def get_cache_settings(cloud=None):
|
||||
def get_cache_settings():
|
||||
config = os_client_config.config.OpenStackConfig(
|
||||
config_files=os_client_config.config.CONFIG_FILES + CONFIG_FILES)
|
||||
# For inventory-wide caching
|
||||
cache_expiration_time = config.get_cache_expiration_time()
|
||||
cache_path = config.get_cache_path()
|
||||
if cloud:
|
||||
cache_path = '{0}_{1}'.format(cache_path, cloud)
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
cache_file = os.path.join(cache_path, 'ansible-inventory.cache')
|
||||
@ -208,8 +195,6 @@ def to_json(in_dict):
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
|
||||
parser.add_argument('--cloud', default=os.environ.get('OS_CLOUD'),
|
||||
help='Cloud name (default: None')
|
||||
parser.add_argument('--private',
|
||||
action='store_true',
|
||||
help='Use private address for ansible host')
|
||||
@ -234,7 +219,6 @@ def main():
|
||||
refresh=args.refresh,
|
||||
config_files=config_files,
|
||||
private=args.private,
|
||||
cloud=args.cloud,
|
||||
)
|
||||
if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
|
||||
inventory_args.update(dict(
|
||||
@ -249,7 +233,7 @@ def main():
|
||||
inventory = shade.inventory.OpenStackInventory(**inventory_args)
|
||||
|
||||
if args.list:
|
||||
output = get_host_groups(inventory, refresh=args.refresh, cloud=args.cloud)
|
||||
output = get_host_groups(inventory, refresh=args.refresh)
|
||||
elif args.host:
|
||||
output = to_json(inventory.get_host(args.host))
|
||||
print(output)
|
||||
|
||||
@ -26,31 +26,30 @@
|
||||
# Groups are determined by the description field of openvz guests
|
||||
# multiple groups can be separated by commas: webserver,dbserver
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen,PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
# List openvz hosts
|
||||
vzhosts = ['vzhost1', 'vzhost2', 'vzhost3']
|
||||
# Add openvz hosts to the inventory and Add "_meta" trick
|
||||
#List openvz hosts
|
||||
vzhosts = ['vzhost1','vzhost2','vzhost3']
|
||||
#Add openvz hosts to the inventory and Add "_meta" trick
|
||||
inventory = {'vzhosts': {'hosts': vzhosts}, '_meta': {'hostvars': {}}}
|
||||
# default group, when description not defined
|
||||
#default group, when description not defined
|
||||
default_group = ['vzguest']
|
||||
|
||||
|
||||
def get_guests():
|
||||
# Loop through vzhosts
|
||||
#Loop through vzhosts
|
||||
for h in vzhosts:
|
||||
# SSH to vzhost and get the list of guests in json
|
||||
pipe = Popen(['ssh', h, 'vzlist', '-j'], stdout=PIPE, universal_newlines=True)
|
||||
#SSH to vzhost and get the list of guests in json
|
||||
pipe = Popen(['ssh', h,'vzlist','-j'], stdout=PIPE, universal_newlines=True)
|
||||
|
||||
# Load Json info of guests
|
||||
#Load Json info of guests
|
||||
json_data = json.loads(pipe.stdout.read())
|
||||
|
||||
# loop through guests
|
||||
#loop through guests
|
||||
for j in json_data:
|
||||
# Add information to host vars
|
||||
#Add information to host vars
|
||||
inventory['_meta']['hostvars'][j['hostname']] = {
|
||||
'ctid': j['ctid'],
|
||||
'veid': j['veid'],
|
||||
@ -60,13 +59,13 @@ def get_guests():
|
||||
'ip': j['ip']
|
||||
}
|
||||
|
||||
# determine group from guest description
|
||||
#determine group from guest description
|
||||
if j['description'] is not None:
|
||||
groups = j['description'].split(",")
|
||||
else:
|
||||
groups = default_group
|
||||
|
||||
# add guest to inventory
|
||||
#add guest to inventory
|
||||
for g in groups:
|
||||
if g not in inventory:
|
||||
inventory[g] = {'hosts': []}
|
||||
|
||||
@ -230,7 +230,7 @@ class OVirtInventory(object):
|
||||
"""
|
||||
return [x.get_name() for x in inst.get_tags().list()]
|
||||
|
||||
def get_machine_type(self, inst):
|
||||
def get_machine_type(self,inst):
|
||||
inst_type = inst.get_instance_type()
|
||||
if inst_type:
|
||||
return self.driver.instancetypes.get(id=inst_type.id).name
|
||||
|
||||
@ -182,9 +182,9 @@ def get_dict_of_struct(connection, vm):
|
||||
(stat.name, stat.values[0].datum) for stat in stats
|
||||
),
|
||||
'devices': dict(
|
||||
(device.name, [ip.address for ip in device.ips]) for device in devices if device.ips
|
||||
(device.name, [ip.address for ip in device.ips]) for device in devices
|
||||
),
|
||||
'ansible_host': next((device.ips[0].address for device in devices if device.ips), None)
|
||||
'ansible_host': devices[0].ips[0].address if len(devices) > 0 else None,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -37,7 +37,6 @@ replace_dash_in_groups = True
|
||||
# The packet inventory output can become very large. To manage its size,
|
||||
# configure which groups should be created.
|
||||
group_by_device_id = True
|
||||
group_by_hostname = True
|
||||
group_by_facility = True
|
||||
group_by_project = True
|
||||
group_by_operating_system = True
|
||||
|
||||
@ -63,11 +63,10 @@ except ImportError:
|
||||
|
||||
ini_section = 'packet'
|
||||
|
||||
|
||||
class PacketInventory(object):
|
||||
|
||||
def _empty_inventory(self):
|
||||
return {"_meta": {"hostvars": {}}}
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
def __init__(self):
|
||||
''' Main execution path '''
|
||||
@ -102,6 +101,7 @@ class PacketInventory(object):
|
||||
|
||||
print(data_to_print)
|
||||
|
||||
|
||||
def is_cache_valid(self):
|
||||
''' Determines if the cache files have expired, or if it is still valid '''
|
||||
|
||||
@ -175,7 +175,6 @@ class PacketInventory(object):
|
||||
# Configure which groups should be created.
|
||||
group_by_options = [
|
||||
'group_by_device_id',
|
||||
'group_by_hostname',
|
||||
'group_by_facility',
|
||||
'group_by_project',
|
||||
'group_by_operating_system',
|
||||
@ -225,13 +224,14 @@ class PacketInventory(object):
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Packet')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List Devices (default: True)')
|
||||
help='List Devices (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific device')
|
||||
help='Get all the variables about a specific device')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to Packet (default: False - use cache files)')
|
||||
help='Force refresh of cache by making API requests to Packet (default: False - use cache files)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
|
||||
def do_api_calls_update_cache(self):
|
||||
''' Do API calls to each region, and save data in cache files '''
|
||||
|
||||
@ -244,7 +244,7 @@ class PacketInventory(object):
|
||||
|
||||
def connect(self):
|
||||
''' create connection to api server'''
|
||||
token = os.environ.get('PACKET_API_TOKEN')
|
||||
token=os.environ.get('PACKET_API_TOKEN')
|
||||
if token is None:
|
||||
raise Exception("Error reading token from environment (PACKET_API_TOKEN)!")
|
||||
manager = packet.Manager(auth_token=token)
|
||||
@ -270,7 +270,7 @@ class PacketInventory(object):
|
||||
|
||||
try:
|
||||
manager = self.connect()
|
||||
devices = manager.list_devices(project_id=project.id, params=params)
|
||||
devices = manager.list_devices(project_id=project.id, params = params)
|
||||
|
||||
for device in devices:
|
||||
self.add_device(device, project)
|
||||
@ -307,6 +307,7 @@ class PacketInventory(object):
|
||||
if ip_address['public'] is True and ip_address['address_family'] == 4:
|
||||
dest = ip_address['address']
|
||||
|
||||
|
||||
if not dest:
|
||||
# Skip devices we cannot address (e.g. private VPC subnet)
|
||||
return
|
||||
@ -328,12 +329,6 @@ class PacketInventory(object):
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'devices', device.id)
|
||||
|
||||
# Inventory: Group by device name (hopefully a group of 1)
|
||||
if self.group_by_hostname:
|
||||
self.push(self.inventory, device.hostname, dest)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'hostnames', project.name)
|
||||
|
||||
# Inventory: Group by project
|
||||
if self.group_by_project:
|
||||
self.push(self.inventory, project.name, dest)
|
||||
@ -378,6 +373,7 @@ class PacketInventory(object):
|
||||
|
||||
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_device(device)
|
||||
|
||||
|
||||
def get_host_info_dict_from_device(self, device):
|
||||
device_vars = {}
|
||||
for key in vars(device):
|
||||
@ -389,7 +385,7 @@ class PacketInventory(object):
|
||||
device_vars[key] = device.state or ''
|
||||
elif key == 'packet_hostname':
|
||||
device_vars[key] = value
|
||||
elif isinstance(value, (int, bool)):
|
||||
elif type(value) in [int, bool]:
|
||||
device_vars[key] = value
|
||||
elif isinstance(value, six.string_types):
|
||||
device_vars[key] = value.strip()
|
||||
@ -407,9 +403,9 @@ class PacketInventory(object):
|
||||
device_vars[key] = k
|
||||
else:
|
||||
pass
|
||||
# print key
|
||||
# print type(value)
|
||||
# print value
|
||||
#print key
|
||||
#print type(value)
|
||||
#print value
|
||||
|
||||
return device_vars
|
||||
|
||||
@ -420,10 +416,10 @@ class PacketInventory(object):
|
||||
# Need to load index from cache
|
||||
self.load_index_from_cache()
|
||||
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# try updating the cache
|
||||
self.do_api_calls_update_cache()
|
||||
if self.args.host not in self.index:
|
||||
if not self.args.host in self.index:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
@ -459,6 +455,7 @@ class PacketInventory(object):
|
||||
json_inventory = cache.read()
|
||||
return json_inventory
|
||||
|
||||
|
||||
def load_index_from_cache(self):
|
||||
''' Reads the index from the cache file sets self.index '''
|
||||
|
||||
@ -466,6 +463,7 @@ class PacketInventory(object):
|
||||
json_index = cache.read()
|
||||
self.index = json.loads(json_index)
|
||||
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
''' Writes data in JSON format to a file '''
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#
|
||||
# { "groups": ["utility", "databases"], "a": false, "b": true }
|
||||
|
||||
import urllib
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
@ -34,16 +35,13 @@ import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
from six import iteritems
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
class ProxmoxNodeList(list):
|
||||
def get_names(self):
|
||||
return [node['node'] for node in self]
|
||||
|
||||
|
||||
class ProxmoxVM(dict):
|
||||
def get_variables(self):
|
||||
variables = {}
|
||||
@ -51,11 +49,8 @@ class ProxmoxVM(dict):
|
||||
variables['proxmox_' + key] = value
|
||||
return variables
|
||||
|
||||
|
||||
class ProxmoxVMList(list):
|
||||
def __init__(self, data=None):
|
||||
data = [] if data is None else data
|
||||
|
||||
def __init__(self, data=[]):
|
||||
for item in data:
|
||||
self.append(ProxmoxVM(item))
|
||||
|
||||
@ -73,17 +68,14 @@ class ProxmoxVMList(list):
|
||||
|
||||
return variables
|
||||
|
||||
|
||||
class ProxmoxPoolList(list):
|
||||
def get_names(self):
|
||||
return [pool['poolid'] for pool in self]
|
||||
|
||||
|
||||
class ProxmoxPool(dict):
|
||||
def get_members_name(self):
|
||||
return [member['name'] for member in self['members'] if member['template'] != 1]
|
||||
|
||||
|
||||
class ProxmoxAPI(object):
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
@ -99,7 +91,7 @@ class ProxmoxAPI(object):
|
||||
def auth(self):
|
||||
request_path = '{}api2/json/access/ticket'.format(self.options.url)
|
||||
|
||||
request_params = urlencode({
|
||||
request_params = urllib.urlencode({
|
||||
'username': self.options.username,
|
||||
'password': self.options.password,
|
||||
})
|
||||
@ -147,7 +139,6 @@ class ProxmoxAPI(object):
|
||||
def pool(self, poolid):
|
||||
return ProxmoxPool(self.get('api2/json/pools/{}'.format(poolid)))
|
||||
|
||||
|
||||
def main_list(options):
|
||||
results = {
|
||||
'all': {
|
||||
@ -208,7 +199,6 @@ def main_list(options):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main_host(options):
|
||||
proxmox_api = ProxmoxAPI(options)
|
||||
proxmox_api.auth()
|
||||
@ -221,7 +211,6 @@ def main_host(options):
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
parser = OptionParser(usage='%prog [options] --list | --host HOSTNAME')
|
||||
parser.add_option('--list', action="store_true", default=False, dest="list")
|
||||
@ -246,6 +235,5 @@ def main():
|
||||
|
||||
print(json.dumps(data, indent=indent))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -1,46 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import os
|
||||
import argparse
|
||||
import types
|
||||
|
||||
RACKHD_URL = 'http://localhost:8080'
|
||||
|
||||
|
||||
class RackhdInventory(object):
|
||||
def __init__(self, nodeids):
|
||||
self._inventory = {}
|
||||
for nodeid in nodeids:
|
||||
self._load_inventory_data(nodeid)
|
||||
inventory = {}
|
||||
for (nodeid, info) in self._inventory.items():
|
||||
inventory[nodeid] = (self._format_output(nodeid, info))
|
||||
for nodeid,info in self._inventory.items():
|
||||
inventory[nodeid]= (self._format_output(nodeid, info))
|
||||
print(json.dumps(inventory))
|
||||
|
||||
def _load_inventory_data(self, nodeid):
|
||||
info = {}
|
||||
info['ohai'] = RACKHD_URL + '/api/common/nodes/{0}/catalogs/ohai'.format(nodeid)
|
||||
info['ohai'] = RACKHD_URL + '/api/common/nodes/{0}/catalogs/ohai'.format(nodeid )
|
||||
info['lookup'] = RACKHD_URL + '/api/common/lookups/?q={0}'.format(nodeid)
|
||||
|
||||
results = {}
|
||||
for (key, url) in info.items():
|
||||
r = requests.get(url, verify=False)
|
||||
for key,url in info.items():
|
||||
r = requests.get( url, verify=False)
|
||||
results[key] = r.text
|
||||
self._inventory[nodeid] = results
|
||||
|
||||
@ -50,8 +35,8 @@ class RackhdInventory(object):
|
||||
ipaddress = ''
|
||||
if len(node_info) > 0:
|
||||
ipaddress = node_info[0]['ipAddress']
|
||||
output = {'hosts': [ipaddress], 'vars': {}}
|
||||
for (key, result) in info.items():
|
||||
output = { 'hosts':[ipaddress],'vars':{}}
|
||||
for key,result in info.items():
|
||||
output['vars'][key] = json.loads(result)
|
||||
output['vars']['ansible_ssh_user'] = 'monorail'
|
||||
except KeyError:
|
||||
@ -65,12 +50,11 @@ def parse_args():
|
||||
parser.add_argument('--list', action='store_true')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
try:
|
||||
# check if rackhd url(ie:10.1.1.45:8080) is specified in the environment
|
||||
#check if rackhd url(ie:10.1.1.45:8080) is specified in the environment
|
||||
RACKHD_URL = 'http://' + str(os.environ['RACKHD_URL'])
|
||||
except:
|
||||
# use default values
|
||||
#use default values
|
||||
pass
|
||||
|
||||
# Use the nodeid specified in the environment to limit the data returned
|
||||
@ -86,7 +70,7 @@ if (parse_args().host):
|
||||
if (parse_args().list):
|
||||
try:
|
||||
url = RACKHD_URL + '/api/common/nodes'
|
||||
r = requests.get(url, verify=False)
|
||||
r = requests.get( url, verify=False)
|
||||
data = json.loads(r.text)
|
||||
for entry in data:
|
||||
if entry['type'] == 'compute':
|
||||
|
||||
@ -168,11 +168,10 @@ except ImportError:
|
||||
|
||||
from time import time
|
||||
|
||||
from ansible.constants import get_config
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils.six import text_type
|
||||
from ansible.constants import get_config, mk_boolean
|
||||
|
||||
NON_CALLABLES = (text_type, str, bool, dict, int, list, type(None))
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
|
||||
|
||||
|
||||
def load_config_file():
|
||||
@ -230,22 +229,18 @@ def _list_into_cache(regions):
|
||||
try:
|
||||
# Ansible 2.3+
|
||||
networks = get_config(p, 'rax', 'access_network',
|
||||
'RAX_ACCESS_NETWORK', 'public', value_type='list')
|
||||
'RAX_ACCESS_NETWORK', 'public', value_type='list')
|
||||
except TypeError:
|
||||
# Ansible 2.2.x and below
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
networks = get_config(p, 'rax', 'access_network',
|
||||
'RAX_ACCESS_NETWORK', 'public', islist=True)
|
||||
'RAX_ACCESS_NETWORK', 'public', islist=True)
|
||||
try:
|
||||
try:
|
||||
# Ansible 2.3+
|
||||
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
|
||||
'RAX_ACCESS_IP_VERSION', 4, value_type='list'))
|
||||
'RAX_ACCESS_IP_VERSION', 4, value_type='list'))
|
||||
except TypeError:
|
||||
# Ansible 2.2.x and below
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
|
||||
'RAX_ACCESS_IP_VERSION', 4, islist=True))
|
||||
'RAX_ACCESS_IP_VERSION', 4, islist=True))
|
||||
except:
|
||||
ip_versions = [4]
|
||||
else:
|
||||
@ -293,7 +288,7 @@ def _list_into_cache(regions):
|
||||
if not cbs_attachments[region]:
|
||||
cbs = pyrax.connect_to_cloud_blockstorage(region)
|
||||
for vol in cbs.list():
|
||||
if boolean(vol.bootable, strict=False):
|
||||
if mk_boolean(vol.bootable):
|
||||
for attachment in vol.attachments:
|
||||
metadata = vol.volume_image_metadata
|
||||
server_id = attachment['server_id']
|
||||
@ -439,12 +434,11 @@ def setup():
|
||||
try:
|
||||
# Ansible 2.3+
|
||||
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
|
||||
value_type='list')
|
||||
value_type='list')
|
||||
except TypeError:
|
||||
# Ansible 2.2.x and below
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
|
||||
islist=True)
|
||||
islist=True)
|
||||
|
||||
for region in region_list:
|
||||
region = region.strip().upper()
|
||||
|
||||
@ -41,7 +41,6 @@ try:
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class SoftLayerInventory(object):
|
||||
common_items = [
|
||||
'id',
|
||||
@ -53,8 +52,7 @@ class SoftLayerInventory(object):
|
||||
'primaryIpAddress',
|
||||
'datacenter',
|
||||
'tagReferences.tag.name',
|
||||
'userData.value',
|
||||
]
|
||||
]
|
||||
|
||||
vs_items = [
|
||||
'lastKnownPowerState.name',
|
||||
@ -63,16 +61,16 @@ class SoftLayerInventory(object):
|
||||
'maxMemory',
|
||||
'activeTransaction.transactionStatus[friendlyName,name]',
|
||||
'status',
|
||||
]
|
||||
]
|
||||
|
||||
hw_items = [
|
||||
'hardwareStatusId',
|
||||
'processorPhysicalCoreAmount',
|
||||
'memoryCapacity',
|
||||
]
|
||||
]
|
||||
|
||||
def _empty_inventory(self):
|
||||
return {"_meta": {"hostvars": {}}}
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
def __init__(self):
|
||||
'''Main path'''
|
||||
@ -106,9 +104,9 @@ class SoftLayerInventory(object):
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer')
|
||||
parser.add_argument('--list', action='store_true', default=False,
|
||||
help='List instances (default: False)')
|
||||
help='List instances (default: False)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific instance')
|
||||
help='Get all the variables about a specific instance')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
@ -138,8 +136,6 @@ class SoftLayerInventory(object):
|
||||
if 'primaryIpAddress' not in instance:
|
||||
return
|
||||
|
||||
instance['userData'] = instance['userData'][0]['value'] if instance['userData'] else ''
|
||||
|
||||
dest = instance['primaryIpAddress']
|
||||
|
||||
self.inventory["_meta"]["hostvars"][dest] = instance
|
||||
@ -178,7 +174,7 @@ class SoftLayerInventory(object):
|
||||
def get_virtual_servers(self):
|
||||
'''Get all the CCI instances'''
|
||||
vs = SoftLayer.VSManager(self.client)
|
||||
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.vs_items))
|
||||
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items,self.vs_items))
|
||||
instances = vs.list_instances(mask=mask)
|
||||
|
||||
for instance in instances:
|
||||
@ -187,7 +183,7 @@ class SoftLayerInventory(object):
|
||||
def get_physical_servers(self):
|
||||
'''Get all the hardware instances'''
|
||||
hw = SoftLayer.HardwareManager(self.client)
|
||||
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.hw_items))
|
||||
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items,self.hw_items))
|
||||
instances = hw.list_hardware(mask=mask)
|
||||
|
||||
for instance in instances:
|
||||
|
||||
@ -56,14 +56,11 @@ try:
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
base_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
default_ini_file = os.path.join(base_dir, "spacewalk.ini")
|
||||
|
||||
base_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
SW_REPORT = '/usr/bin/spacewalk-report'
|
||||
CACHE_DIR = os.path.join(base_dir, ".spacewalk_reports")
|
||||
CACHE_AGE = 300 # 5min
|
||||
INI_FILE = os.path.expanduser(os.path.expandvars(os.environ.get("SPACEWALK_INI_PATH", default_ini_file)))
|
||||
|
||||
CACHE_AGE = 300 # 5min
|
||||
INI_FILE = os.path.join(base_dir, "spacewalk.ini")
|
||||
|
||||
# Sanity check
|
||||
if not os.path.exists(SW_REPORT):
|
||||
@ -76,8 +73,7 @@ if not os.path.exists(CACHE_DIR):
|
||||
os.chmod(CACHE_DIR, 0o2775)
|
||||
|
||||
# Helper functions
|
||||
# ------------------------------
|
||||
|
||||
#------------------------------
|
||||
|
||||
def spacewalk_report(name):
|
||||
"""Yield a dictionary form of each CSV output produced by the specified
|
||||
@ -95,7 +91,7 @@ def spacewalk_report(name):
|
||||
lines = open(cache_filename, 'r').readlines()
|
||||
keys = lines[0].strip().split(',')
|
||||
# add 'spacewalk_' prefix to the keys
|
||||
keys = ['spacewalk_' + key for key in keys]
|
||||
keys = [ 'spacewalk_' + key for key in keys ]
|
||||
for line in lines[1:]:
|
||||
values = line.strip().split(',')
|
||||
if len(keys) == len(values):
|
||||
@ -103,7 +99,7 @@ def spacewalk_report(name):
|
||||
|
||||
|
||||
# Options
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
|
||||
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
|
||||
parser.add_option('--list', default=False, dest="list", action="store_true",
|
||||
@ -121,20 +117,20 @@ parser.add_option('-p', default=False, dest="prefix_org_name", action="store_tru
|
||||
|
||||
|
||||
# read spacewalk.ini if present
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
if os.path.exists(INI_FILE):
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(INI_FILE)
|
||||
if config.has_option('spacewalk', 'cache_age'):
|
||||
CACHE_AGE = config.get('spacewalk', 'cache_age')
|
||||
if not options.org_number and config.has_option('spacewalk', 'org_number'):
|
||||
options.org_number = config.get('spacewalk', 'org_number')
|
||||
if not options.prefix_org_name and config.has_option('spacewalk', 'prefix_org_name'):
|
||||
options.prefix_org_name = config.getboolean('spacewalk', 'prefix_org_name')
|
||||
if config.has_option('spacewalk' , 'cache_age'):
|
||||
CACHE_AGE = config.get('spacewalk' , 'cache_age')
|
||||
if not options.org_number and config.has_option('spacewalk' , 'org_number'):
|
||||
options.org_number = config.get('spacewalk' , 'org_number')
|
||||
if not options.prefix_org_name and config.has_option('spacewalk' , 'prefix_org_name'):
|
||||
options.prefix_org_name = config.getboolean('spacewalk' , 'prefix_org_name')
|
||||
|
||||
|
||||
# Generate dictionary for mapping group_id to org_id
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
org_groups = {}
|
||||
try:
|
||||
for group in spacewalk_report('system-groups'):
|
||||
@ -147,14 +143,14 @@ except (OSError) as e:
|
||||
|
||||
|
||||
# List out the known server from Spacewalk
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
if options.list:
|
||||
|
||||
# to build the "_meta"-Group with hostvars first create dictionary for later use
|
||||
host_vars = {}
|
||||
try:
|
||||
for item in spacewalk_report('inventory'):
|
||||
host_vars[item['spacewalk_profile_name']] = dict((key, (value.split(';') if ';' in value else value)) for key, value in item.items())
|
||||
host_vars[ item['spacewalk_profile_name'] ] = dict( ( key, ( value.split(';') if ';' in value else value) ) for key, value in item.items() )
|
||||
|
||||
except (OSError) as e:
|
||||
print('Problem executing the command "%s inventory": %s' %
|
||||
@ -162,11 +158,11 @@ if options.list:
|
||||
sys.exit(2)
|
||||
|
||||
groups = {}
|
||||
meta = {"hostvars": {}}
|
||||
meta = { "hostvars" : {} }
|
||||
try:
|
||||
for system in spacewalk_report('system-groups-systems'):
|
||||
# first get org_id of system
|
||||
org_id = org_groups[system['spacewalk_group_id']]
|
||||
org_id = org_groups[ system['spacewalk_group_id'] ]
|
||||
|
||||
# shall we add the org_id as prefix to the group name:
|
||||
if options.prefix_org_name:
|
||||
@ -182,16 +178,16 @@ if options.list:
|
||||
groups[group_name] = set()
|
||||
|
||||
groups[group_name].add(system['spacewalk_server_name'])
|
||||
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]:
|
||||
meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']]
|
||||
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta[ "hostvars" ]:
|
||||
meta[ "hostvars" ][ system['spacewalk_server_name'] ] = host_vars[ system['spacewalk_server_name'] ]
|
||||
# or we list all groups and systems:
|
||||
else:
|
||||
if group_name not in groups:
|
||||
groups[group_name] = set()
|
||||
|
||||
groups[group_name].add(system['spacewalk_server_name'])
|
||||
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]:
|
||||
meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']]
|
||||
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta[ "hostvars" ]:
|
||||
meta[ "hostvars" ][ system['spacewalk_server_name'] ] = host_vars[ system['spacewalk_server_name'] ]
|
||||
|
||||
except (OSError) as e:
|
||||
print('Problem executing the command "%s system-groups-systems": %s' %
|
||||
@ -202,15 +198,15 @@ if options.list:
|
||||
for group, systems in iteritems(groups):
|
||||
print('[%s]\n%s\n' % (group, '\n'.join(systems)))
|
||||
else:
|
||||
final = dict([(k, list(s)) for k, s in iteritems(groups)])
|
||||
final = dict( [ (k, list(s)) for k, s in iteritems(groups) ] )
|
||||
final["_meta"] = meta
|
||||
print(json.dumps(final))
|
||||
# print(json.dumps(groups))
|
||||
print(json.dumps( final ))
|
||||
#print(json.dumps(groups))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Return a details information concerning the spacewalk server
|
||||
# ------------------------------
|
||||
#------------------------------
|
||||
elif options.host:
|
||||
|
||||
host_details = {}
|
||||
@ -230,7 +226,7 @@ elif options.host:
|
||||
for k, v in iteritems(host_details):
|
||||
print(' %s: %s' % (k, '\n '.join(v.split(';'))))
|
||||
else:
|
||||
print(json.dumps(dict((key, (value.split(';') if ';' in value else value)) for key, value in host_details.items())))
|
||||
print( json.dumps( dict( ( key, ( value.split(';') if ';' in value else value) ) for key, value in host_details.items() ) ) )
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
|
||||
@ -43,24 +43,21 @@
|
||||
import argparse
|
||||
import os.path
|
||||
import sys
|
||||
from collections import MutableSequence
|
||||
import paramiko
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
import paramiko
|
||||
|
||||
|
||||
SSH_CONF = '~/.ssh/config'
|
||||
|
||||
_key = 'ssh_config'
|
||||
|
||||
_ssh_to_ansible = [('user', 'ansible_ssh_user'),
|
||||
('hostname', 'ansible_ssh_host'),
|
||||
('identityfile', 'ansible_ssh_private_key_file'),
|
||||
('port', 'ansible_ssh_port')]
|
||||
('hostname', 'ansible_ssh_host'),
|
||||
('identityfile', 'ansible_ssh_private_key_file'),
|
||||
('port', 'ansible_ssh_port')]
|
||||
|
||||
|
||||
def get_config():
|
||||
@ -71,7 +68,7 @@ def get_config():
|
||||
cfg.parse(f)
|
||||
ret_dict = {}
|
||||
for d in cfg._config:
|
||||
if isinstance(d['host'], MutableSequence):
|
||||
if type(d['host']) is list:
|
||||
alias = d['host'][0]
|
||||
else:
|
||||
alias = d['host']
|
||||
@ -96,7 +93,7 @@ def print_list():
|
||||
# If the attribute is a list, just take the first element.
|
||||
# Private key is returned in a list for some reason.
|
||||
attr = attributes[ssh_opt]
|
||||
if isinstance(attr, MutableSequence):
|
||||
if type(attr) is list:
|
||||
attr = attr[0]
|
||||
tmp_dict[ans_opt] = attr
|
||||
if tmp_dict:
|
||||
|
||||
@ -64,9 +64,9 @@ CONFIG_FILES = ['/etc/stacki/stacki.yml', '/etc/ansible/stacki.yml']
|
||||
|
||||
|
||||
def stack_auth(params):
|
||||
endpoint = params['stacki_endpoint']
|
||||
auth_creds = {'USERNAME': params['stacki_user'],
|
||||
'PASSWORD': params['stacki_password']}
|
||||
endpoint = params['stacki_endpoint']
|
||||
auth_creds = {'USERNAME': params['stacki_user'],
|
||||
'PASSWORD': params['stacki_password']}
|
||||
|
||||
client = requests.session()
|
||||
client.get(endpoint)
|
||||
@ -99,18 +99,17 @@ def stack_build_header(auth_creds):
|
||||
|
||||
def stack_host_list(endpoint, header, client):
|
||||
|
||||
stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host"}),
|
||||
stack_r = client.post(endpoint, data=json.dumps({ "cmd": "list host"}),
|
||||
headers=header)
|
||||
return json.loads(stack_r.json())
|
||||
|
||||
|
||||
def stack_net_list(endpoint, header, client):
|
||||
|
||||
stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host interface"}),
|
||||
stack_r = client.post(endpoint, data=json.dumps({ "cmd": "list host interface"}),
|
||||
headers=header)
|
||||
return json.loads(stack_r.json())
|
||||
|
||||
|
||||
def format_meta(hostdata, intfdata, config):
|
||||
use_hostnames = config['use_hostnames']
|
||||
meta = dict(all=dict(hosts=list()),
|
||||
@ -160,6 +159,7 @@ def parse_args():
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
|
||||
if StrictVersion(requests.__version__) < StrictVersion("2.4.3"):
|
||||
sys.exit('requests>=2.4.3 is required for this inventory script')
|
||||
|
||||
|
||||
@ -16,21 +16,20 @@
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen,PIPE
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class SetEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, set):
|
||||
return list(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
VBOX = "VBoxManage"
|
||||
VBOX="VBoxManage"
|
||||
|
||||
|
||||
def get_hosts(host=None):
|
||||
@ -40,7 +39,7 @@ def get_hosts(host=None):
|
||||
if host:
|
||||
p = Popen([VBOX, 'showvminfo', host], stdout=PIPE)
|
||||
else:
|
||||
returned = {'all': set(), '_metadata': {}}
|
||||
returned = { 'all': set(), '_metadata': {} }
|
||||
p = Popen([VBOX, 'list', '-l', 'vms'], stdout=PIPE)
|
||||
except:
|
||||
sys.exit(1)
|
||||
@ -51,7 +50,7 @@ def get_hosts(host=None):
|
||||
for line in p.stdout.readlines():
|
||||
|
||||
try:
|
||||
k, v = line.split(':', 1)
|
||||
k,v = line.split(':',1)
|
||||
except:
|
||||
continue
|
||||
|
||||
@ -63,11 +62,11 @@ def get_hosts(host=None):
|
||||
if v not in hostvars:
|
||||
curname = v
|
||||
hostvars[curname] = {}
|
||||
try: # try to get network info
|
||||
x = Popen([VBOX, 'guestproperty', 'get', curname, "/VirtualBox/GuestInfo/Net/0/V4/IP"], stdout=PIPE)
|
||||
try: # try to get network info
|
||||
x = Popen([VBOX, 'guestproperty', 'get', curname,"/VirtualBox/GuestInfo/Net/0/V4/IP"],stdout=PIPE)
|
||||
ipinfo = x.stdout.read()
|
||||
if 'Value' in ipinfo:
|
||||
a, ip = ipinfo.split(':', 1)
|
||||
a,ip = ipinfo.split(':',1)
|
||||
hostvars[curname]['ansible_ssh_host'] = ip.strip()
|
||||
except:
|
||||
pass
|
||||
@ -84,11 +83,11 @@ def get_hosts(host=None):
|
||||
returned['all'].add(curname)
|
||||
continue
|
||||
|
||||
pref_k = 'vbox_' + k.strip().replace(' ', '_')
|
||||
pref_k = 'vbox_' + k.strip().replace(' ','_')
|
||||
if k.startswith(' '):
|
||||
if prevkey not in hostvars[curname]:
|
||||
hostvars[curname][prevkey] = {}
|
||||
hostvars[curname][prevkey][pref_k] = v
|
||||
hostvars[curname][prevkey][pref_k]= v
|
||||
else:
|
||||
if v != '':
|
||||
hostvars[curname][pref_k] = v
|
||||
|
||||
@ -38,20 +38,18 @@ import os
|
||||
import ssl
|
||||
import sys
|
||||
import time
|
||||
import ConfigParser
|
||||
|
||||
from six import integer_types, text_type, string_types
|
||||
from six.moves import configparser
|
||||
from six import text_type, string_types
|
||||
|
||||
# Disable logging message trigged by pSphere/suds.
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
from logging import Handler
|
||||
|
||||
class NullHandler(Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logging.getLogger('psphere').addHandler(NullHandler())
|
||||
logging.getLogger('suds').addHandler(NullHandler())
|
||||
|
||||
@ -64,11 +62,11 @@ from suds.sudsobject import Object as SudsObject
|
||||
class VMwareInventory(object):
|
||||
|
||||
def __init__(self, guests_only=None):
|
||||
self.config = configparser.SafeConfigParser()
|
||||
self.config = ConfigParser.SafeConfigParser()
|
||||
if os.environ.get('VMWARE_INI', ''):
|
||||
config_files = [os.environ['VMWARE_INI']]
|
||||
else:
|
||||
config_files = [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']
|
||||
config_files = [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']
|
||||
for config_file in config_files:
|
||||
if os.path.exists(config_file):
|
||||
self.config.read(config_file)
|
||||
@ -210,7 +208,7 @@ class VMwareInventory(object):
|
||||
if obj_info != ():
|
||||
l.append(obj_info)
|
||||
return l
|
||||
elif isinstance(obj, (type(None), bool, float) + string_types + integer_types):
|
||||
elif isinstance(obj, (type(None), bool, int, long, float, string_types)):
|
||||
return obj
|
||||
else:
|
||||
return ()
|
||||
@ -364,7 +362,7 @@ class VMwareInventory(object):
|
||||
# Loop through all VMs on physical host.
|
||||
for vm in host.vm:
|
||||
if prefix_filter:
|
||||
if vm.name.startswith(prefix_filter):
|
||||
if vm.name.startswith( prefix_filter ):
|
||||
continue
|
||||
self._add_host(inv, 'all', vm.name)
|
||||
self._add_host(inv, vm_group, vm.name)
|
||||
|
||||
@ -1,19 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of Ansible,
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Requirements
|
||||
# - pyvmomi >= 6.0.0.2016.4
|
||||
@ -38,34 +23,34 @@ $ jq '._meta.hostvars[].config' data.json | head
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import datetime
|
||||
import getpass
|
||||
import itertools
|
||||
import json
|
||||
import jinja2
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import ssl
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from collections import defaultdict
|
||||
from six.moves import configparser
|
||||
from time import time
|
||||
|
||||
import six
|
||||
from jinja2 import Environment
|
||||
from six import integer_types, string_types
|
||||
from six.moves import configparser
|
||||
|
||||
HAS_PYVMOMI = False
|
||||
try:
|
||||
import argparse
|
||||
except ImportError:
|
||||
sys.exit('Error: This inventory script required "argparse" python module. Please install it or upgrade to python-2.7')
|
||||
|
||||
try:
|
||||
from pyVmomi import vim, vmodl
|
||||
from pyVmomi import vim
|
||||
from pyVim.connect import SmartConnect, Disconnect
|
||||
|
||||
HAS_PYVMOMI = True
|
||||
except ImportError:
|
||||
sys.exit("ERROR: This inventory script required 'pyVmomi' Python module, it was not able to load it")
|
||||
pass
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
hasvcr = False
|
||||
try:
|
||||
@ -76,23 +61,6 @@ except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def regex_match(s, pattern):
|
||||
'''Custom filter for regex matching'''
|
||||
reg = re.compile(pattern)
|
||||
if reg.match(s):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def select_chain_match(inlist, key, pattern):
|
||||
'''Get a key from a list of dicts, squash values to a single list, then filter'''
|
||||
outlist = [x[key] for x in inlist]
|
||||
outlist = list(itertools.chain(*outlist))
|
||||
outlist = [x for x in outlist if regex_match(x, pattern)]
|
||||
return outlist
|
||||
|
||||
|
||||
class VMwareMissingHostException(Exception):
|
||||
pass
|
||||
|
||||
@ -121,7 +89,10 @@ class VMWareInventory(object):
|
||||
skip_keys = []
|
||||
groupby_patterns = []
|
||||
|
||||
safe_types = [bool, str, float, None] + list(integer_types)
|
||||
if sys.version_info > (3, 0):
|
||||
safe_types = [int, bool, str, float, None]
|
||||
else:
|
||||
safe_types = [int, long, bool, str, float, None]
|
||||
iter_types = [dict, list]
|
||||
|
||||
bad_types = ['Array', 'disabledMethod', 'declaredAlarmState']
|
||||
@ -133,18 +104,15 @@ class VMWareInventory(object):
|
||||
|
||||
custom_fields = {}
|
||||
|
||||
# use jinja environments to allow for custom filters
|
||||
env = Environment()
|
||||
env.filters['regex_match'] = regex_match
|
||||
env.filters['select_chain_match'] = select_chain_match
|
||||
|
||||
# translation table for attributes to fetch for known vim types
|
||||
|
||||
vimTable = {
|
||||
vim.Datastore: ['_moId', 'name'],
|
||||
vim.ResourcePool: ['_moId', 'name'],
|
||||
vim.HostSystem: ['_moId', 'name'],
|
||||
}
|
||||
if not HAS_PYVMOMI:
|
||||
vimTable = {}
|
||||
else:
|
||||
vimTable = {
|
||||
vim.Datastore: ['_moId', 'name'],
|
||||
vim.ResourcePool: ['_moId', 'name'],
|
||||
vim.HostSystem: ['_moId', 'name'],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _empty_inventory():
|
||||
@ -365,21 +333,13 @@ class VMWareInventory(object):
|
||||
''' Make API calls '''
|
||||
|
||||
instances = []
|
||||
try:
|
||||
si = SmartConnect(**inkwargs)
|
||||
except ssl.SSLError as connection_error:
|
||||
if '[SSL: CERTIFICATE_VERIFY_FAILED]' in str(connection_error) and self.validate_certs:
|
||||
sys.exit("Unable to connect to ESXi server due to %s, "
|
||||
"please specify validate_certs=False and try again" % connection_error)
|
||||
|
||||
except Exception as exc:
|
||||
self.debugl("Unable to connect to ESXi server due to %s" % exc)
|
||||
sys.exit("Unable to connect to ESXi server due to %s" % exc)
|
||||
si = SmartConnect(**inkwargs)
|
||||
|
||||
self.debugl('retrieving all instances')
|
||||
if not si:
|
||||
sys.exit("Could not connect to the specified host using specified "
|
||||
"username and password")
|
||||
print("Could not connect to the specified host using specified "
|
||||
"username and password")
|
||||
return -1
|
||||
atexit.register(Disconnect, si)
|
||||
content = si.RetrieveContent()
|
||||
|
||||
@ -410,16 +370,12 @@ class VMWareInventory(object):
|
||||
instance_tuples.append((instance, ifacts))
|
||||
self.debugl('facts collected for all instances')
|
||||
|
||||
try:
|
||||
cfm = content.customFieldsManager
|
||||
if cfm is not None and cfm.field:
|
||||
for f in cfm.field:
|
||||
if f.managedObjectType == vim.VirtualMachine:
|
||||
self.custom_fields[f.key] = f.name
|
||||
self.debugl('%d custom fields collected' % len(self.custom_fields))
|
||||
except vmodl.RuntimeFault as exc:
|
||||
self.debugl("Unable to gather custom fields due to %s" % exc.msg)
|
||||
|
||||
cfm = content.customFieldsManager
|
||||
if cfm is not None and cfm.field:
|
||||
for f in cfm.field:
|
||||
if f.managedObjectType == vim.VirtualMachine:
|
||||
self.custom_fields[f.key] = f.name
|
||||
self.debugl('%d custom fieds collected' % len(self.custom_fields))
|
||||
return instance_tuples
|
||||
|
||||
def instances_to_inventory(self, instances):
|
||||
@ -456,7 +412,7 @@ class VMWareInventory(object):
|
||||
# Reset the inventory keys
|
||||
for k, v in name_mapping.items():
|
||||
|
||||
if not host_mapping or k not in host_mapping:
|
||||
if not host_mapping or not k in host_mapping:
|
||||
continue
|
||||
|
||||
# set ansible_host (2.x)
|
||||
@ -511,7 +467,7 @@ class VMWareInventory(object):
|
||||
for k, v in inventory['_meta']['hostvars'].items():
|
||||
if 'customvalue' in v:
|
||||
for tv in v['customvalue']:
|
||||
if not isinstance(tv['value'], string_types):
|
||||
if not isinstance(tv['value'], str) and not isinstance(tv['value'], unicode):
|
||||
continue
|
||||
|
||||
newkey = None
|
||||
@ -542,7 +498,7 @@ class VMWareInventory(object):
|
||||
|
||||
mapping = {}
|
||||
for k, v in inventory['_meta']['hostvars'].items():
|
||||
t = self.env.from_string(pattern)
|
||||
t = jinja2.Template(pattern)
|
||||
newkey = None
|
||||
try:
|
||||
newkey = t.render(v)
|
||||
@ -588,27 +544,15 @@ class VMWareInventory(object):
|
||||
|
||||
for idx, x in enumerate(parts):
|
||||
|
||||
if isinstance(val, dict):
|
||||
if x in val:
|
||||
val = val.get(x)
|
||||
elif x.lower() in val:
|
||||
val = val.get(x.lower())
|
||||
# if the val wasn't set yet, get it from the parent
|
||||
if not val:
|
||||
val = getattr(vm, x)
|
||||
else:
|
||||
# if the val wasn't set yet, get it from the parent
|
||||
if not val:
|
||||
try:
|
||||
val = getattr(vm, x)
|
||||
except AttributeError as e:
|
||||
self.debugl(e)
|
||||
else:
|
||||
# in a subkey, get the subprop from the previous attrib
|
||||
try:
|
||||
val = getattr(val, x)
|
||||
except AttributeError as e:
|
||||
self.debugl(e)
|
||||
|
||||
# make sure it serializes
|
||||
val = self._process_object_types(val)
|
||||
# in a subkey, get the subprop from the previous attrib
|
||||
try:
|
||||
val = getattr(val, x)
|
||||
except AttributeError as e:
|
||||
self.debugl(e)
|
||||
|
||||
# lowercase keys if requested
|
||||
if self.lowerkeys:
|
||||
@ -672,7 +616,7 @@ class VMWareInventory(object):
|
||||
|
||||
return rdata
|
||||
|
||||
def _process_object_types(self, vobj, thisvm=None, inkey='', level=0):
|
||||
def _process_object_types(self, vobj, thisvm=None, inkey=None, level=0):
|
||||
''' Serialize an object '''
|
||||
rdata = {}
|
||||
|
||||
@ -696,10 +640,12 @@ class VMWareInventory(object):
|
||||
rdata = vobj.decode('ascii', 'ignore')
|
||||
elif issubclass(type(vobj), bool) or isinstance(vobj, bool):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), integer_types) or isinstance(vobj, integer_types):
|
||||
elif issubclass(type(vobj), int) or isinstance(vobj, int):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), float) or isinstance(vobj, float):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), long) or isinstance(vobj, long):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), list) or issubclass(type(vobj), tuple):
|
||||
rdata = []
|
||||
try:
|
||||
@ -764,7 +710,7 @@ class VMWareInventory(object):
|
||||
return self.inventory['_meta']['hostvars'][host]
|
||||
elif self.args.host and self.inventory['_meta']['hostvars']:
|
||||
match = None
|
||||
for k, v in self.inventory['_meta']['hostvars'].items():
|
||||
for k, v in self.inventory['_meta']['hostvars']:
|
||||
if self.inventory['_meta']['hostvars'][k]['name'] == self.args.host:
|
||||
match = k
|
||||
break
|
||||
@ -779,3 +725,5 @@ class VMWareInventory(object):
|
||||
if __name__ == "__main__":
|
||||
# Run the script
|
||||
print(VMWareInventory().show())
|
||||
|
||||
|
||||
|
||||
@ -54,7 +54,6 @@ except ImportError as e:
|
||||
# Imports for ansible
|
||||
import ConfigParser
|
||||
|
||||
|
||||
class AzureInventory(object):
|
||||
def __init__(self):
|
||||
"""Main execution path."""
|
||||
@ -172,9 +171,10 @@ class AzureInventory(object):
|
||||
parser.add_argument('--list-images', action='store',
|
||||
help='Get all available images.')
|
||||
parser.add_argument('--refresh-cache',
|
||||
action='store_true', default=False,
|
||||
help='Force refresh of thecache by making API requests to Azure '
|
||||
'(default: False - use cache files)')
|
||||
action='store_true', default=False,
|
||||
help='Force refresh of thecache by making API requests to Azure '
|
||||
'(default: False - use cache files)',
|
||||
)
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all information about an instance.')
|
||||
self.args = parser.parse_args()
|
||||
@ -198,7 +198,7 @@ class AzureInventory(object):
|
||||
associated with a cloud service.
|
||||
"""
|
||||
try:
|
||||
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name, embed_detail=True).deployments.deployments:
|
||||
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
|
||||
self.add_deployment(cloud_service, deployment)
|
||||
except Exception as e:
|
||||
sys.exit("Error: Failed to access deployments - {0}".format(str(e)))
|
||||
|
||||
@ -24,12 +24,10 @@ Zabbix Server external inventory script.
|
||||
========================================
|
||||
|
||||
Returns hosts and hostgroups from Zabbix Server.
|
||||
If you want to run with --limit against a host group with space in the
|
||||
name, use asterisk. For example --limit="Linux*servers".
|
||||
|
||||
Configuration is read from `zabbix.ini`.
|
||||
|
||||
Tested with Zabbix Server 2.0.6 and 3.2.3.
|
||||
Tested with Zabbix Server 2.0.6.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
@ -37,10 +35,7 @@ from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError:
|
||||
import configparser
|
||||
import ConfigParser
|
||||
|
||||
try:
|
||||
from zabbix_api import ZabbixAPI
|
||||
@ -54,11 +49,10 @@ try:
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class ZabbixInventory(object):
|
||||
|
||||
def read_settings(self):
|
||||
config = configparser.SafeConfigParser()
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
conf_path = './zabbix.ini'
|
||||
if not os.path.exists(conf_path):
|
||||
conf_path = os.path.dirname(os.path.realpath(__file__)) + '/zabbix.ini'
|
||||
@ -102,14 +96,11 @@ class ZabbixInventory(object):
|
||||
for group in host['groups']:
|
||||
groupname = group['name']
|
||||
|
||||
if groupname not in data:
|
||||
if not groupname in data:
|
||||
data[groupname] = self.hoststub()
|
||||
|
||||
data[groupname]['hosts'].append(hostname)
|
||||
|
||||
# Prevents Ansible from calling this script for each server with --host
|
||||
data['_meta'] = {'hostvars': self.meta}
|
||||
|
||||
return data
|
||||
|
||||
def __init__(self):
|
||||
@ -118,7 +109,6 @@ class ZabbixInventory(object):
|
||||
self.zabbix_server = None
|
||||
self.zabbix_username = None
|
||||
self.zabbix_password = None
|
||||
self.meta = {}
|
||||
|
||||
self.read_settings()
|
||||
self.read_cli()
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen,PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
@ -14,9 +14,14 @@ PAPER =
|
||||
BUILDDIR = _build
|
||||
RSTDIR = rst
|
||||
MODULES_PATH = ../../lib
|
||||
EXCLUDE_PATHS = ../../lib/ansible/modules ../../lib/ansible/utils/module_docs_fragments ../../lib/ansible/module_utils/six
|
||||
EXCLUDE_PATHS = ../../lib/ansible/modules ../../lib/ansible/utils/module_docs_fragments ../../lib/ansible/compat/six ../../lib/ansible/module_utils/six.py
|
||||
DOC_PROJECTS = "Ansible API"
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
|
||||
108
docs/api/conf.py
108
docs/api/conf.py
@ -18,7 +18,7 @@ import os
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('../bin'))
|
||||
#sys.path.insert(0, os.path.abspath('../bin'))
|
||||
sys.path.insert(0, os.path.abspath('../lib/ansible'))
|
||||
import sphinx_rtd_theme
|
||||
import alabaster
|
||||
@ -26,7 +26,7 @@ import alabaster
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
@ -41,8 +41,8 @@ extensions = [
|
||||
'alabaster',
|
||||
]
|
||||
|
||||
# autodoc_default_flags = ['members', 'show-inheritance', 'inherited-members', 'undoc-members', ]
|
||||
autodoc_default_flags = ['members', 'show-inheritance', 'undoc-members', ]
|
||||
#autodoc_default_flags = ['members', 'show-inheritance', 'inherited-members', 'undoc-members',]
|
||||
autodoc_default_flags = ['members', 'show-inheritance', 'undoc-members',]
|
||||
autoclass_content = 'both'
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_mock_imports = ['xmltodict', 'winrm', 'redis', 'StricRedis']
|
||||
@ -56,7 +56,7 @@ templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
@ -84,9 +84,9 @@ language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@ -94,27 +94,27 @@ exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
@ -124,13 +124,13 @@ todo_include_todos = True
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
# html_theme = 'alabaster'
|
||||
# html_theme_path = ['../docsite/_themes']
|
||||
# html_theme = 'srtd'
|
||||
#html_theme = 'alabaster'
|
||||
#html_theme_path = ['../docsite/_themes']
|
||||
#html_theme = 'srtd'
|
||||
html_short_title = 'Ansible Documentation'
|
||||
|
||||
# html_theme = "sphinx_rtd_theme"
|
||||
# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
#html_theme = "sphinx_rtd_theme"
|
||||
#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
html_theme_path = [alabaster.get_path()]
|
||||
html_theme = 'alabaster'
|
||||
@ -138,26 +138,26 @@ html_theme = 'alabaster'
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
# html_theme_options = {}
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
@ -167,62 +167,62 @@ html_static_path = ['_static']
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
# html_search_language = 'en'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
# html_search_options = {'type': 'default'}
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
# html_search_scorer = 'scorer.js'
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Ansibledoc'
|
||||
@ -231,16 +231,16 @@ htmlhelp_basename = 'Ansibledoc'
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
@ -253,23 +253,23 @@ latex_documents = [
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
@ -282,7 +282,7 @@ man_pages = [
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
@ -297,13 +297,13 @@ texinfo_documents = [
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# texinfo_no_detailmenu = False
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
DEFAULT_TEMPLATE_FILE = 'config.rst.j2'
|
||||
|
||||
|
||||
def generate_parser():
|
||||
p = optparse.OptionParser(
|
||||
version='%prog 1.0',
|
||||
usage='usage: %prog [options]',
|
||||
description='Generate module documentation from metadata',
|
||||
)
|
||||
p.add_option("-t", "--template-file", action="store", dest="template_file", default=DEFAULT_TEMPLATE_FILE, help="directory containing Jinja2 templates")
|
||||
p.add_option("-o", "--output-dir", action="store", dest="output_dir", default='/tmp/', help="Output directory for rst files")
|
||||
p.add_option("-d", "--docs-source", action="store", dest="docs", default=None, help="Source for attribute docs")
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def fix_description(config_options):
|
||||
'''some descriptions are strings, some are lists. workaround it...'''
|
||||
|
||||
for config_key in config_options:
|
||||
description = config_options[config_key].get('description', [])
|
||||
if isinstance(description, list):
|
||||
desc_list = description
|
||||
else:
|
||||
desc_list = [description]
|
||||
config_options[config_key]['description'] = desc_list
|
||||
return config_options
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
parser = generate_parser()
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
output_dir = os.path.abspath(options.output_dir)
|
||||
template_file_full_path = os.path.abspath(options.template_file)
|
||||
template_file = os.path.basename(template_file_full_path)
|
||||
template_dir = os.path.dirname(os.path.abspath(template_file_full_path))
|
||||
|
||||
if options.docs:
|
||||
with open(options.docs) as f:
|
||||
docs = yaml.safe_load(f)
|
||||
else:
|
||||
docs = {}
|
||||
|
||||
config_options = docs
|
||||
config_options = fix_description(config_options)
|
||||
|
||||
env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True,)
|
||||
template = env.get_template(template_file)
|
||||
output_name = os.path.join(output_dir, template_file.replace('.j2', ''))
|
||||
temp_vars = {'config_options': config_options}
|
||||
|
||||
with open(output_name, 'w') as f:
|
||||
f.write(template.render(temp_vars).encode('utf-8'))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[:]))
|
||||
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import optparse
|
||||
import yaml
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from ansible.playbook import Play
|
||||
from ansible.playbook.block import Block
|
||||
from ansible.playbook.role import Role
|
||||
from ansible.playbook.task import Task
|
||||
|
||||
template_file = 'playbooks_keywords.rst.j2'
|
||||
oblist = {}
|
||||
clist = []
|
||||
class_list = [Play, Role, Block, Task]
|
||||
|
||||
p = optparse.OptionParser(
|
||||
version='%prog 1.0',
|
||||
usage='usage: %prog [options]',
|
||||
description='Generate module documentation from metadata',
|
||||
)
|
||||
p.add_option("-T", "--template-dir", action="store", dest="template_dir", default="../templates", help="directory containing Jinja2 templates")
|
||||
p.add_option("-o", "--output-dir", action="store", dest="output_dir", default='/tmp/', help="Output directory for rst files")
|
||||
p.add_option("-d", "--docs-source", action="store", dest="docs", default=None, help="Source for attribute docs")
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
|
||||
for aclass in class_list:
|
||||
aobj = aclass()
|
||||
name = type(aobj).__name__
|
||||
|
||||
if options.docs:
|
||||
with open(options.docs) as f:
|
||||
docs = yaml.safe_load(f)
|
||||
else:
|
||||
docs = {}
|
||||
|
||||
# build ordered list to loop over and dict with attributes
|
||||
clist.append(name)
|
||||
oblist[name] = dict((x, aobj.__dict__['_attributes'][x]) for x in aobj.__dict__['_attributes'] if 'private' not in x or not x.private)
|
||||
|
||||
# pick up docs if they exist
|
||||
for a in oblist[name]:
|
||||
if a in docs:
|
||||
oblist[name][a] = docs[a]
|
||||
else:
|
||||
oblist[name][a] = ' UNDOCUMENTED!! '
|
||||
|
||||
# loop is really with_ for users
|
||||
if name == 'Task':
|
||||
oblist[name]['with_<lookup_plugin>'] = 'with_ is how loops are defined, it can use any available lookup plugin to generate the item list'
|
||||
|
||||
# local_action is implicit with action
|
||||
if 'action' in oblist[name]:
|
||||
oblist[name]['local_action'] = 'Same as action but also implies ``delegate_to: localhost``'
|
||||
|
||||
# remove unusable (used to be private?)
|
||||
for nouse in ('loop', 'loop_args'):
|
||||
if nouse in oblist[name]:
|
||||
del oblist[name][nouse]
|
||||
|
||||
env = Environment(loader=FileSystemLoader(options.template_dir), trim_blocks=True,)
|
||||
template = env.get_template(template_file)
|
||||
outputname = options.output_dir + template_file.replace('.j2', '')
|
||||
tempvars = {'oblist': oblist, 'clist': clist}
|
||||
|
||||
with open(outputname, 'w') as f:
|
||||
f.write(template.render(tempvars))
|
||||
@ -1,280 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
|
||||
|
||||
def generate_parser():
|
||||
p = optparse.OptionParser(
|
||||
version='%prog 1.0',
|
||||
usage='usage: %prog [options]',
|
||||
description='Generate cli documentation from cli docstrings',
|
||||
)
|
||||
|
||||
p.add_option("-t", "--template-file", action="store", dest="template_file", default="../templates/man.j2", help="path to jinja2 template")
|
||||
p.add_option("-o", "--output-dir", action="store", dest="output_dir", default='/tmp/', help="Output directory for rst files")
|
||||
p.add_option("-f", "--output-format", action="store", dest="output_format", default='man', help="Output format for docs (the default 'man' or 'rst')")
|
||||
return p
|
||||
|
||||
|
||||
# from https://www.python.org/dev/peps/pep-0257/
|
||||
def trim_docstring(docstring):
|
||||
if not docstring:
|
||||
return ''
|
||||
# Convert tabs to spaces (following the normal Python rules)
|
||||
# and split into a list of lines:
|
||||
lines = docstring.expandtabs().splitlines()
|
||||
# Determine minimum indentation (first line doesn't count):
|
||||
indent = sys.maxsize
|
||||
for line in lines[1:]:
|
||||
stripped = line.lstrip()
|
||||
if stripped:
|
||||
indent = min(indent, len(line) - len(stripped))
|
||||
# Remove indentation (first line is special):
|
||||
trimmed = [lines[0].strip()]
|
||||
if indent < sys.maxsize:
|
||||
for line in lines[1:]:
|
||||
trimmed.append(line[indent:].rstrip())
|
||||
# Strip off trailing and leading blank lines:
|
||||
while trimmed and not trimmed[-1]:
|
||||
trimmed.pop()
|
||||
while trimmed and not trimmed[0]:
|
||||
trimmed.pop(0)
|
||||
# Return a single string:
|
||||
return '\n'.join(trimmed)
|
||||
|
||||
|
||||
def get_options(optlist):
|
||||
''' get actual options '''
|
||||
|
||||
opts = []
|
||||
for opt in optlist:
|
||||
res = {
|
||||
'desc': opt.help,
|
||||
'options': opt._short_opts + opt._long_opts
|
||||
}
|
||||
if opt.action == 'store':
|
||||
res['arg'] = opt.dest.upper()
|
||||
opts.append(res)
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def get_option_groups(option_parser):
|
||||
groups = []
|
||||
for option_group in option_parser.option_groups:
|
||||
group_info = {}
|
||||
group_info['desc'] = option_group.get_description()
|
||||
group_info['options'] = option_group.option_list
|
||||
group_info['group_obj'] = option_group
|
||||
groups.append(group_info)
|
||||
return groups
|
||||
|
||||
|
||||
def opt_doc_list(cli):
|
||||
''' iterate over options lists '''
|
||||
|
||||
results = []
|
||||
for option_group in cli.parser.option_groups:
|
||||
results.extend(get_options(option_group.option_list))
|
||||
|
||||
results.extend(get_options(cli.parser.option_list))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# def opts_docs(cli, name):
|
||||
def opts_docs(cli_class_name, cli_module_name):
|
||||
''' generate doc structure from options '''
|
||||
|
||||
cli_name = 'ansible-%s' % cli_module_name
|
||||
if cli_module_name == 'adhoc':
|
||||
cli_name = 'ansible'
|
||||
|
||||
# WIth no action/subcommand
|
||||
# shared opts set
|
||||
# instantiate each cli and ask its options
|
||||
cli_klass = getattr(__import__("ansible.cli.%s" % cli_module_name,
|
||||
fromlist=[cli_class_name]), cli_class_name)
|
||||
cli = cli_klass([])
|
||||
|
||||
# parse the common options
|
||||
try:
|
||||
cli.parse()
|
||||
except:
|
||||
pass
|
||||
|
||||
# base/common cli info
|
||||
docs = {
|
||||
'cli': cli_module_name,
|
||||
'cli_name': cli_name,
|
||||
'usage': cli.parser.usage,
|
||||
'short_desc': cli.parser.description,
|
||||
'long_desc': trim_docstring(cli.__doc__),
|
||||
'actions': {},
|
||||
}
|
||||
option_info = {'option_names': [],
|
||||
'options': [],
|
||||
'groups': []}
|
||||
|
||||
for extras in ('ARGUMENTS'):
|
||||
if hasattr(cli, extras):
|
||||
docs[extras.lower()] = getattr(cli, extras)
|
||||
|
||||
common_opts = opt_doc_list(cli)
|
||||
groups_info = get_option_groups(cli.parser)
|
||||
shared_opt_names = []
|
||||
for opt in common_opts:
|
||||
shared_opt_names.extend(opt.get('options', []))
|
||||
|
||||
option_info['options'] = common_opts
|
||||
option_info['option_names'] = shared_opt_names
|
||||
|
||||
option_info['groups'].extend(groups_info)
|
||||
|
||||
docs.update(option_info)
|
||||
|
||||
# now for each action/subcommand
|
||||
# force populate parser with per action options
|
||||
|
||||
# use class attrs not the attrs on a instance (not that it matters here...)
|
||||
for action in getattr(cli_klass, 'VALID_ACTIONS', ()):
|
||||
# instantiate each cli and ask its options
|
||||
action_cli_klass = getattr(__import__("ansible.cli.%s" % cli_module_name,
|
||||
fromlist=[cli_class_name]), cli_class_name)
|
||||
# init with args with action added?
|
||||
cli = action_cli_klass([])
|
||||
cli.args.append(action)
|
||||
|
||||
try:
|
||||
cli.parse()
|
||||
except:
|
||||
pass
|
||||
|
||||
# FIXME/TODO: needed?
|
||||
# avoid dupe errors
|
||||
cli.parser.set_conflict_handler('resolve')
|
||||
|
||||
cli.set_action()
|
||||
|
||||
action_info = {'option_names': [],
|
||||
'options': []}
|
||||
# docs['actions'][action] = {}
|
||||
# docs['actions'][action]['name'] = action
|
||||
action_info['name'] = action
|
||||
action_info['desc'] = trim_docstring(getattr(cli, 'execute_%s' % action).__doc__)
|
||||
|
||||
# docs['actions'][action]['desc'] = getattr(cli, 'execute_%s' % action).__doc__.strip()
|
||||
action_doc_list = opt_doc_list(cli)
|
||||
|
||||
uncommon_options = []
|
||||
for action_doc in action_doc_list:
|
||||
# uncommon_options = []
|
||||
|
||||
option_aliases = action_doc.get('options', [])
|
||||
for option_alias in option_aliases:
|
||||
|
||||
if option_alias in shared_opt_names:
|
||||
continue
|
||||
|
||||
# TODO: use set
|
||||
if option_alias not in action_info['option_names']:
|
||||
action_info['option_names'].append(option_alias)
|
||||
|
||||
if action_doc in action_info['options']:
|
||||
continue
|
||||
|
||||
uncommon_options.append(action_doc)
|
||||
|
||||
action_info['options'] = uncommon_options
|
||||
|
||||
docs['actions'][action] = action_info
|
||||
|
||||
docs['options'] = opt_doc_list(cli)
|
||||
return docs
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = generate_parser()
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
template_file = options.template_file
|
||||
template_path = os.path.expanduser(template_file)
|
||||
template_dir = os.path.abspath(os.path.dirname(template_path))
|
||||
template_basename = os.path.basename(template_file)
|
||||
|
||||
output_dir = os.path.abspath(options.output_dir)
|
||||
output_format = options.output_format
|
||||
|
||||
cli_modules = args
|
||||
|
||||
# various cli parsing things checks sys.argv if the 'args' that are passed in are []
|
||||
# so just remove any args so the cli modules dont try to parse them resulting in warnings
|
||||
sys.argv = [sys.argv[0]]
|
||||
# need to be in right dir
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
|
||||
allvars = {}
|
||||
output = {}
|
||||
cli_list = []
|
||||
cli_bin_name_list = []
|
||||
|
||||
# for binary in os.listdir('../../lib/ansible/cli'):
|
||||
for cli_module_name in cli_modules:
|
||||
binary = os.path.basename(os.path.expanduser(cli_module_name))
|
||||
|
||||
if not binary.endswith('.py'):
|
||||
continue
|
||||
elif binary == '__init__.py':
|
||||
continue
|
||||
|
||||
cli_name = os.path.splitext(binary)[0]
|
||||
|
||||
if cli_name == 'adhoc':
|
||||
cli_class_name = 'AdHocCLI'
|
||||
# myclass = 'AdHocCLI'
|
||||
output[cli_name] = 'ansible.1.asciidoc.in'
|
||||
cli_bin_name = 'ansible'
|
||||
else:
|
||||
# myclass = "%sCLI" % libname.capitalize()
|
||||
cli_class_name = "%sCLI" % cli_name.capitalize()
|
||||
output[cli_name] = 'ansible-%s.1.asciidoc.in' % cli_name
|
||||
cli_bin_name = 'ansible-%s' % cli_name
|
||||
|
||||
# FIXME:
|
||||
allvars[cli_name] = opts_docs(cli_class_name, cli_name)
|
||||
cli_bin_name_list.append(cli_bin_name)
|
||||
|
||||
cli_list = allvars.keys()
|
||||
|
||||
doc_name_formats = {'man': '%s.1.asciidoc.in',
|
||||
'rst': '%s.rst'}
|
||||
|
||||
for cli_name in cli_list:
|
||||
|
||||
# template it!
|
||||
env = Environment(loader=FileSystemLoader(template_dir))
|
||||
template = env.get_template(template_basename)
|
||||
|
||||
# add rest to vars
|
||||
tvars = allvars[cli_name]
|
||||
tvars['cli_list'] = cli_list
|
||||
tvars['cli_bin_name_list'] = cli_bin_name_list
|
||||
tvars['cli'] = cli_name
|
||||
if '-i' in tvars['options']:
|
||||
print('uses inventory')
|
||||
|
||||
manpage = template.render(tvars)
|
||||
filename = os.path.join(output_dir, doc_name_formats[output_format] % tvars['cli_name'])
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(to_bytes(manpage))
|
||||
print("Wrote doc to %s" % filename)
|
||||
@ -1,629 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
|
||||
# (c) 2012-2014, Michael DeHaan <michael@ansible.com> and others
|
||||
# (c) 2017 Ansible Project
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import datetime
|
||||
import glob
|
||||
import optparse
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from html import escape as html_escape
|
||||
except ImportError:
|
||||
# Python-3.2 or later
|
||||
import cgi
|
||||
|
||||
def html_escape(text, quote=True):
|
||||
return cgi.escape(text, quote)
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from six import iteritems, string_types
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.utils import plugin_docs
|
||||
|
||||
|
||||
#####################################################################################
|
||||
# constants and paths
|
||||
|
||||
# if a module is added in a version of Ansible older than this, don't print the version added information
|
||||
# in the module documentation because everyone is assumed to be running something newer than this already.
|
||||
TO_OLD_TO_BE_NOTABLE = 1.3
|
||||
|
||||
# Get parent directory of the directory this script lives in
|
||||
MODULEDIR = os.path.abspath(os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), os.pardir, 'lib', 'ansible', 'modules'
|
||||
))
|
||||
|
||||
# The name of the DOCUMENTATION template
|
||||
EXAMPLE_YAML = os.path.abspath(os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yml'
|
||||
))
|
||||
|
||||
_ITALIC = re.compile(r"I\(([^)]+)\)")
|
||||
_BOLD = re.compile(r"B\(([^)]+)\)")
|
||||
_MODULE = re.compile(r"M\(([^)]+)\)")
|
||||
_URL = re.compile(r"U\(([^)]+)\)")
|
||||
_CONST = re.compile(r"C\(([^)]+)\)")
|
||||
|
||||
DEPRECATED = b" (D)"
|
||||
|
||||
|
||||
def rst_ify(text):
|
||||
''' convert symbols like I(this is in italics) to valid restructured text '''
|
||||
|
||||
try:
|
||||
t = _ITALIC.sub(r'*' + r"\1" + r"*", text)
|
||||
t = _BOLD.sub(r'**' + r"\1" + r"**", t)
|
||||
t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t)
|
||||
t = _URL.sub(r"\1", t)
|
||||
t = _CONST.sub(r'``' + r"\1" + r"``", t)
|
||||
except Exception as e:
|
||||
raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e)))
|
||||
|
||||
return t
|
||||
|
||||
|
||||
def html_ify(text):
|
||||
''' convert symbols like I(this is in italics) to valid HTML '''
|
||||
|
||||
t = html_escape(text)
|
||||
t = _ITALIC.sub("<em>" + r"\1" + "</em>", t)
|
||||
t = _BOLD.sub("<b>" + r"\1" + "</b>", t)
|
||||
t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t)
|
||||
t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t)
|
||||
t = _CONST.sub("<code>" + r"\1" + "</code>", t)
|
||||
|
||||
return t
|
||||
|
||||
|
||||
def rst_fmt(text, fmt):
|
||||
''' helper for Jinja2 to do format strings '''
|
||||
|
||||
return fmt % (text)
|
||||
|
||||
|
||||
def rst_xline(width, char="="):
|
||||
''' return a restructured text line of a given length '''
|
||||
|
||||
return char * width
|
||||
|
||||
|
||||
def write_data(text, output_dir, outputname, module=None):
|
||||
''' dumps module output to a file or the screen, as requested '''
|
||||
|
||||
if output_dir is not None:
|
||||
if module:
|
||||
outputname = outputname % module
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
fname = os.path.join(output_dir, outputname)
|
||||
fname = fname.replace(".py", "")
|
||||
with open(fname, 'wb') as f:
|
||||
f.write(to_bytes(text))
|
||||
else:
|
||||
print(text)
|
||||
|
||||
|
||||
def get_module_info(module_dir, limit_to_modules=None, verbose=False):
|
||||
'''
|
||||
Returns information about modules and the categories that they belong to
|
||||
|
||||
:arg module_dir: file system path to the top of the modules directory
|
||||
:kwarg limit_to_modules: If given, this is a list of module names to
|
||||
generate information for. All other modules will be ignored.
|
||||
:returns: Tuple of two dicts containing module_info, categories, and
|
||||
aliases and a set listing deprecated modules:
|
||||
|
||||
:module_info: mapping of module names to information about them. The fields of the dict are:
|
||||
|
||||
:path: filesystem path to the module
|
||||
:deprecated: boolean. True means the module is deprecated otherwise not.
|
||||
:aliases: set of aliases to this module name
|
||||
:metadata: The modules metadata (as recorded in the module)
|
||||
:doc: The documentation structure for the module
|
||||
:examples: The module's examples
|
||||
:returndocs: The module's returndocs
|
||||
|
||||
:categories: maps category names to a dict. The dict contains at
|
||||
least one key, '_modules' which contains a list of module names in
|
||||
that category. Any other keys in the dict are subcategories with
|
||||
the same structure.
|
||||
|
||||
'''
|
||||
|
||||
categories = dict()
|
||||
module_info = defaultdict(dict)
|
||||
|
||||
# * windows powershell modules have documentation stubs in python docstring
|
||||
# format (they are not executed) so skip the ps1 format files
|
||||
# * One glob level for every module level that we're going to traverse
|
||||
files = (
|
||||
glob.glob("%s/*.py" % module_dir) +
|
||||
glob.glob("%s/*/*.py" % module_dir) +
|
||||
glob.glob("%s/*/*/*.py" % module_dir) +
|
||||
glob.glob("%s/*/*/*/*.py" % module_dir)
|
||||
)
|
||||
|
||||
for module_path in files:
|
||||
# Do not list __init__.py files
|
||||
if module_path.endswith('__init__.py'):
|
||||
continue
|
||||
|
||||
# Do not list blacklisted modules
|
||||
module = os.path.splitext(os.path.basename(module_path))[0]
|
||||
if module in plugin_docs.BLACKLIST['MODULE'] or module == 'base':
|
||||
continue
|
||||
|
||||
# If requested, limit module documentation building only to passed-in
|
||||
# modules.
|
||||
if limit_to_modules is not None and module.lower() not in limit_to_modules:
|
||||
continue
|
||||
|
||||
deprecated = False
|
||||
if module.startswith("_"):
|
||||
if os.path.islink(module_path):
|
||||
# Handle aliases
|
||||
source = os.path.splitext(os.path.basename(os.path.realpath(module_path)))[0]
|
||||
module = module.replace("_", "", 1)
|
||||
aliases = module_info[source].get('aliases', set())
|
||||
aliases.add(module)
|
||||
# In case we just created this via get()'s fallback
|
||||
module_info[source]['aliases'] = aliases
|
||||
continue
|
||||
else:
|
||||
# Handle deprecations
|
||||
module = module.replace("_", "", 1)
|
||||
deprecated = True
|
||||
|
||||
#
|
||||
# Regular module to process
|
||||
#
|
||||
|
||||
category = categories
|
||||
|
||||
# Start at the second directory because we don't want the "vendor"
|
||||
mod_path_only = os.path.dirname(module_path[len(module_dir):])
|
||||
|
||||
module_categories = []
|
||||
# build up the categories that this module belongs to
|
||||
for new_cat in mod_path_only.split('/')[1:]:
|
||||
if new_cat not in category:
|
||||
category[new_cat] = dict()
|
||||
category[new_cat]['_modules'] = []
|
||||
module_categories.append(new_cat)
|
||||
category = category[new_cat]
|
||||
|
||||
category['_modules'].append(module)
|
||||
|
||||
# the category we will use in links (so list_of_all_plugins can point to plugins/action_plugins/*'
|
||||
if module_categories:
|
||||
primary_category = module_categories[0]
|
||||
|
||||
# use ansible core library to parse out doc metadata YAML and plaintext examples
|
||||
doc, examples, returndocs, metadata = plugin_docs.get_docstring(module_path, verbose=verbose)
|
||||
|
||||
# save all the information
|
||||
module_info[module] = {'path': module_path,
|
||||
'deprecated': deprecated,
|
||||
'aliases': set(),
|
||||
'metadata': metadata,
|
||||
'doc': doc,
|
||||
'examples': examples,
|
||||
'returndocs': returndocs,
|
||||
'categories': module_categories,
|
||||
'primary_category': primary_category,
|
||||
}
|
||||
|
||||
# keep module tests out of becoming module docs
|
||||
if 'test' in categories:
|
||||
del categories['test']
|
||||
|
||||
return module_info, categories
|
||||
|
||||
|
||||
def generate_parser():
|
||||
''' generate an optparse parser '''
|
||||
|
||||
p = optparse.OptionParser(
|
||||
version='%prog 1.0',
|
||||
usage='usage: %prog [options] arg1 arg2',
|
||||
description='Generate module documentation from metadata',
|
||||
)
|
||||
|
||||
p.add_option("-A", "--ansible-version", action="store", dest="ansible_version", default="unknown", help="Ansible version number")
|
||||
p.add_option("-M", "--module-dir", action="store", dest="module_dir", default=MODULEDIR, help="Ansible library path")
|
||||
p.add_option("-P", "--plugin-type", action="store", dest="plugin_type", default='modules', help="The type of plugin (plugins, modules)")
|
||||
p.add_option("-T", "--template-dir", action="store", dest="template_dir", default="hacking/templates", help="directory containing Jinja2 templates")
|
||||
p.add_option("-t", "--type", action='store', dest='type', choices=['rst'], default='rst', help="Document type")
|
||||
p.add_option("-v", "--verbose", action='store_true', default=False, help="Verbose")
|
||||
p.add_option("-o", "--output-dir", action="store", dest="output_dir", default=None, help="Output directory for module files")
|
||||
p.add_option("-I", "--includes-file", action="store", dest="includes_file", default=None, help="Create a file containing list of processed modules")
|
||||
p.add_option("-l", "--limit-to-modules", action="store", dest="limit_to_modules", default=None,
|
||||
help="Limit building module documentation to comma-separated list of modules. Specify non-existing module name for no modules.")
|
||||
p.add_option('-V', action='version', help='Show version number and exit')
|
||||
return p
|
||||
|
||||
|
||||
def jinja2_environment(template_dir, typ, plugin_type):
|
||||
|
||||
env = Environment(loader=FileSystemLoader(template_dir),
|
||||
variable_start_string="@{",
|
||||
variable_end_string="}@",
|
||||
trim_blocks=True)
|
||||
env.globals['xline'] = rst_xline
|
||||
|
||||
templates = {}
|
||||
if typ == 'rst':
|
||||
env.filters['convert_symbols_to_format'] = rst_ify
|
||||
env.filters['html_ify'] = html_ify
|
||||
env.filters['fmt'] = rst_fmt
|
||||
env.filters['xline'] = rst_xline
|
||||
templates['plugin'] = env.get_template('plugin.rst.j2')
|
||||
templates['category_list'] = env.get_template('%s_by_category.rst.j2' % plugin_type)
|
||||
templates['support_list'] = env.get_template('%s_by_support.rst.j2' % plugin_type)
|
||||
templates['list_of_CATEGORY_modules'] = env.get_template('list_of_CATEGORY_%s.rst.j2' % plugin_type)
|
||||
else:
|
||||
raise Exception("unknown module format type: %s" % typ)
|
||||
|
||||
return templates
|
||||
|
||||
|
||||
def too_old(added):
|
||||
if not added:
|
||||
return False
|
||||
try:
|
||||
added_tokens = str(added).split(".")
|
||||
readded = added_tokens[0] + "." + added_tokens[1]
|
||||
added_float = float(readded)
|
||||
except ValueError as e:
|
||||
warnings.warn("Could not parse %s: %s" % (added, str(e)))
|
||||
return False
|
||||
return added_float < TO_OLD_TO_BE_NOTABLE
|
||||
|
||||
|
||||
def process_modules(module_map, templates, outputname, output_dir, ansible_version, plugin_type):
|
||||
for module in module_map:
|
||||
# print("rendering: %s" % module)
|
||||
|
||||
# pprint.pprint(('process_modules module:', module))
|
||||
|
||||
fname = module_map[module]['path']
|
||||
|
||||
# pprint.pprint(('process_modules module_info: ', module_map[module]))
|
||||
|
||||
module_categories = module_map[module].get('categories', [])
|
||||
|
||||
# crash if module is missing documentation and not explicitly hidden from docs index
|
||||
if module_map[module]['doc'] is None:
|
||||
print("%s: ERROR: MODULE MISSING DOCUMENTATION" % (fname,))
|
||||
_doc = {'module': module,
|
||||
'version_added': '2.4',
|
||||
'filename': fname}
|
||||
module_map[module]['doc'] = _doc
|
||||
# continue
|
||||
|
||||
# Going to reference this heavily so make a short name to reference it by
|
||||
doc = module_map[module]['doc']
|
||||
|
||||
# pprint.pprint(('process_modules doc: ', doc))
|
||||
|
||||
# add some defaults for plugins that dont have most of the info
|
||||
doc['module'] = doc.get('module', module)
|
||||
doc['version_added'] = doc.get('version_added', 'historical')
|
||||
|
||||
doc['plugin_type'] = plugin_type
|
||||
|
||||
if module_map[module]['deprecated'] and 'deprecated' not in doc:
|
||||
print("%s: WARNING: MODULE MISSING DEPRECATION DOCUMENTATION: %s" % (fname, 'deprecated'))
|
||||
|
||||
required_fields = ('short_description',)
|
||||
for field in required_fields:
|
||||
if field not in doc:
|
||||
print("%s: WARNING: MODULE MISSING field '%s'" % (fname, field))
|
||||
|
||||
not_nullable_fields = ('short_description',)
|
||||
for field in not_nullable_fields:
|
||||
if field in doc and doc[field] in (None, ''):
|
||||
print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field]))
|
||||
|
||||
if 'version_added' not in doc:
|
||||
pprint.pprint(doc)
|
||||
# sys.exit("*** ERROR: missing version_added in: %s ***\n" % module)
|
||||
|
||||
#
|
||||
# The present template gets everything from doc so we spend most of this
|
||||
# function moving data into doc for the template to reference
|
||||
#
|
||||
|
||||
if module_map[module]['aliases']:
|
||||
doc['aliases'] = module_map[module]['aliases']
|
||||
|
||||
# don't show version added information if it's too old to be called out
|
||||
added = 0
|
||||
if doc['version_added'] == 'historical':
|
||||
del doc['version_added']
|
||||
else:
|
||||
added = doc['version_added']
|
||||
|
||||
# Strip old version_added for the module
|
||||
if too_old(added):
|
||||
del doc['version_added']
|
||||
|
||||
option_names = []
|
||||
|
||||
if 'options' in doc and doc['options']:
|
||||
for (k, v) in iteritems(doc['options']):
|
||||
# Error out if there's no description
|
||||
if 'description' not in doc['options'][k]:
|
||||
raise AnsibleError("Missing required description for option %s in %s " % (k, module))
|
||||
|
||||
# Error out if required isn't a boolean (people have been putting
|
||||
# information on when something is required in here. Those need
|
||||
# to go in the description instead).
|
||||
required_value = doc['options'][k].get('required', False)
|
||||
if not isinstance(required_value, bool):
|
||||
raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % (required_value, k, module))
|
||||
|
||||
# Strip old version_added information for options
|
||||
if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']):
|
||||
del doc['options'][k]['version_added']
|
||||
|
||||
# Make sure description is a list of lines for later formatting
|
||||
if not isinstance(doc['options'][k]['description'], list):
|
||||
doc['options'][k]['description'] = [doc['options'][k]['description']]
|
||||
|
||||
option_names.append(k)
|
||||
|
||||
option_names.sort()
|
||||
|
||||
doc['option_keys'] = option_names
|
||||
doc['filename'] = fname
|
||||
doc['docuri'] = doc['module'].replace('_', '-')
|
||||
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
|
||||
doc['ansible_version'] = ansible_version
|
||||
|
||||
# check the 'deprecated' field in doc. We expect a dict potentially with 'why', 'version', and 'alternative' fields
|
||||
# examples = module_map[module]['examples']
|
||||
# print('\n\n%s: type of examples: %s\n' % (module, type(examples)))
|
||||
# if examples and not isinstance(examples, (str, unicode, list)):
|
||||
# raise TypeError('module %s examples is wrong type (%s): %s' % (module, type(examples), examples))
|
||||
|
||||
# use 'examples' for 'plainexamples' if 'examples' is a string
|
||||
if isinstance(module_map[module]['examples'], string_types):
|
||||
doc['plainexamples'] = module_map[module]['examples'] # plain text
|
||||
else:
|
||||
doc['plainexamples'] = ''
|
||||
|
||||
doc['metadata'] = module_map[module]['metadata']
|
||||
|
||||
# pprint.pprint(module_map[module]
|
||||
if module_map[module]['returndocs']:
|
||||
try:
|
||||
doc['returndocs'] = yaml.safe_load(module_map[module]['returndocs'])
|
||||
except Exception as e:
|
||||
print("%s:%s:yaml error:%s:returndocs=%s" % (fname, module, e, module_map[module]['returndocs']))
|
||||
doc['returndocs'] = None
|
||||
else:
|
||||
doc['returndocs'] = None
|
||||
|
||||
doc['author'] = doc.get('author', ['UNKNOWN'])
|
||||
if isinstance(doc['author'], string_types):
|
||||
doc['author'] = [doc['author']]
|
||||
|
||||
# print('about to template')
|
||||
# pprint.pprint(doc)
|
||||
text = templates['plugin'].render(doc)
|
||||
|
||||
# plugins get namespace dirs but modules do not
|
||||
if plugin_type == 'plugins':
|
||||
for module_category in module_categories:
|
||||
category_output_dir = os.path.join(output_dir, 'plugins', '%s' % module_category)
|
||||
write_data(text, category_output_dir, outputname, module)
|
||||
else:
|
||||
write_data(text, output_dir, outputname, module)
|
||||
|
||||
|
||||
def process_categories(mod_info, categories, templates,
|
||||
output_dir, output_name, plugin_type):
|
||||
for category in sorted(categories.keys()):
|
||||
if (plugin_type, category) == ('plugins', ''):
|
||||
print('skipping unknown cat: %s' % category)
|
||||
continue
|
||||
module_map = categories[category]
|
||||
category_filename = output_name % category
|
||||
|
||||
print("*** recording category %s in %s ***" % (category, category_filename))
|
||||
|
||||
# start a new category file
|
||||
|
||||
category_name = category.replace("_", " ")
|
||||
category_title = category_name.title()
|
||||
|
||||
subcategories = dict((k, v) for k, v in module_map.items() if k != '_modules')
|
||||
template_data = {'title': category_title,
|
||||
'category_name': category_name,
|
||||
'category': module_map,
|
||||
'subcategories': subcategories,
|
||||
'module_info': mod_info,
|
||||
'plugin_type': plugin_type
|
||||
}
|
||||
|
||||
text = templates['list_of_CATEGORY_modules'].render(template_data)
|
||||
write_data(text, output_dir, category_filename)
|
||||
|
||||
|
||||
def process_support_levels(mod_info, templates, output_dir, plugin_type):
|
||||
supported_by = {'Ansible Core Team': {'slug': 'core_supported',
|
||||
'modules': [],
|
||||
'output': 'core_maintained.rst',
|
||||
'blurb': "These are :doc:`modules maintained by the"
|
||||
" Ansible Core Team<core_maintained>` and will always ship"
|
||||
" with Ansible itself."},
|
||||
'Ansible Network Team': {'slug': 'network_supported',
|
||||
'modules': [],
|
||||
'output': 'network_maintained.rst',
|
||||
'blurb': "These are :doc:`modules maintained by the"
|
||||
" Ansible Network Team<network_maintained>` in"
|
||||
" a relationship similar to how the Ansible Core Team"
|
||||
" maintains the Core modules."},
|
||||
'Ansible Partners': {'slug': 'partner_supported',
|
||||
'modules': [],
|
||||
'output': 'partner_maintained.rst',
|
||||
'blurb': """
|
||||
Some examples of :doc:`Certified Modules<partner_maintained>` are those submitted by other
|
||||
companies. Maintainers of these types of modules must watch for any issues reported or pull requests
|
||||
raised against the module.
|
||||
|
||||
The Ansible Core Team will review all modules becoming certified. Core committers will review
|
||||
proposed changes to existing Certified Modules once the community maintainers of the module have
|
||||
approved the changes. Core committers will also ensure that any issues that arise due to Ansible
|
||||
engine changes will be remediated. Also, it is strongly recommended (but not presently required)
|
||||
for these types of modules to have unit tests.
|
||||
|
||||
These modules are currently shipped with Ansible, but might be shipped separately in the future.
|
||||
"""},
|
||||
'Ansible Community': {'slug': 'community_supported',
|
||||
'modules': [],
|
||||
'output': 'community_maintained.rst',
|
||||
'blurb': """
|
||||
These are :doc:`modules maintained by the Ansible Community<community_maintained>`. They **are
|
||||
not** supported by the Ansible Core Team or by companies/partners associated to the module.
|
||||
|
||||
They are still fully usable, but the response rate to issues is purely up to the community. Best
|
||||
effort support will be provided but is not covered under any support contracts.
|
||||
|
||||
These modules are currently shipped with Ansible, but will most likely be shipped separately in the future.
|
||||
"""},
|
||||
}
|
||||
|
||||
# only gen support pages for modules for now, need to split and namespace templates and generated docs
|
||||
if plugin_type == 'plugins':
|
||||
return
|
||||
# Separate the modules by support_level
|
||||
for module, info in mod_info.items():
|
||||
if not info.get('metadata', None):
|
||||
print('no metadata for %s' % module)
|
||||
continue
|
||||
if info['metadata']['supported_by'] == 'core':
|
||||
supported_by['Ansible Core Team']['modules'].append(module)
|
||||
elif info['metadata']['supported_by'] == 'network':
|
||||
supported_by['Ansible Network Team']['modules'].append(module)
|
||||
elif info['metadata']['supported_by'] == 'certified':
|
||||
supported_by['Ansible Partners']['modules'].append(module)
|
||||
elif info['metadata']['supported_by'] == 'community':
|
||||
supported_by['Ansible Community']['modules'].append(module)
|
||||
else:
|
||||
raise AnsibleError('Unknown supported_by value: %s' % info['metadata']['supported_by'])
|
||||
|
||||
# Render the module lists
|
||||
for maintainers, data in supported_by.items():
|
||||
template_data = {'maintainers': maintainers,
|
||||
'modules': data['modules'],
|
||||
'slug': data['slug'],
|
||||
'module_info': mod_info,
|
||||
}
|
||||
text = templates['support_list'].render(template_data)
|
||||
write_data(text, output_dir, data['output'])
|
||||
|
||||
|
||||
def validate_options(options):
|
||||
''' validate option parser options '''
|
||||
|
||||
if not options.module_dir:
|
||||
sys.exit("--module-dir is required", file=sys.stderr)
|
||||
if not os.path.exists(options.module_dir):
|
||||
sys.exit("--module-dir does not exist: %s" % options.module_dir, file=sys.stderr)
|
||||
if not options.template_dir:
|
||||
sys.exit("--template-dir must be specified")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
p = generate_parser()
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
validate_options(options)
|
||||
|
||||
plugin_type = options.plugin_type
|
||||
|
||||
if plugin_type == 'modules':
|
||||
templates = jinja2_environment(options.template_dir, options.type, plugin_type)
|
||||
output_dir = options.output_dir
|
||||
# trim trailing s off of plugin_type for plugin_type=='modules'. ie 'copy_module.rst'
|
||||
outputname = '%s_' + '%s.rst' % plugin_type[:-1]
|
||||
else:
|
||||
templates = jinja2_environment(options.template_dir, options.type, 'plugins')
|
||||
# for plugins, just use 'ssh.rst' vs 'ssh_module.rst'
|
||||
outputname = '%s.rst'
|
||||
output_dir = '%s/plugins/%s' % (options.output_dir, plugin_type)
|
||||
|
||||
# Convert passed-in limit_to_modules to None or list of modules.
|
||||
if options.limit_to_modules is not None:
|
||||
options.limit_to_modules = [s.lower() for s in options.limit_to_modules.split(",")]
|
||||
|
||||
mod_info, categories = get_module_info(options.module_dir, limit_to_modules=options.limit_to_modules, verbose=options.verbose)
|
||||
|
||||
categories['all'] = {'_modules': mod_info.keys()}
|
||||
|
||||
# pprint.pprint(categories)
|
||||
# pprint.pprint(mod_info)
|
||||
# pprint.pprint(dict(mod_info))
|
||||
# Transform the data
|
||||
if options.type == 'rst':
|
||||
for key, record in mod_info.items():
|
||||
# pprint.pprint(('record', record))
|
||||
if record.get('doc', None):
|
||||
short_desc = record['doc']['short_description']
|
||||
if short_desc is None:
|
||||
print('WARNING: short_description for %s is None' % key)
|
||||
short_desc = ''
|
||||
record['doc']['short_description'] = rst_ify(short_desc)
|
||||
|
||||
if plugin_type == 'modules':
|
||||
# Write master category list
|
||||
category_list_text = templates['category_list'].render(categories=sorted(categories.keys()))
|
||||
category_index_name = '%s_by_category.rst' % plugin_type
|
||||
write_data(category_list_text, output_dir, category_index_name)
|
||||
|
||||
# Render all the individual module pages
|
||||
process_modules(mod_info, templates, outputname, output_dir, options.ansible_version, plugin_type)
|
||||
|
||||
# Render all the categories for modules
|
||||
if plugin_type == 'modules':
|
||||
category_list_name_template = 'list_of_%s_' + '%s.rst' % plugin_type
|
||||
process_categories(mod_info, categories, templates, output_dir, category_list_name_template, plugin_type)
|
||||
|
||||
# Render all the categories for modules
|
||||
process_support_levels(mod_info, templates, output_dir, plugin_type)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,12 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
cat <<- EOF > ../docsite/rst/dev_guide/testing/sanity/index.rst
|
||||
Sanity Tests
|
||||
============
|
||||
|
||||
The following sanity tests are available as \`\`--test\`\` options for \`\`ansible-test sanity\`\`:
|
||||
|
||||
$(for test in $(../../test/runner/ansible-test sanity --list-tests); do echo "- :doc:\`${test} <${test}>\`"; done)
|
||||
|
||||
This list is also available using \`\`ansible-test sanity --list-tests\`\`.
|
||||
EOF
|
||||
2
docs/docsite/.gitignore
vendored
2
docs/docsite/.gitignore
vendored
@ -11,7 +11,5 @@ ansible*.xml
|
||||
.buildinfo
|
||||
objects.inv
|
||||
.doctrees
|
||||
rst/dev_guide/testing/sanity/index.rst
|
||||
rst/modules/*.rst
|
||||
rst/playbooks_keywords.rst
|
||||
*.min.css
|
||||
|
||||
@ -1,45 +1,20 @@
|
||||
OS := $(shell uname -s)
|
||||
SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"):
|
||||
FORMATTER=../bin/plugin_formatter.py
|
||||
TESTING_FORMATTER=../bin/testing_formatter.sh
|
||||
DUMPER=../bin/dump_keywords.py
|
||||
CONFIG_DUMPER=../bin/dump_config.py
|
||||
GENERATE_CLI=../bin/generate_man.py
|
||||
FORMATTER=../../hacking/module_formatter.py
|
||||
DUMPER=../../hacking/dump_playbook_attributes.py
|
||||
ifeq ($(shell echo $(OS) | egrep -ic 'Darwin|FreeBSD|OpenBSD|DragonFly'),1)
|
||||
CPUS ?= $(shell sysctl hw.ncpu|awk '{print $$2}')
|
||||
else
|
||||
CPUS ?= $(shell nproc)
|
||||
endif
|
||||
|
||||
MODULE_ARGS=
|
||||
ifdef MODULES
|
||||
MODULE_ARGS = -l $(MODULES)
|
||||
endif
|
||||
|
||||
PLUGIN_ARGS=
|
||||
ifdef PLUGINS
|
||||
PLUGIN_ARGS = -l $(PLUGINS)
|
||||
endif
|
||||
|
||||
DOC_PLUGINS ?= cache callback connection inventory lookup strategy vars
|
||||
|
||||
assertrst:
|
||||
ifndef rst
|
||||
$(error specify document or pattern with rst=somefile.rst)
|
||||
endif
|
||||
|
||||
all: docs
|
||||
|
||||
docs: clean htmldocs
|
||||
|
||||
generate_rst: testing keywords modules plugins staticmin cli config
|
||||
|
||||
htmldocs: generate_rst
|
||||
htmldocs: directives modules staticmin
|
||||
CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx html
|
||||
|
||||
singlehtmldocs: generate_rst
|
||||
CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx singlehtml
|
||||
|
||||
webdocs: docs
|
||||
|
||||
#TODO: leaving htmlout removal for those having older versions, should eventually be removed also
|
||||
@ -47,7 +22,7 @@ clean:
|
||||
-rm -rf htmlout
|
||||
-rm -rf _build
|
||||
-rm -f .buildinfo
|
||||
-rm -f objects.inv
|
||||
-rm -f *.inv
|
||||
-rm -rf *.doctrees
|
||||
@echo "Cleaning up minified css files"
|
||||
find . -type f -name "*.min.css" -delete
|
||||
@ -60,46 +35,15 @@ clean:
|
||||
-rm rst/list_of_*.rst
|
||||
-rm rst/*_by_category.rst
|
||||
-rm rst/*_module.rst
|
||||
-rm rst/*_plugin.rst
|
||||
-rm rst/*_maintained.rst
|
||||
-rm rst/playbooks_directives.rst
|
||||
-rm rst/playbooks_keywords.rst
|
||||
-rm rst/plugins/*/*.rst
|
||||
-rm rst/config.rst
|
||||
-rm rst/ansible-*.rst
|
||||
-rm rst/ansible.rst
|
||||
|
||||
.PHONEY: docs clean
|
||||
|
||||
# TODO: make generate_man output dir cli option
|
||||
cli: $(GENERATE_CLI)
|
||||
PYTHONPATH=../../lib $(GENERATE_CLI) --template-file=../templates/cli_rst.j2 --output-dir=rst/ --output-format rst ../../lib/ansible/cli/*.py
|
||||
directives: $(FORMATTER) ../../hacking/templates/rst.j2
|
||||
PYTHONPATH=../../lib $(DUMPER) --template-dir=../../hacking/templates --output-dir=rst/
|
||||
|
||||
keywords: $(FORMATTER) ../templates/playbooks_keywords.rst.j2
|
||||
PYTHONPATH=../../lib $(DUMPER) --template-dir=../templates --output-dir=rst/ -d ./keyword_desc.yml
|
||||
|
||||
config:
|
||||
PYTHONPATH=../../lib $(CONFIG_DUMPER) --template-file=../templates/config.rst.j2 --output-dir=rst/ -d ../../lib/ansible/config/base.yml
|
||||
|
||||
modules: $(FORMATTER) ../templates/plugin.rst.j2
|
||||
PYTHONPATH=../../lib $(FORMATTER) -t rst --template-dir=../templates --module-dir=../../lib/ansible/modules -o rst/ $(MODULE_ARGS)
|
||||
|
||||
plugins: $(FORMATTER) ../templates/plugin.rst.j2
|
||||
@echo "looping over doc plugins"
|
||||
for plugin in $(DOC_PLUGINS); \
|
||||
do \
|
||||
PYTHONPATH=../../lib $(FORMATTER) -t rst --plugin-type $$plugin --template-dir=../templates --module-dir=../../lib/ansible/plugins/$$plugin -o rst/ $(PLUGIN_ARGS); \
|
||||
done
|
||||
|
||||
testing:
|
||||
$(TESTING_FORMATTER)
|
||||
modules: $(FORMATTER) ../../hacking/templates/rst.j2
|
||||
PYTHONPATH=../../lib $(FORMATTER) -t rst --template-dir=../../hacking/templates --module-dir=../../lib/ansible/modules -o rst/
|
||||
|
||||
staticmin:
|
||||
cat _themes/srtd/static/css/theme.css | sed -e 's/^[ ]*//g; s/[ ]*$$//g; s/\([:{;,]\) /\1/g; s/ {/{/g; s/\/\*.*\*\///g; /^$$/d' | sed -e :a -e '$$!N; s/\n\(.\)/\1/; ta' > _themes/srtd/static/css/theme.min.css
|
||||
|
||||
epub:
|
||||
(CPUS=$(CPUS) make -f Makefile.sphinx epub)
|
||||
|
||||
htmlsingle: assertrst
|
||||
sphinx-build -j $(CPUS) -b html -d _build/doctrees ./rst _build/html rst/$(rst)
|
||||
@echo "Output is in _build/html/$(rst:.rst=.html)"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS = -j $(CPUS) -n -w rst_warnings
|
||||
SPHINXOPTS = -j $(CPUS)
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = sdfsdf
|
||||
SOURCEDIR = rst
|
||||
@ -17,4 +17,4 @@ help:
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile.sphinx
|
||||
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
@ -14,12 +14,6 @@ such as link references, you may install sphinx and build the documentation by r
|
||||
To include module documentation you'll need to run `make webdocs` at the top level of the repository. The generated
|
||||
html files are in docsite/htmlout/.
|
||||
|
||||
To limit module documentation building to a specific module, run `MODULES=NAME
|
||||
make webdocs` instead. This should make testing module documentation syntax much
|
||||
faster. Instead of a single module, you can also specify a comma-separated list
|
||||
of modules. In order to skip building documentation for all modules, specify
|
||||
non-existing module name, for example `MODULES=none make webdocs`.
|
||||
|
||||
If you do not want to learn the reStructuredText format, you can also [file issues] about
|
||||
documentation problems on the Ansible GitHub project.
|
||||
|
||||
|
||||
4
docs/docsite/_themes/srtd/ansible_eol_banner.html
Normal file
4
docs/docsite/_themes/srtd/ansible_eol_banner.html
Normal file
@ -0,0 +1,4 @@
|
||||
{# Creates a banner at the top of the page for EOL versions. #}
|
||||
<div id='banner' class='Admonition caution'>
|
||||
<p>You are reading an unmaintained version of the Ansible documentation. Unmaintained Ansible versions can contain unfixed security vulnerabilities (CVE). Please upgrade to a maintained version. See <a href="https://docs.ansible.com/ansible/latest/index.html">the latest Ansible documentation</a>.</p>
|
||||
</div>
|
||||
@ -1,11 +1,5 @@
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="{{ pathto(master_doc) }}">Docs</a> »</li>
|
||||
<li><a href="">{{ title }}</a></li>
|
||||
{% if not pagename.endswith('_module') and (not 'list_of' in pagename) and (not 'category' in pagename) %}
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
<a href="https://github.com/ansible/ansible/blob/devel/docs/docsite/rst/{{ pagename }}.rst" class="icon icon-github"> Edit on GitHub</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<hr/>
|
||||
|
||||
|
||||
@ -34,6 +34,9 @@
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||
{% endif %}
|
||||
|
||||
<link rel="canonical" href="https://docs.ansible.com/ansible/latest/{{ pagename }}.html"/>
|
||||
|
||||
|
||||
{# CSS #}
|
||||
<link href='https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700' rel='stylesheet' type='text/css'>
|
||||
<link href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.3/css/font-awesome.min.css' rel='stylesheet' type='text/css'>
|
||||
@ -140,7 +143,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a class="DocSite-nav" href="/">
|
||||
<a class="DocSite-nav" href="http://docs.ansible.com/">
|
||||
<img class="DocSiteNav-logo"
|
||||
src="{{ pathto('_static/', 1) }}images/logo_invert.png"
|
||||
alt="Ansible Logo">
|
||||
@ -151,23 +154,23 @@
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
|
||||
{# SIDE NAV, TOGGLES ON MOBILE #}
|
||||
{# SIDE NAV TOGGLES ON MOBILE #}
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||||
|
||||
<div style="background-color:#5bbdbf;height=80px;margin:'auto auto auto auto'">
|
||||
<a class="DocSiteProduct-header DocSiteProduct-header--core" href="/">
|
||||
<a class="DocSiteProduct-header DocSiteProduct-header--core" href="http://docs.ansible.com">
|
||||
<div class="DocSiteProduct-productName">
|
||||
<div class="DocSiteProduct-logoText">
|
||||
Ansible
|
||||
Ansible Core
|
||||
<div class="DocSiteProduct-CurrentVersion" align="right">
|
||||
devel
|
||||
v2.3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
<div class="DocSiteProduct-CheckVersionPara">For previous versions, see the <a class="DocSiteProduct-versionheader" href="/#coreversionselect">documentation archive.</a></div>
|
||||
|
||||
<div class="DocSiteProduct-CheckVersionPara">For previous versions, see the <a class="DocSiteProduct-versionheader" href="http://docs.ansible.com/">documentation archive.</a></div>
|
||||
</div>
|
||||
|
||||
<div id="menu-id" class="wy-menu wy-menu-vertical" data-spy="affix">
|
||||
@ -228,6 +231,7 @@
|
||||
</div> -->
|
||||
|
||||
{% include "breadcrumbs.html" %}
|
||||
{% include "ansible_eol_banner.html" %}
|
||||
<div id="page-content">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
@ -4721,6 +4721,7 @@ span[id*='MathJax-Span'] {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
.wy-menu-vertical a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
226
docs/docsite/conf.py
Normal file
226
docs/docsite/conf.py
Normal file
@ -0,0 +1,226 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Sep 27 13:23:22 2008-2009.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# The contents of this file are pickled, so don't put values in the namespace
|
||||
# that aren't pickleable (module imports are okay, they're removed
|
||||
# automatically).
|
||||
#
|
||||
# All configuration values have a default value; values that are commented out
|
||||
# serve to show the default value.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# pip install sphinx_rtd_theme
|
||||
#import sphinx_rtd_theme
|
||||
#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# If your extensions are in another directory, add it here. If the directory
|
||||
# is relative to the documentation root, use os.path.abspath to make it
|
||||
# absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('some/directory'))
|
||||
#
|
||||
sys.path.insert(0, os.path.join('ansible', 'lib'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
|
||||
VERSION='2.2'
|
||||
AUTHOR='Ansible, Inc'
|
||||
|
||||
|
||||
# General configuration
|
||||
# ---------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings.
|
||||
# They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
|
||||
# Later on, add 'sphinx.ext.viewcode' to the list if you want to have
|
||||
# colorized code generated too for references.
|
||||
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['.templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General substitutions.
|
||||
project = 'Ansible Documentation'
|
||||
copyright = "2013-2017 Ansible, Inc"
|
||||
|
||||
# The default replacements for |version| and |release|, also used in various
|
||||
# other places throughout the built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = VERSION
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = VERSION
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directories, that shouldn't be
|
||||
# searched for source files.
|
||||
#exclude_dirs = []
|
||||
|
||||
# A list of glob-style patterns that should be excluded when looking
|
||||
# for source files.
|
||||
exclude_patterns = ['modules']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
highlight_language = 'YAML+Jinja'
|
||||
|
||||
#Substitutions, variables, entities, & shortcuts for text which do not need to link to anything.
|
||||
#For titles which should be a link, use the intersphinx anchors set at the index, chapter, and section levels, such as qi_start_:
|
||||
rst_epilog = """
|
||||
.. |acapi| replace:: *Ansible Core API Guide*
|
||||
.. |acrn| replace:: *Ansible Core Release Notes*
|
||||
.. |ac| replace:: Ansible Core
|
||||
.. |acversion| replace:: Ansible Core Version 2.1
|
||||
.. |acversionshort| replace:: Ansible Core 2.1
|
||||
.. |versionshortest| replace:: 2.2
|
||||
.. |versiondev| replace:: 2.3
|
||||
.. |pubdate| replace:: July 19, 2016
|
||||
.. |rhel| replace:: Red Hat Enterprise Linux
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Options for HTML output
|
||||
# -----------------------
|
||||
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'srtd'
|
||||
html_short_title = 'Ansible Documentation'
|
||||
|
||||
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
||||
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
||||
# given in html_static_path.
|
||||
#html_style = 'solar.css'
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = 'Ansible Documentation'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (within the static path) to place at the top of
|
||||
# the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = 'favicon.ico'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = ['.static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
||||
html_copy_source = False
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Poseidodoc'
|
||||
|
||||
|
||||
# Options for LaTeX output
|
||||
# ------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, document class
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'ansible.tex', 'Ansible 2.2 Documentation', AUTHOR, 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
autoclass_content = 'both'
|
||||
Binary file not shown.
@ -1,57 +0,0 @@
|
||||
accelerate: DEPRECATED, set to True to use accelerate connection plugin.
|
||||
accelerate_ipv6: "DEPRECATED, set to True to force accelerate plugin to use ipv6 for it's connection."
|
||||
accelerate_port: DEPRECATED, set to override default port use for accelerate connection.
|
||||
action: "The 'action' to execute for a task, it normally translates into a C(module) or action plugin."
|
||||
args: DEPRECATED, A secondary way to add arguments into a task, it takes a dictionary in which keys map to options and values .. well you get it.
|
||||
always: List of tasks, in a block, that execute no matter if there is an error in the block or not.
|
||||
always_run: DEPRECATED, forces a task to run even in check mode, use :term:`check_mode` directive instead.
|
||||
any_errors_fatal: Force any un-handled task errors on any host to propagate to all hosts and end the play.
|
||||
async: Run a task asyncronouslly if the C(action) supports this.
|
||||
become: Boolean that controls if privilege escalation is used or not on :term:`Task` execution.
|
||||
become_flags: A string of flag(s) to pass to the privilege escalation program when :term:`become` is True.
|
||||
become_method: Which method of privilege escalation to use. i.e. sudo/su/etc.
|
||||
become_user: "User that you 'become' after using privilege escalation, the remote/login user must have permissions to become this user."
|
||||
block: List of tasks in a block.
|
||||
changed_when: "Conditional expression that overrides the task's normal 'changed' status."
|
||||
check_mode: "A boolean that controls if a task is executed in 'check' mode"
|
||||
connection: Allows you to change the connection plugin used for tasks to execute on the target.
|
||||
delay: Number of seconds to delay between retries, this setting is only used in combination with :term:`until`.
|
||||
delegate_facts: Boolean that allows you to apply facts to delegated host instead of inventory_hostname.
|
||||
delegate_to: Host to execute task instead of the target (inventory_hostname), connection vars from the delegated host will also be used for the task.
|
||||
diff: "Toggle to make tasks return 'diff' information or not."
|
||||
environment: A dictionary that gets converted into environment vars to be provided for the task upon execution.
|
||||
fact_path: Set the fact path option for the fact gathering plugin controlled by :term:`gather_facts`.
|
||||
failed_when: "Conditional expression that overrides the task's normal 'failed' status."
|
||||
force_handlers: Will force notified handler execution for hosts even if they failed during the play, it will not trigger if the play itself fails.
|
||||
gather_facts: "A boolean that controls if the play will automatically run the 'setup' task to gather facts for the hosts."
|
||||
gather_subset: Allows you to pass subset options to the fact gathering plugin controlled by :term:`gather_facts`.
|
||||
gather_timeout: Allows you to set the timeout for the fact gathering plugin controlled by :term:`gather_facts`.
|
||||
handlers: "A section with tasks that are treated as handlers, these won't get executed normally, only when notified. After each section of tasks is complete."
|
||||
hosts: "A list of groups, hosts or host pattern that translates into a list of hosts that are the play's target."
|
||||
ignore_errors: Boolean that allows you to ignore task failures and continue with play. It does not affect connection errors.
|
||||
loop_control: "Several keys here allow you to modify/set loop behaviour in a task see http://docs.ansible.com/ansible/latest/playbooks_loops.html#loop-control for details."
|
||||
max_fail_percentage: can be used to abort the run after a given percentage of hosts in the current batch has failed.
|
||||
name: "It's a name, works mostly for documentation, in the case of tasks/handlers it can be an identifier."
|
||||
no_log: Boolean that controls information disclosure.
|
||||
notify: "list of handlers to notify when the task returns a 'changed=True' status."
|
||||
order: Controls the sorting of hosts as they are used for executing the play. Possible values are inventory (default), sorted, reverse_sorted, reverse_inventory and shuffle.
|
||||
poll: Sets the polling interval in seconds for async tasks (default 10s).
|
||||
port: Used to override the default port used in a connection.
|
||||
post_tasks: A list of tasks to execute after the :term:`tasks` section.
|
||||
pre_tasks: A list of tasks to execute before :term:`roles`.
|
||||
remote_user: User used to log into the target via the connection plugin. AKA login user.
|
||||
register: Name of variable that will contain task status and module return data.
|
||||
rescue: List of tasks in a :term:`block` that run if there is a task error in the main :term:`block` list.
|
||||
retries: "Number of retries before giving up in a :term:`until` loop. This setting is only used in combination with :term:`until`."
|
||||
roles: List of roles to be imported into the play
|
||||
run_once: Boolean that will bypass the host loop, forcing the task to execute on the first host available and will also apply any facts to all active hosts.
|
||||
serial: Defines the 'batch' of hosts to execute the current play until the end.
|
||||
strategy: Allows you to choose the connection plugin to use for the play.
|
||||
tags: Tags applied to the task or included tasks, this allows selecting subsets of tasks from the command line.
|
||||
tasks: Main list of tasks to execute in the play, they run after :term:`roles` and before :term:`post_tasks`.
|
||||
until: "This keyword implies a ':term:`retries` loop' that will go on until the condition supplied here is met or we hit the :term:`retries` limit."
|
||||
vars: Dictionary/map of variables
|
||||
vars_files: List of files that contain vars to include in the play.
|
||||
vars_prompt: list of variables to prompt for.
|
||||
vault_password: Secret used to decrypt vaulted files or variables.
|
||||
when: Conditional expression, determines if an iteration of a task is run or not.
|
||||
Binary file not shown.
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
Command Line Tools
|
||||
==================
|
||||
|
||||
Most users are familiar with `ansible` and `ansible-playbook`, but those are not the only utilities Ansible provides.
|
||||
Below you is a complete list of Ansible utilities. Each page contains a description of the utility and a listing of supported parameters.
|
||||
|
||||
.. toctree:: :maxdepth: 1
|
||||
:glob:
|
||||
|
||||
ansible
|
||||
ansible-*
|
||||
@ -3,7 +3,7 @@ Committers Guidelines (for people with commit rights to Ansible on GitHub)
|
||||
|
||||
These are the guidelines for people with commit access to Ansible. Committers are essentially acting as members of the Ansible Core team, although not necessarily as an employee of Ansible and Red Hat. Please read the guidelines before you commit.
|
||||
|
||||
These guidelines apply to everyone. At the same time, this ISN'T a process document. So just use good judgement. You've been given commit access because we trust your judgement.
|
||||
These guidelines apply to everyone. At the same time, this ISN’T a process document. So just use good judgement. You’ve been given commit access because we trust your judgement.
|
||||
|
||||
That said, use the trust wisely.
|
||||
|
||||
@ -12,25 +12,25 @@ If you abuse the trust and break components and builds, etc., the trust level fa
|
||||
Features, High Level Design, and Roadmap
|
||||
========================================
|
||||
|
||||
As a core team member, you are an integral part of the team that develops the roadmap. Please be engaged, and push for the features and fixes that you want to see. Also keep in mind that Red Hat, as a company, will commit to certain features, fixes, APIs, etc. for various releases. Red Hat, the company, and the Ansible team must get these committed features (etc.) completed and released as scheduled. Obligations to users, the community, and customers must come first. Because of these commitments, a feature you want to develop yourself may not get into a release if it impacts a lot of other parts within Ansible.
|
||||
As a core team member, you are an integral part of the team that develops the roadmap. Please be engaged, and push for the features and fixes that you want to see. Also keep in mind that Red Hat, as a company, will commit to certain features, fixes, APIs, etc. for various releases. Red Hat, the company, and the Ansible team must get these committed features (etc.) completed and released as scheduled. Obligations to users, the community, and customers must come first. Because of these commitments, a feature you want to develop yourself many not get into a release if it impacts a lot of other parts within Ansible.
|
||||
|
||||
Any other new features and changes to high level design should go through the proposal process (TBD), to ensure the community and core team have had a chance to review the idea and approve it. The core team has sole responsibility for merging new features based on proposals.
|
||||
|
||||
Our Workflow on GitHub
|
||||
======================
|
||||
|
||||
As a committer, you may already know this, but our workflow forms a lot of our team policies. Please ensure you're aware of the following workflow steps:
|
||||
As a committer, you may already know this, but our workflow forms a lot of our team policies. Please ensure you’re aware of the following workflow steps:
|
||||
|
||||
* Fork the repository upon which you want to do some work to your own personal repository
|
||||
* Work on the specific branch upon which you need to commit
|
||||
* Create a Pull Request back to the Ansible repository and tag the people you would like to review; assign someone as the primary "owner" of your request
|
||||
* Create a Pull Request back to the Ansible repository and tag the people you would like to review; assign someone as the primary “owner” of your request
|
||||
* Adjust code as necessary based on the Comments provided
|
||||
* Ask someone on the Core Team to do a final review and merge
|
||||
|
||||
Addendum to workflow for Committers:
|
||||
------------------------------------
|
||||
|
||||
The Core Team is aware that this can be a difficult process at times. Sometimes, the team breaks the rules: Direct commits, merging their own PRs. This section is a set of guidelines. If you're changing a comma in a doc, or making a very minor change, you can use your best judgement. This is another trust thing. The process is critical for any major change, but for little things or getting something done quickly, use your best judgement and make sure people on the team are aware of your work.
|
||||
The Core Team is aware that this can be a difficult process at times. Sometimes, the team breaks the rules: Direct commits, merging their own PRs. This section is a set of guidelines. If you’re changing a comma in a doc, or making a very minor change, you can use your best judgement. This is another trust thing. The process is critical for any major change, but for little things or getting something done quickly, use your best judgement and make sure people on the team are aware of your work.
|
||||
|
||||
Roles on Core
|
||||
=============
|
||||
@ -39,14 +39,14 @@ Roles on Core
|
||||
|
||||
General Rules
|
||||
=============
|
||||
Individuals with direct commit access to ansible/ansible are entrusted with powers that allow them to do a broad variety of things--probably more than we can write down. Rather than rules, treat these as general *guidelines*, individuals with this power are expected to use their best judgement.
|
||||
Individuals with direct commit access to ansible/ansible (+core, + extras) are entrusted with powers that allow them to do a broad variety of things--probably more than we can write down. Rather than rules, treat these as general *guidelines*, individuals with this power are expected to use their best judgement.
|
||||
|
||||
* Don't
|
||||
* Don’t
|
||||
|
||||
- Commit directly.
|
||||
- Merge your own PRs. Someone else should have a chance to review and approve the PR merge. If you are a Core Committer, you have a small amount of leeway here for very minor changes.
|
||||
- Forget about alternate environments. Consider the alternatives--yes, people have bad environments, but they are the ones who need us the most.
|
||||
- Drag your community team members down. Always discuss the technical merits, but you should never address the person's limitations (you can later go for beers and call them idiots, but not in IRC/Github/etc.).
|
||||
- Drag your community team members down. Always discuss the technical merits, but you should never address the person’s limitations (you can later go for beers and call them idiots, but not in IRC/Github/etc.).
|
||||
- Forget about the maintenance burden. Some things are really cool to have, but they might not be worth shoehorning in if the maintenance burden is too great.
|
||||
- Break playbooks. Always keep backwards compatibility in mind.
|
||||
- Forget to keep it simple. Complexity breeds all kinds of problems.
|
||||
@ -55,7 +55,7 @@ Individuals with direct commit access to ansible/ansible are entrusted with powe
|
||||
|
||||
- Squash, avoid merges whenever possible, use github's squash commits or cherry pick if needed (bisect thanks you).
|
||||
- Be active. Committers who have no activity on the project (through merges, triage, commits, etc.) will have their permissions suspended.
|
||||
- Consider backwards compatibility (goes back to "don't break existing playbooks").
|
||||
- Consider backwards compatibility (goes back to "don’t break existing playbooks").
|
||||
- Write tests. PRs with tests are looked at with more priority than PRs without tests that should have them included. While not all changes require tests, be sure to add them for bug fixes or functionality changes.
|
||||
- Discuss with other committers, specially when you are unsure of something.
|
||||
- Document! If your PR is a new feature or a change to behavior, make sure you've updated all associated documentation or have notified the right people to do so. It also helps to add the version of Core against which this documentation is compatible (to avoid confusion with stable versus devel docs, for backwards compatibility, etc.).
|
||||
@ -114,13 +114,11 @@ Individuals who've been asked to become a part of this group have generally been
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Trond Hindenes | trondhindenes | | |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Jon Hawkesworth | jhawkesworth | jhawkesworth | |
|
||||
| Jon Hawkesworth | jhawkseworth | jhawkseworth | |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Will Thames | willthames | willthames | |
|
||||
| Will Thames | wilthames | willthames | |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Ryan Brown | ryansb | ryansb | |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Adrian Likins | alikins | alikins | |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
| Dag Wieers | dagwieers | dagwieers | dag@wieers.com |
|
||||
+---------------------+----------------------+--------------------+----------------------+
|
||||
|
||||
@ -3,10 +3,10 @@ Return Values
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Ansible modules normally return a data structure that can be registered into a variable, or seen directly when output by
|
||||
Ansible modules normally return a data structure that can be registered into a variable, or seen directly when output by
|
||||
the `ansible` program. Each module can optionally document its own unique return values (visible through ansible-doc and https://docs.ansible.com).
|
||||
|
||||
This document covers return values common to all modules.
|
||||
This document covers return values common to all modules.
|
||||
|
||||
.. note:: Some of these keys might be set by Ansible itself once it processes the module's return information.
|
||||
|
||||
@ -54,7 +54,7 @@ Some modules execute command line utilities or are geared for executing commands
|
||||
|
||||
stderr_lines
|
||||
````````````
|
||||
When `stderr` is returned we also always provide this field which is a list of strings, one item per line from the original.
|
||||
When c(stderr) is returned we also always provide this field which is a list of strings, one item per line from the original.
|
||||
|
||||
stdout
|
||||
``````
|
||||
@ -62,7 +62,7 @@ Some modules execute command line utilities or are geared for executing commands
|
||||
|
||||
stdout_lines
|
||||
````````````
|
||||
When `stdout` is returned, Ansible always provides a list of strings, each containing one item per line from the original output.
|
||||
When c(stdout) is returned, Ansible always provides a list of strings, each containing one item per line from the original output.
|
||||
|
||||
|
||||
.. _internal_return_values:
|
||||
|
||||
@ -2,7 +2,7 @@ Community Information & Contributing
|
||||
````````````````````````````````````
|
||||
|
||||
Ansible is an open source project designed to bring together administrators and developers of all kinds to collaborate on building
|
||||
IT automation solutions that work well for them.
|
||||
IT automation solutions that work well for them.
|
||||
|
||||
Should you wish to get more involved -- whether in terms of just asking a question, helping other users, introducing new people to Ansible, or helping with the software or documentation, we welcome your contributions to the project.
|
||||
|
||||
@ -16,7 +16,7 @@ I've Got A Question
|
||||
|
||||
We're happy to help!
|
||||
|
||||
Ansible questions are best asked on the `Ansible Google Group Mailing List <http://groups.google.com/group/ansible-project>`_.
|
||||
Ansible questions are best asked on the `Ansible Google Group Mailing List <http://groups.google.com/group/ansible-project>`_.
|
||||
|
||||
This is a very large high-traffic list for answering questions and sharing tips
|
||||
and tricks. Anyone can join, and email delivery is optional if you just want to read the group online. To cut down on spam, your first post is moderated, though posts are approved quickly.
|
||||
@ -42,10 +42,10 @@ This is a low-traffic read-only list, where we'll share release announcements an
|
||||
I'd Like To Help Share and Promote Ansible
|
||||
------------------------------------------
|
||||
|
||||
You can help share Ansible with others by telling friends and colleagues, writing a blog post,
|
||||
or presenting at user groups (like DevOps groups or the local LUG).
|
||||
You can help share Ansible with others by telling friends and colleagues, writing a blog post,
|
||||
or presenting at user groups (like DevOps groups or the local LUG).
|
||||
|
||||
You are also welcome to share slides on speakerdeck, sign up for a free account and tag it "Ansible". On Twitter,
|
||||
You are also welcome to share slides on speakerdeck, sign up for a free account and tag it “Ansible”. On Twitter,
|
||||
you can also share things with #ansible and may wish to `follow us <https://twitter.com/ansible>`_.
|
||||
|
||||
I'd Like To Help Ansible Move Faster
|
||||
@ -73,31 +73,31 @@ more quickly.
|
||||
Do not use the issue tracker for "how do I do this" type questions. These are great candidates
|
||||
for IRC or the mailing list instead where things are likely to be more of a discussion.
|
||||
|
||||
To be respectful of reviewers' time and allow us to help everyone efficiently, please
|
||||
To be respectful of reviewers' time and allow us to help everyone efficiently, please
|
||||
provide minimal well-reduced and well-commented examples versus sharing your entire production
|
||||
playbook. Include playbook snippets and output where possible.
|
||||
playbook. Include playbook snippets and output where possible.
|
||||
|
||||
When sharing YAML in playbooks, formatting can be preserved by using `code blocks <https://help.github.com/articles/creating-and-highlighting-code-blocks/>`_.
|
||||
|
||||
For multiple-file content, we encourage use of gist.github.com. Online pastebin content can expire, so it's nice to have things around for a longer term if they
|
||||
are referenced in a ticket.
|
||||
|
||||
If you are not sure if something is a bug yet, you are welcome to ask about something on
|
||||
the mailing list or IRC first.
|
||||
If you are not sure if something is a bug yet, you are welcome to ask about something on
|
||||
the mailing list or IRC first.
|
||||
|
||||
As we are a very high volume project, if you determine that
|
||||
As we are a very high volume project, if you determine that
|
||||
you do have a bug, please be sure to open the issue yourself to ensure we have a record of
|
||||
it. Don't rely on someone else in the community to file the bug report for you.
|
||||
it. Don’t rely on someone else in the community to file the bug report for you.
|
||||
|
||||
It may take some time to get to your report, see our information about priority flags below.
|
||||
|
||||
I'd Like To Help With Documentation
|
||||
-----------------------------------
|
||||
|
||||
Ansible documentation is a community project too!
|
||||
Ansible documentation is a community project too!
|
||||
|
||||
If you would like to help with the
|
||||
documentation, whether correcting a typo or improving a section, or maybe even
|
||||
If you would like to help with the
|
||||
documentation, whether correcting a typo or improving a section, or maybe even
|
||||
documenting a new feature, submit a GitHub pull request to the code that
|
||||
lives in the ``docsite/rst`` subdirectory of the project for most pages, and there is an "Edit on GitHub"
|
||||
link up on those.
|
||||
@ -107,9 +107,9 @@ Module documentation is generated from a ``DOCUMENTATION`` structure embedded in
|
||||
For more information on module documentation see `How to document modules <http://docs.ansible.com/ansible/dev_guide/developing_modules_documenting.html>`_.
|
||||
|
||||
Aside from modules, the main docs are in restructured text
|
||||
format.
|
||||
format.
|
||||
|
||||
If you aren't comfortable with restructured text, you can also open a ticket on
|
||||
If you aren’t comfortable with restructured text, you can also open a ticket on
|
||||
GitHub about any errors you spot or sections you would like to see added. For more information
|
||||
on creating pull requests, please refer to the
|
||||
`GitHub help guide <https://help.github.com/articles/using-pull-requests>`_.
|
||||
@ -155,7 +155,7 @@ If you make a mistake you do not need to close your PR. Instead, create a clean
|
||||
with ``--force`` to overwrite the existing branch (permissible in this case as no one else should be using that
|
||||
branch as reference). Code comments won't be lost, they just won't be attached to the existing branch.
|
||||
|
||||
We'll then review your contributions and engage with you about questions and so on.
|
||||
We’ll then review your contributions and engage with you about questions and so on.
|
||||
|
||||
Because we have a very large and active community it may take awhile to get your contributions
|
||||
in! See the notes about priorities in a later section for understanding our work queue.
|
||||
@ -172,14 +172,14 @@ are interested in writing new modules to be included in the core Ansible distrib
|
||||
to the `module development documentation <http://docs.ansible.com/developing_modules.html>`_.
|
||||
|
||||
Ansible's aesthetic encourages simple, readable code and consistent,
|
||||
conservatively extending, backwards-compatible improvements. When contributing code to Ansible,
|
||||
conservatively extending, backwards-compatible improvements. When contributing code to Ansible,
|
||||
please observe the following guidelines:
|
||||
|
||||
- Code developed for Ansible needs to support Python2-2.6 or higher and Python3-3.5 or higher.
|
||||
- Use a 4-space indent, not tabs.
|
||||
- We do not enforce 80 column lines; up to 160 columns are acceptable.
|
||||
- We do not accept 'style only' pull requests unless the code is nearly unreadable.
|
||||
- We are not strictly compliant with PEP8. See :doc:`dev_guide/testing_pep8` for more information.
|
||||
- We are "PEP8ish", but not strictly compliant, see :doc:`testing_pep8` for more information.
|
||||
|
||||
You can also contribute by testing and revising other requests, especially if it is one you are interested
|
||||
in using. Please keep your comments clear and to the point, courteous and constructive, tickets are not a
|
||||
@ -228,7 +228,7 @@ To subscribe to a group from a non-google account, you can send an email to the
|
||||
IRC Meetings
|
||||
------------
|
||||
|
||||
The Ansible community holds regular IRC meetings on various topics, and anyone who is interested is invited to
|
||||
The Ansible community holds regular IRC meetings on various topics, and anyone who is interested is invited to
|
||||
participate. For more information about Ansible meetings, consult the `meeting schedule and agenda page <https://github.com/ansible/community/blob/master/meetings/README.md>`_.
|
||||
|
||||
Release Numbering
|
||||
@ -286,7 +286,7 @@ These labels don't really have a definition - they are a simple ordering. Howev
|
||||
affecting a major module (yum, apt, etc) is likely to be prioritized higher than a module
|
||||
affecting a smaller number of users.
|
||||
|
||||
Since we place a strong emphasis on testing and code review, it may take a few months for a minor feature to get merged. This doesn't mean that we'll be exhausting all of the higher-priority queues before getting to your ticket; we will also take periodic sweeps through the lower priority queues and give them some attention as well, particularly in the area of new module changes.
|
||||
Since we place a strong emphasis on testing and code review, it may take a few months for a minor feature to get merged. This doesn't mean that we'll be exhausting all of the higher-priority queues before getting to your ticket; we will also take periodic sweeps through the lower priority queues and give them some attention as well, particularly in the area of new module changes.
|
||||
|
||||
|
||||
Every bit of effort helps - if you're wishing to expedite the inclusion of a P3 feature pull request for instance, the best thing you can do is help close P2 bug reports.
|
||||
|
||||
@ -41,9 +41,9 @@ Each module has at least one assigned maintainer, listed in a `maintainer's file
|
||||
.. _Ansibullbot: https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md
|
||||
.. _maintainer's file: https://github.com/ansible/ansible/blob/devel/.github/BOTMETA.yml
|
||||
|
||||
Some modules have no community maintainers assigned. In this case, the maintainer is listed as ``$team_ansible``. Ultimately, it's our goal to have at least one community maintainer for every module.
|
||||
Some modules have no community maintainers assigned. In this case, the maintainer is listed as ``$team_ansible``. Ultimately, it’s our goal to have at least one community maintainer for every module.
|
||||
|
||||
The maintainer's job is to review PRs and decide whether that PR should be merged (``shipit``) or revised (``needs_revision``).
|
||||
The maintainer’s job is to review PRs and decide whether that PR should be merged (``shipit``) or revised (``needs_revision``).
|
||||
|
||||
The ultimate goal of any pull request is to reach **shipit** status, where the Core team then decides whether the PR is ready to be merged. Not every PR that reaches the **shipit** label is actually ready to be merged, but the better our reviewers are, and the better our guidelines are, the more likely it will be that a PR that reaches **shipit** will be mergeable.
|
||||
|
||||
@ -54,7 +54,7 @@ Workflow
|
||||
|
||||
Ansibullbot runs continuously. You can generally expect to see changes to your issue or pull request within thirty minutes. Ansibullbot examines every open pull request in the repositories, and enforces state roughly according to the following workflow:
|
||||
|
||||
- If a pull request has no workflow labels, it's considered **new**. Files in the pull request are identified, and the maintainers of those files are pinged by the bot, along with instructions on how to review the pull request. (Note: sometimes we strip labels from a pull request to "reboot" this process.)
|
||||
- If a pull request has no workflow labels, it’s considered **new**. Files in the pull request are identified, and the maintainers of those files are pinged by the bot, along with instructions on how to review the pull request. (Note: sometimes we strip labels from a pull request to “reboot” this process.)
|
||||
- If the module maintainer is not ``$team_ansible``, the pull request then goes into the **community_review** state.
|
||||
- If the module maintainer is ``$team_ansible``, the pull request then goes into the **core_review** state (and probably sits for a while).
|
||||
- If the pull request is in **community_review** and has received comments from the maintainer:
|
||||
@ -110,4 +110,4 @@ Special Labels
|
||||
|
||||
- **new_plugin**: this is for new modules or plugins that are not yet in Ansible.
|
||||
|
||||
**Note:** `new_plugin` kicks off a completely separate process, and frankly it doesn't work very well at present. We're working our best to improve this process.
|
||||
**Note:** `new_plugin` kicks off a completely separate process, and frankly it doesn’t work very well at present. We’re working our best to improve this process.
|
||||
|
||||
@ -15,13 +15,13 @@ In addition to the information below, module maintainers should be familiar with
|
||||
Maintainer Responsibilities
|
||||
===========================
|
||||
|
||||
When you contribute a new module to the [ansible/ansible](https://github.com/ansible/ansible) repository, you become the maintainer for that module once it has been merged. Maintainership empowers you with the authority to accept, reject, or request revisions to pull requests on your module -- but as they say, "with great power comes great responsibility."
|
||||
When you contribute a new module to the `ansible/ansible <https://github.com/ansible/ansible>`_ repository, you become the maintainer for that module once it has been merged. Maintainership empowers you with the authority to accept, reject, or request revisions to pull requests on your module -- but as they say, "with great power comes great responsibility."
|
||||
|
||||
Maintainers of Ansible modules are expected to provide feedback, responses, or actions on pull requests or issues to the module(s) they maintain in a reasonably timely manner.
|
||||
|
||||
It is also recommended that you occasionally revisit the [contribution guidelines](https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md), as they are continually refined. Occasionally, you may be requested to update your module to move it closer to the general accepted standard requirements. We hope for this to be infrequent, and will always be a request with a fair amount of lead time (ie: not by tomorrow!).
|
||||
It is also recommended that you occasionally revisit the `contribution guidelines <https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md>`_, as they are continually refined. Occasionally, you may be requested to update your module to move it closer to the general accepted standard requirements. We hope for this to be infrequent, and will always be a request with a fair amount of lead time (ie: not by tomorrow!).
|
||||
|
||||
Finally, following the [ansible-devel](https://groups.google.com/forum/#!forum/ansible-devel) mailing list can be a great way to participate in the broader Ansible community, and a place where you can influence the overall direction, quality, and goals of Ansible and its modules. If you're not on this relatively low-volume list, please join us here: https://groups.google.com/forum/#!forum/ansible-devel
|
||||
Finally, following the `ansible-devel <https://groups.google.com/forum/#!forum/ansible-devel>`_ mailing list can be a great way to participate in the broader Ansible community, and a place where you can influence the overall direction, quality, and goals of Ansible and its modules. If you're not on this relatively low-volume list, please join us here: https://groups.google.com/forum/#!forum/ansible-devel
|
||||
|
||||
The Ansible community hopes that you will find that maintaining your module is as rewarding for you as having the module is for the wider community.
|
||||
|
||||
@ -31,21 +31,21 @@ Pull Requests, Issues, and Workflow
|
||||
Pull Requests
|
||||
-------------
|
||||
|
||||
Module pull requests are located in the [main Ansible repository](https://github.com/ansible/ansible/pulls).
|
||||
Module pull requests are located in the `main Ansible repository <https://github.com/ansible/ansible/pulls>`_.
|
||||
|
||||
Because of the high volume of pull requests, notification of PRs to specific modules are routed by an automated bot to the appropriate maintainer for handling. It is recommended that you set an appropriate notification process to receive notifications which mention your GitHub ID.
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
Issues for modules, including bug reports, documentation bug reports, and feature requests, are tracked in the [ansible repository](https://github.com/ansible/ansible/issues).
|
||||
Issues for modules, including bug reports, documentation bug reports, and feature requests, are tracked in the `ansible repository <https://github.com/ansible/ansible/issues>`_.
|
||||
|
||||
Issues for modules are routed to their maintainers via an automated process. This process is still being refined, and currently depends upon the issue creator to provide adequate details (specifically, providing the proper module name) in order to route it correctly. If you are a maintainer of a specific module, it is recommended that you periodically search module issues for issues which mention your module's name (or some variation on that name), as well as setting an appropriate notification process for receiving notification of mentions of your GitHub ID.
|
||||
|
||||
PR Workflow
|
||||
-----------
|
||||
|
||||
Automated routing of pull requests is handled by a tool called [Ansibot](https://github.com/ansible/ansibullbot).
|
||||
Automated routing of pull requests is handled by a tool called `Ansibot <https://github.com/ansible/ansibullbot>`_.
|
||||
|
||||
Being moderately familiar with how the workflow behind the bot operates can be helpful to you, and -- should things go awry -- your feedback can be helpful to the folks that continually help Ansibullbot to evolve.
|
||||
|
||||
|
||||
@ -16,5 +16,3 @@ of some of the most popular of these tools.
|
||||
- `Ansigenome <https://github.com/nickjj/ansigenome>`_ is a command line tool designed to help you manage your Ansible roles.
|
||||
|
||||
- `Awesome Ansible <https://github.com/jdauphant/awesome-ansible>`_ is a collaboratively curated list of awesome Ansible resources.
|
||||
|
||||
- `Ansible Inventory Grapher <http://github.com/willthames/ansible-inventory-grapher>_` can be used to visually display inventory inheritance hierarchies and at what level a variable is defined in inventory.
|
||||
|
||||
@ -25,7 +25,7 @@ For multiple-file content, we encourage use of gist.github.com. Online pastebin
|
||||
|
||||
If you are not sure if something is a bug yet, you are welcome to ask about something on the mailing list or IRC first.
|
||||
|
||||
As we are a very high volume project, if you determine that you do have a bug, please be sure to open the issue yourself to ensure we have a record of it. Don't rely on someone else in the community to file the bug report for you.
|
||||
As we are a very high volume project, if you determine that you do have a bug, please be sure to open the issue yourself to ensure we have a record of it. Don’t rely on someone else in the community to file the bug report for you.
|
||||
|
||||
Requesting a feature
|
||||
====================
|
||||
|
||||
@ -17,19 +17,19 @@ import sys
|
||||
import os
|
||||
|
||||
# pip install sphinx_rtd_theme
|
||||
# import sphinx_rtd_theme
|
||||
# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
#import sphinx_rtd_theme
|
||||
#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# If your extensions are in another directory, add it here. If the directory
|
||||
# is relative to the documentation root, use os.path.abspath to make it
|
||||
# absolute, like shown here.
|
||||
# sys.path.append(os.path.abspath('some/directory'))
|
||||
#sys.path.append(os.path.abspath('some/directory'))
|
||||
#
|
||||
sys.path.insert(0, os.path.join('ansible', 'lib'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
|
||||
VERSION = '2.4'
|
||||
AUTHOR = 'Ansible, Inc'
|
||||
VERSION='2.2'
|
||||
AUTHOR='Ansible, Inc'
|
||||
|
||||
|
||||
# General configuration
|
||||
@ -38,7 +38,7 @@ AUTHOR = 'Ansible, Inc'
|
||||
# Add any Sphinx extension module names here, as strings.
|
||||
# They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
|
||||
# Later on, add 'sphinx.ext.viewcode' to the list if you want to have
|
||||
# colorized code generated too for references.
|
||||
@ -55,7 +55,7 @@ master_doc = 'index'
|
||||
|
||||
# General substitutions.
|
||||
project = 'Ansible Documentation'
|
||||
copyright = "2013-2017 Ansible, Inc"
|
||||
copyright = "2013-2016 Ansible, Inc"
|
||||
|
||||
# The default replacements for |version| and |release|, also used in various
|
||||
# other places throughout the built documents.
|
||||
@ -67,16 +67,16 @@ release = VERSION
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
# unused_docs = []
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directories, that shouldn't be
|
||||
# searched for source files.
|
||||
# exclude_dirs = []
|
||||
#exclude_dirs = []
|
||||
|
||||
# A list of glob-style patterns that should be excluded when looking
|
||||
# for source files.
|
||||
@ -84,26 +84,26 @@ exclude_patterns = ['modules']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
highlight_language = 'YAML+Jinja'
|
||||
|
||||
# Substitutions, variables, entities, & shortcuts for text which do not need to link to anything.
|
||||
# For titles which should be a link, use the intersphinx anchors set at the index, chapter, and section levels, such as qi_start_:
|
||||
#Substitutions, variables, entities, & shortcuts for text which do not need to link to anything.
|
||||
#For titles which should be a link, use the intersphinx anchors set at the index, chapter, and section levels, such as qi_start_:
|
||||
rst_epilog = """
|
||||
.. |acapi| replace:: *Ansible Core API Guide*
|
||||
.. |acrn| replace:: *Ansible Core Release Notes*
|
||||
@ -128,28 +128,28 @@ html_short_title = 'Ansible Documentation'
|
||||
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
||||
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
||||
# given in html_static_path.
|
||||
# html_style = 'solar.css'
|
||||
#html_style = 'solar.css'
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = 'Ansible Documentation'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (within the static path) to place at the top of
|
||||
# the sidebar.
|
||||
# html_logo = None
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = 'favicon.ico'
|
||||
#html_favicon = 'favicon.ico'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
# html_static_path = ['.static']
|
||||
#html_static_path = ['.static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
@ -157,23 +157,23 @@ html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_use_modindex = True
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
#html_split_index = False
|
||||
|
||||
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
||||
html_copy_source = False
|
||||
@ -181,10 +181,10 @@ html_copy_source = False
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = ''
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Poseidodoc'
|
||||
@ -194,37 +194,34 @@ htmlhelp_basename = 'Poseidodoc'
|
||||
# ------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
# latex_paper_size = 'letter'
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# latex_font_size = '10pt'
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, document class
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'ansible.tex', 'Ansible 2.2 Documentation', AUTHOR, 'manual'),
|
||||
('index', 'ansible.tex', 'Ansible 1.2 Documentation',
|
||||
AUTHOR, 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# latex_preamble = ''
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_use_modindex = True
|
||||
#latex_use_modindex = True
|
||||
|
||||
autoclass_content = 'both'
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/2/', (None, '../python2-2.7.13.inv')),
|
||||
'python3': ('https://docs.python.org/3/', (None, '../python3-3.6.2.inv')),
|
||||
'jinja2': ('http://jinja.pocoo.org/docs/', (None, '../jinja2-2.9.7.inv'))}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user