Compare commits
1096 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e07227194b | |||
| 0a47cf5ee1 | |||
| 69fbc5ad1f | |||
| ce6bd0c96d | |||
| f5d7005e6c | |||
| 9961c1e2ef | |||
| 33be7526c7 | |||
| 4f3b671e47 | |||
| 84f485620c | |||
| c8245f3440 | |||
| aed907958d | |||
| 06923cd1f6 | |||
| 23831668c1 | |||
| 4b7f2579a8 | |||
| 9441bd723f | |||
| a94a228cb4 | |||
| a908bd6a8f | |||
| 6f05eb9b69 | |||
| 731ef69eae | |||
| cff8ea1434 | |||
| 3fd77edecd | |||
| d3b543c2a2 | |||
| ee4bb97ab7 | |||
| 4e79835b34 | |||
| 6701726ba5 | |||
| 28ca655217 | |||
| d5c5390acf | |||
| 736b554691 | |||
| f834c62ffb | |||
| 558ca593f3 | |||
| c98b828950 | |||
| 3934cd7601 | |||
| 6aafb42f2f | |||
| ee268535f6 | |||
| aaa413db3b | |||
| feb497a9e8 | |||
| 1c77f95312 | |||
| bde44f9df4 | |||
| 9d9431109b | |||
| 08a6ac3fc7 | |||
| 84cc3e2c8a | |||
| bb3e7f4719 | |||
| 25162c98ec | |||
| d21d3677a6 | |||
| 514f9232a0 | |||
| b0f7296dde | |||
| db7149da1c | |||
| 5252b7bc93 | |||
| 31d1afcb19 | |||
| 4abf23d34d | |||
| 9991dfee6c | |||
| 88e4051afc | |||
| 8344511e47 | |||
| c16572b6e2 | |||
| 339c05bb88 | |||
| ac16efe289 | |||
| 4448391244 | |||
| 946e9473a9 | |||
| 63b1be4f37 | |||
| d779f88149 | |||
| 5be35f270c | |||
| 96f6244336 | |||
| eef726258d | |||
| f8fcbc6bd7 | |||
| 620aeeb572 | |||
| e9bf75156e | |||
| e5b013ea41 | |||
| e4c935835f | |||
| c9453daf28 | |||
| b2ea92d36d | |||
| 7583704144 | |||
| f3407dec0e | |||
| 4405244224 | |||
| fea5d36417 | |||
| e35d5188f9 | |||
| f160502b7b | |||
| 7da94e0359 | |||
| 41195d0d49 | |||
| b1563c8411 | |||
| eedd92d711 | |||
| e98153d26d | |||
| ab44a4abba | |||
| 1aaf86069a | |||
| 036f853d42 | |||
| 2f6b9e69a7 | |||
| 05ae500d78 | |||
| 8831e59700 | |||
| 8dadf8a905 | |||
| 7b6f8e485a | |||
| 2c90298841 | |||
| fb686d40bc | |||
| 971dd53622 | |||
| dd3beb844f | |||
| c5d2f6b0d3 | |||
| 60f06c36b3 | |||
| 29d49d415f | |||
| 7644dbe330 | |||
| 621708e976 | |||
| aa98c84a62 | |||
| 85d6af637a | |||
| 70ed560fb3 | |||
| 6886683e16 | |||
| 291648ccd9 | |||
| 579b80ab8f | |||
| d90210973d | |||
| 271db7bddb | |||
| 35163bce96 | |||
| b1ccda00f8 | |||
| 1487b6a1bb | |||
| 3a8dc2308b | |||
| a82522a5c4 | |||
| 20a7227056 | |||
| 6d8d1ad426 | |||
| 90944ec45a | |||
| 4b04d7a914 | |||
| 0028a9d651 | |||
| 25e415c031 | |||
| 7399224428 | |||
| 1f91a70310 | |||
| de34c7c133 | |||
| f4484f800b | |||
| 6042419624 | |||
| 5927373c26 | |||
| 5fe8205d36 | |||
| 5612cce4a4 | |||
| 6d183959ba | |||
| 2063e10c00 | |||
| 60d3e9f3de | |||
| 034e8f59ed | |||
| 0841ed4796 | |||
| 2b24131baa | |||
| 788e11f1db | |||
| dfaef8061a | |||
| a18e9d64c7 | |||
| 34acc95435 | |||
| 69ad124bd2 | |||
| 5849ab3162 | |||
| 89f8562df9 | |||
| b8b34b5110 | |||
| baa91ebc95 | |||
| 2786149bdc | |||
| d5837aab23 | |||
| f3d7294690 | |||
| 448c89a06d | |||
| d6b3f40cb7 | |||
| dfdf4ab8eb | |||
| 1a51af0e38 | |||
| 6d2f003ceb | |||
| 7e542d78a7 | |||
| 062484b9bd | |||
| e74b9e1d34 | |||
| b505a3796c | |||
| e2497f0cff | |||
| 79069827c2 | |||
| 6b0ef9fd26 | |||
| a873e1a467 | |||
| e593c828b4 | |||
| 110244d7b4 | |||
| 8ae9831439 | |||
| dbeb26ad6d | |||
| fc9a6bb07c | |||
| ee679c0190 | |||
| 85fb83a5db | |||
| da92ce796b | |||
| ae94b050af | |||
| 23a62f5a78 | |||
| 30c019447b | |||
| bb58d3f20c | |||
| 7788dea238 | |||
| d855b4bcf6 | |||
| 8ae9125ff2 | |||
| 822cb74aed | |||
| f1dfddc5e5 | |||
| bbf95abc8f | |||
| d5bb7258c8 | |||
| 6b5f19a796 | |||
| 0ebc46628a | |||
| e8b0b07311 | |||
| cdb8343ec6 | |||
| 7fed89b51d | |||
| 2290dfe1ef | |||
| 2f74cd84bd | |||
| 790ed81b14 | |||
| de912381ac | |||
| 06688efb24 | |||
| 49fc8c7634 | |||
| 98624cb394 | |||
| 3f149e09bc | |||
| 2dcd0846ab | |||
| e07bfd9ef8 | |||
| 4bd17ee518 | |||
| dcee25c6db | |||
| 3d66ca59f9 | |||
| 04954dbc1e | |||
| 612561ad0e | |||
| 62a56a219b | |||
| 013aa2533a | |||
| 1b36446ab3 | |||
| 5a16a270fc | |||
| 136a7d5604 | |||
| 46a4ae74a7 | |||
| 1e35ac7603 | |||
| 9541b47b52 | |||
| c3a6e8dfd6 | |||
| b39aed8736 | |||
| 5503cfaa44 | |||
| f32778aa93 | |||
| 5c09fd4210 | |||
| 343e07a31a | |||
| 4a95194424 | |||
| dd727ecebf | |||
| 56e8863683 | |||
| 2319c4d74f | |||
| 35fdc5411f | |||
| 87d9eeae57 | |||
| 2eac8c6c29 | |||
| 41dd4a1f93 | |||
| d6e4b1bf5b | |||
| bb0b2305e3 | |||
| f010bcde33 | |||
| 4a6b5c5de2 | |||
| c8854a23ea | |||
| 63b0e5e752 | |||
| 250f52bfba | |||
| 8c5a146013 | |||
| c5d7ef3d7f | |||
| 26bbac3062 | |||
| 051f66cf16 | |||
| b58228a73b | |||
| 323cbdafb7 | |||
| d74a61d362 | |||
| cc82e7dd06 | |||
| 3d65d6159f | |||
| a1c23af6fc | |||
| 91dc8bc136 | |||
| 70907f574e | |||
| 0679537495 | |||
| 1737d6b884 | |||
| 9f1690ab64 | |||
| 70b063ce0e | |||
| a9dad7f3bd | |||
| ba22ca13ba | |||
| f470ebe6b7 | |||
| 0592e1be79 | |||
| ebc2fe08c3 | |||
| a95bff0f5f | |||
| 5d7ab8a300 | |||
| adfc4d6f59 | |||
| 8c8552ac32 | |||
| e07674d756 | |||
| 3d5c4df2ac | |||
| 184523ecac | |||
| c3f443f56c | |||
| 8f5beb74c5 | |||
| 05464816c2 | |||
| 03af450bdc | |||
| 9ed59da294 | |||
| 9f6da7b885 | |||
| 761330b125 | |||
| 94546831b4 | |||
| 348b49c166 | |||
| 88433eb93f | |||
| 826c71fcb1 | |||
| e170d7304b | |||
| a6f64a8e04 | |||
| 01e922aa69 | |||
| 85fb7c6d4b | |||
| 8fa2070074 | |||
| 53d24ef002 | |||
| 046c6c66f2 | |||
| ad5433b166 | |||
| f67aa85c3f | |||
| 85a31616de | |||
| b3548703f7 | |||
| d14741799a | |||
| 459d9ea30d | |||
| 212296e59c | |||
| cef73d1545 | |||
| fedfd18774 | |||
| f11ab4383a | |||
| 7acc0a632b | |||
| 1d27909ec0 | |||
| 20c1a67711 | |||
| e1b1702616 | |||
| 564a212b3c | |||
| 56d45074af | |||
| 17423c9268 | |||
| 9bab05bafc | |||
| 765061d445 | |||
| 4677699500 | |||
| 6616fba1ab | |||
| 5f04ef8b4c | |||
| 7779da264d | |||
| a1f192976f | |||
| 83029755f7 | |||
| f897f19fc5 | |||
| 14f7c85ce7 | |||
| b90eccc4f8 | |||
| 2165351f2c | |||
| b2ad63a860 | |||
| 5683277e4a | |||
| ca87fa56d8 | |||
| f3d9f8da7b | |||
| 35de8da108 | |||
| 48c5d696d2 | |||
| f785f1ea15 | |||
| a83aa1071b | |||
| 695b4bcb46 | |||
| 886a17853f | |||
| f2dc815e3f | |||
| 6600f7b7ac | |||
| ac3ec0a410 | |||
| b86dc96d55 | |||
| a72a6e42c5 | |||
| 6477bdc6fc | |||
| e620fed755 | |||
| 880328c10f | |||
| 31a4fe412f | |||
| a2f76c1c69 | |||
| 22f3aef4dc | |||
| 19b78cedc8 | |||
| 5846c1b8c4 | |||
| d0eba79760 | |||
| 8e9344eef7 | |||
| d9bc01f443 | |||
| 635689b3af | |||
| eb8a11237c | |||
| 7ef70c263f | |||
| cf3e368d15 | |||
| 190e4c26d1 | |||
| f67c070c0a | |||
| 2275b2f950 | |||
| 8b737c959d | |||
| a00d6d7234 | |||
| f9bdf07387 | |||
| f8060d109e | |||
| 349af83c91 | |||
| 1290aa2668 | |||
| 7491054ed6 | |||
| e4338d0ca3 | |||
| 8a4df98f69 | |||
| f13e20655e | |||
| f5e0fbf65b | |||
| e756ee3741 | |||
| 5aab75e71a | |||
| 28698d5fcf | |||
| b96da3e5b0 | |||
| 705f19d0e2 | |||
| b8a2a526fc | |||
| 39c4a4f8a7 | |||
| 3582c20abb | |||
| bf6e783d7a | |||
| 8ca106d266 | |||
| 1af4677b66 | |||
| 8a8ffa2342 | |||
| 61ee632d23 | |||
| 80a9016636 | |||
| 7a3b2ac287 | |||
| 754888d896 | |||
| dfee6e19ca | |||
| ddb358a3cf | |||
| ed07940386 | |||
| be19e21126 | |||
| 13e8ef5f35 | |||
| 7d86663dd2 | |||
| 62db667760 | |||
| 2d7691500e | |||
| e744e864d9 | |||
| 1fd5de21d2 | |||
| 8b55217df0 | |||
| e442175a9e | |||
| 78911f1b26 | |||
| 4823371aff | |||
| 5599287927 | |||
| 2d81a1fa77 | |||
| be9d7df6c4 | |||
| e655e2f051 | |||
| 9261d3962b | |||
| d80ec35183 | |||
| 95d7b4c5db | |||
| ac5afcae28 | |||
| b1d88235f0 | |||
| e1c83b0367 | |||
| e30e9a74c2 | |||
| a4620920b2 | |||
| aa5ef9561d | |||
| 6184a0a313 | |||
| 0d0bc5e620 | |||
| 29ffed869c | |||
| 2aaaf986d3 | |||
| a75348b406 | |||
| 8898643c1d | |||
| 9377c3f525 | |||
| 5a9ca1e513 | |||
| 57c9534abb | |||
| 0604d580d9 | |||
| e108d1758c | |||
| bc01f0d6fb | |||
| 1193810d46 | |||
| 0649e637d3 | |||
| ce5dceab0e | |||
| 08a77135f3 | |||
| 508e6ecf43 | |||
| 25a460fe0f | |||
| 5e9793d742 | |||
| a040807fd0 | |||
| 7c600ae0d3 | |||
| 7b8bd280e8 | |||
| 94de4db928 | |||
| c976238a81 | |||
| e0fb264a89 | |||
| 27b2ae8ddc | |||
| c84e76e31d | |||
| d25ee940c4 | |||
| cc7b34e9bd | |||
| 6bbaa26f64 | |||
| 8c370645a6 | |||
| 4b29c2cf74 | |||
| 62d038dca4 | |||
| 06cfc52afd | |||
| 618a519ee3 | |||
| 6b5088c676 | |||
| 88f1294f74 | |||
| 0c855a85bc | |||
| dff7b0fdbc | |||
| 405c097c43 | |||
| 83f277cfe6 | |||
| 9a7a2a4d60 | |||
| d68f22a28d | |||
| e7af005408 | |||
| 10bce54ef0 | |||
| 9e4fac5ebd | |||
| 6506e17eff | |||
| 6c6f2b46b4 | |||
| ee5246fc36 | |||
| 42620d2f19 | |||
| b55ef665ba | |||
| 66d0acfde3 | |||
| 151085c745 | |||
| 0db5ccfecb | |||
| 69612ba16d | |||
| d47e15a1f2 | |||
| 3035394159 | |||
| c2daa77089 | |||
| af8f11e3f7 | |||
| 19c0202aaf | |||
| 69517832cd | |||
| c63189f650 | |||
| 07101085ca | |||
| 51216dcfae | |||
| 84f98534ae | |||
| d181a64315 | |||
| 489844f960 | |||
| 5530d771b4 | |||
| df147ab921 | |||
| 8a4e969b93 | |||
| f52624f6d2 | |||
| faed650bb1 | |||
| 620accf117 | |||
| f500eeec1d | |||
| f8a9541405 | |||
| 730b617186 | |||
| 8e71b26264 | |||
| 3270c097ef | |||
| 6b6e3c64c8 | |||
| 58a4d2f7b4 | |||
| 27d3ac9dc6 | |||
| 5dab2efcdf | |||
| 29ac1a8efc | |||
| 734db4ffe9 | |||
| 9778eaf4f9 | |||
| 4e9a970616 | |||
| b355dca7dd | |||
| b45437541a | |||
| 069feb5d9c | |||
| 04c535b1e9 | |||
| 5d6022b001 | |||
| 34f7e6ffa0 | |||
| 3939f7a812 | |||
| 2f97afb634 | |||
| fc1d6ab6b5 | |||
| f5f17e98ff | |||
| 0b3e05d9a4 | |||
| b75c662ab7 | |||
| 4e6d89b5b5 | |||
| 0ff151f70b | |||
| 78b7a3a115 | |||
| 8d97bb4f8e | |||
| 9bb83067bc | |||
| f6af88fe9f | |||
| 21e23b34d2 | |||
| 5a75433ce9 | |||
| 4ded8cb3b3 | |||
| cc948f339c | |||
| b8c4bb9e6e | |||
| 60e0410298 | |||
| c22d86b92f | |||
| 1bd9ea6440 | |||
| 8912258016 | |||
| 4cd3262f50 | |||
| e039d88438 | |||
| 5072ed3bd3 | |||
| 95bbe2ef8b | |||
| b2af95e4a6 | |||
| 57f6ad458a | |||
| e1ecb95b3d | |||
| 08b70949c7 | |||
| eefe66f1cc | |||
| b25b9fd840 | |||
| 544a8e44d7 | |||
| e041c2e2cd | |||
| e7e68e69c2 | |||
| c607ba5740 | |||
| a478e5c981 | |||
| 086400cde0 | |||
| d77be4eecf | |||
| 5da4ab2d90 | |||
| d5b8553d1c | |||
| 2a002f5c0b | |||
| dd11b01350 | |||
| 9e78b10ae3 | |||
| 5bb0f6908b | |||
| bf47bb4753 | |||
| d72eaa3fc5 | |||
| 65fe7b7003 | |||
| 94eb11a6d7 | |||
| 7df0e5259f | |||
| 70777f044d | |||
| 4cffdbbc56 | |||
| 0f5a4e2112 | |||
| c0aac03b0c | |||
| 99746d4a83 | |||
| 49db16752f | |||
| 16bf3e15c7 | |||
| 56b1a8ceed | |||
| 88d1285f33 | |||
| 06b914c5b3 | |||
| f5481621f8 | |||
| 4a2bf7f60a | |||
| 0725dd8dc8 | |||
| aca23f9402 | |||
| f9a65f42d1 | |||
| 4c5d98c7ee | |||
| 7f3736bc2c | |||
| 1f4cb79787 | |||
| 4503ad7c04 | |||
| e7eab93243 | |||
| 04555f525e | |||
| 58ad934283 | |||
| 1829d519b5 | |||
| d7f83471ae | |||
| 522a3b5974 | |||
| 1f8696f5c1 | |||
| 046e43ec11 | |||
| cf9d7cd68e | |||
| ba1c9b04cb | |||
| dd023d137a | |||
| 84f2968023 | |||
| 0cdd081b53 | |||
| 9d5a79f586 | |||
| c0495c3b9b | |||
| 1da9d23f90 | |||
| e12f3d4029 | |||
| b9e100a506 | |||
| df779decf6 | |||
| 565f336182 | |||
| 14a9c3ab9e | |||
| 95540f40b0 | |||
| 5b57011152 | |||
| 335f136e71 | |||
| e795fa1ec2 | |||
| 925c9eb38f | |||
| 8ebd228bd3 | |||
| c0638842d8 | |||
| aa91e11906 | |||
| 5b49a059ac | |||
| 20fc6a29d3 | |||
| b3b01bb7a3 | |||
| 20c6e5302b | |||
| 9d88b3eedd | |||
| eabbaaec46 | |||
| 869a97e1cb | |||
| ccd0952d54 | |||
| 0709c48f64 | |||
| f456d999a9 | |||
| ad9e2939bf | |||
| c25ead382c | |||
| 022b2290bb | |||
| dc14681530 | |||
| 8621539341 | |||
| 4280e00d56 | |||
| 7ef3e7dccb | |||
| 49bef3f6c2 | |||
| fa71237f8a | |||
| ba97ad13e3 | |||
| 8a09e1281b | |||
| 633e513a65 | |||
| 975c323e39 | |||
| 979b778430 | |||
| b36aa61237 | |||
| 23cd7ad08a | |||
| d158218c3f | |||
| 14db2fa376 | |||
| 8360f9f46c | |||
| a2307aa7a0 | |||
| 2dd430d9c0 | |||
| e4d827e6de | |||
| df5d134ab9 | |||
| 151a9b385d | |||
| 4d003127fa | |||
| 074667261e | |||
| 8cb9cace33 | |||
| e052cf4727 | |||
| 16c57c6674 | |||
| 92de282430 | |||
| edfcdbffa1 | |||
| c0d40439a1 | |||
| 2b6d21d23c | |||
| b3ee7fdd97 | |||
| 566f220d16 | |||
| edd2a3c6ed | |||
| 620a6c4185 | |||
| 496a2b7f56 | |||
| deb51becc2 | |||
| 4c42721619 | |||
| 92c37c9c07 | |||
| 104c3d7d6e | |||
| 256d61e1e7 | |||
| 23ae68de4f | |||
| 56649eb76b | |||
| e8a760a042 | |||
| 800bf4b232 | |||
| e7de31bb18 | |||
| 500941917d | |||
| a2e5d38f59 | |||
| c68a5661a4 | |||
| f247579221 | |||
| b0c2545dc4 | |||
| f3ae13a052 | |||
| 09f7bd1a9b | |||
| b12eeec85f | |||
| f542e10cc6 | |||
| 67fcd7bea1 | |||
| 87edeaf5b6 | |||
| 85277e9e64 | |||
| d9e8a6c2e1 | |||
| b9f7ea7198 | |||
| 47eea55d13 | |||
| 5b6046545e | |||
| 720953664c | |||
| 989817657f | |||
| 9e51b96a10 | |||
| 3830b2ce1d | |||
| 3299485d9b | |||
| 47542aefc5 | |||
| be6b9b0594 | |||
| d91e07b611 | |||
| 3a8c277cb8 | |||
| 922e34c144 | |||
| c4522d9f29 | |||
| b57e9c9a27 | |||
| 038ebac577 | |||
| 49cc3c05b8 | |||
| 1133b6dd94 | |||
| ee2e913315 | |||
| 68e31cf23f | |||
| 514f491514 | |||
| 07580692f5 | |||
| 3ebf6cb223 | |||
| b8ba2d3066 | |||
| 0483da674b | |||
| 4caa76cbd8 | |||
| fce2650d5f | |||
| 68f07e83d9 | |||
| cadd806a67 | |||
| c351c47cea | |||
| 0e83972847 | |||
| a252abcc6c | |||
| beb9d3ac1e | |||
| 2019a97b4e | |||
| 9ad592965a | |||
| 8a9b8a727b | |||
| 5c2bad90c9 | |||
| 1605fc1fad | |||
| b72b1eb50c | |||
| 1824dd33cf | |||
| 6a5a7bd0f8 | |||
| e02457015b | |||
| 3b562aa367 | |||
| f81f541354 | |||
| 3c4e9fdc98 | |||
| de207dc3fa | |||
| dad0e0fefb | |||
| 815a5408e0 | |||
| 7fc9ac4cf9 | |||
| 1676ff4e2d | |||
| 44e3eef15e | |||
| 1ac07ee677 | |||
| 0926c91b28 | |||
| 0219d88fdd | |||
| 7bd6fde2a3 | |||
| cb0fb4c42f | |||
| 728df81bab | |||
| f28ea0cda1 | |||
| a07e928358 | |||
| 1f3e22687e | |||
| 10c89f4d49 | |||
| 41e524eb58 | |||
| df53801e0f | |||
| ce9401beda | |||
| b721176bdd | |||
| 2befb446a3 | |||
| f4245ee5f0 | |||
| 331cfa6b36 | |||
| ea3bf960aa | |||
| 688086c54a | |||
| 391acfb20c | |||
| af762446a1 | |||
| 54a9f17b25 | |||
| fc7591eeaa | |||
| b1c6990129 | |||
| 3e296ae3aa | |||
| 04cc44bf17 | |||
| 4581c19fa0 | |||
| b3f0678f2d | |||
| a29d532711 | |||
| 50e95372d3 | |||
| 05ded8d363 | |||
| 056061922e | |||
| 65b3792130 | |||
| ff9072db7d | |||
| 0716835e47 | |||
| 2085ec0971 | |||
| 2be8cbfa2b | |||
| d36fdb84b0 | |||
| e028ce7c2e | |||
| 2f9e3d141e | |||
| f1ae9eb98b | |||
| fb5d2d10db | |||
| 8328bb7d63 | |||
| 10387cb19c | |||
| d47a833a8d | |||
| 866863c209 | |||
| 6642d2447e | |||
| 77e471650b | |||
| 532381203e | |||
| 4d7f9fbb70 | |||
| f732aa3d1c | |||
| fa6aa5ef98 | |||
| b9d8ca4f30 | |||
| ce2cdc22c4 | |||
| 71d7f1acf9 | |||
| 157bdbbd73 | |||
| 53328a8f80 | |||
| d6a1adb1d3 | |||
| 980b675e5d | |||
| 1ff800bab3 | |||
| 717884b61a | |||
| 47629a455c | |||
| dde93ae55a | |||
| 6dd26386af | |||
| 30f42b9735 | |||
| 5d5231c4d0 | |||
| dde17404c6 | |||
| 962e6e5572 | |||
| 1aa3c1522c | |||
| 3b5ea2aa71 | |||
| dac0db9ca2 | |||
| b76e8c9028 | |||
| 2de89035e7 | |||
| 49385bb3a3 | |||
| 1e32e8bbf7 | |||
| 2253fa1019 | |||
| 75d5f2579d | |||
| cd2f44877a | |||
| 840f3c951b | |||
| bb7ef45347 | |||
| 2baf4f2e22 | |||
| 8fb2e2707c | |||
| a4b07056ce | |||
| c39b7261f1 | |||
| eef5bf4778 | |||
| e32866d137 | |||
| 2078627e3b | |||
| c4a6b68cb9 | |||
| 288499e391 | |||
| 10fc56adf0 | |||
| db19d83e40 | |||
| 58cb60c8af | |||
| ebdb991813 | |||
| fb7ab7b7ed | |||
| 40d790f6b9 | |||
| e15da1563b | |||
| a4004e50df | |||
| 0ca377eea4 | |||
| 69e8b75dc3 | |||
| 6491e0a1fb | |||
| f187227cb6 | |||
| 1694146c2d | |||
| 769ea7aefc | |||
| 5a58eb16c0 | |||
| 2f71eb0366 | |||
| f131a7da36 | |||
| 355bfc6d21 | |||
| 4825c0f9c0 | |||
| 616cfd27b2 | |||
| d7a7b6cb59 | |||
| 6eabfa0749 | |||
| a87f46727b | |||
| f39ab48239 | |||
| 726d0bd79f | |||
| d3aaecba43 | |||
| def22a9cf0 | |||
| dfd60149c0 | |||
| 799fb60051 | |||
| db014182e6 | |||
| c5f791a297 | |||
| 4c4a96b088 | |||
| 8e0560f41c | |||
| 52bcdf5e38 | |||
| 78654d7b6d | |||
| def40d0389 | |||
| 3807e16cad | |||
| 429d1108ce | |||
| 107784980e | |||
| d3fa1c36a5 | |||
| 6eb53ad540 | |||
| c963278c58 | |||
| 1669620379 | |||
| c3221f12cc | |||
| 3aefdb0e87 | |||
| 21381fc533 | |||
| 73f7416864 | |||
| dd7113cc63 | |||
| f52d403d55 | |||
| cf3da1bd25 | |||
| 78c02a9163 | |||
| 1b249b87be | |||
| 2797b9f7fb | |||
| f7d4b418d5 | |||
| e21595fcc7 | |||
| 71350a176c | |||
| 9395ebd479 | |||
| abdd5c1060 | |||
| 340faa5fde | |||
| 72e8a40ccd | |||
| b243b8dcbf | |||
| 992c2b8234 | |||
| b896157d61 | |||
| 25d868c654 | |||
| b71cf6653f | |||
| 0127df97b5 | |||
| 4994566124 | |||
| bdba38c790 | |||
| 2673b4b8a4 | |||
| 85cedf52ef | |||
| f81000c127 | |||
| 8559f0167d | |||
| c193f774c2 | |||
| d9fe6e959b | |||
| 7ea369a068 | |||
| a00ab37b22 | |||
| c9f6a2c978 | |||
| 2166ed8e98 | |||
| f4e01d2b19 | |||
| 0151ed4508 | |||
| 1051d8f5ca | |||
| 7b5f77aba6 | |||
| fc7bb858e4 | |||
| 2d75199d83 | |||
| e485aec355 | |||
| f0d9f9652c | |||
| 7f5c1b8f02 | |||
| eadc78549f | |||
| 0dcbffe784 | |||
| 2f04126ea8 | |||
| 77f33a70ba | |||
| b06c4a1618 | |||
| 8f165e1d3c | |||
| 21550f6586 | |||
| dfd671d60e | |||
| 5954a7d8cc | |||
| 449c60153c | |||
| 01576f9447 | |||
| 2059c5f657 | |||
| 26c141e96e | |||
| b247da54f1 | |||
| 1c5f5acadf | |||
| 0c2dd7b057 | |||
| 16c4802476 | |||
| 139a1324e9 | |||
| 7d228c5198 | |||
| b388ac721e | |||
| 07d7b7c802 | |||
| 589ae67d95 | |||
| ee20586d28 | |||
| 57c4559a47 | |||
| d049d425e6 | |||
| aed5414cb6 | |||
| d6368ab4fa | |||
| eba1340542 | |||
| 0165ceb3cc | |||
| 81773f5635 | |||
| c6a709a954 | |||
| 26fb8eeb99 | |||
| 9177b7f30f | |||
| ab5ea63ae7 | |||
| e38ea3807c | |||
| ab75ff5b39 | |||
| 439b218309 | |||
| 666afef0e5 | |||
| 7b7108928e | |||
| 3671d046b9 | |||
| 4aac448b06 | |||
| 7b7428a31e | |||
| bc0980496c | |||
| bbde1bdfa0 | |||
| 6831fb796c | |||
| f63d7bcacc | |||
| 02bd7b74e1 | |||
| 806fdf0fbe | |||
| 6c99039a9b | |||
| de25ec1f29 | |||
| 25e86ddb7d | |||
| 0b9610e845 | |||
| 2eba6d71e3 | |||
| 85647b252d | |||
| 837061879f | |||
| a510bdc469 | |||
| da97024419 | |||
| 7c43ca4fb1 | |||
| cd5fe8a467 | |||
| 0ab8d9e193 | |||
| 6c547e5f0f | |||
| 4c6c953992 | |||
| 9d7668207e | |||
| b9b0cd775b | |||
| 4f95dacee1 | |||
| 85f0ff2572 | |||
| b733b43e00 | |||
| 1caca11647 | |||
| bdeff18675 | |||
| 48d3881d17 | |||
| 4dec1d172b | |||
| b30aadcf0b | |||
| 3f82b9b889 | |||
| 8c091b35c5 | |||
| a12ee6f30b | |||
| 55f4824735 | |||
| 05b559805b | |||
| e481c456e0 | |||
| adbf3590c5 | |||
| 2850b8d921 | |||
| 7c1b25e49b | |||
| e030d0854c | |||
| 449725a214 | |||
| ecfb22c8d8 | |||
| 39e052acbb | |||
| daed69a4b1 | |||
| da00799c56 | |||
| ace0d5ade4 | |||
| 06fce9a0b3 | |||
| e5baf5a926 | |||
| 2b1a7dacc9 | |||
| f05a84ad42 | |||
| 6497570967 | |||
| bef07194c0 | |||
| 1171da6fcd | |||
| 9a0e2e338c | |||
| c7f799675d | |||
| 4d4b10309d | |||
| 6a1c0ca718 | |||
| fbc48a0d23 | |||
| 04196e20a8 | |||
| 59d2f57d64 | |||
| 402e3b8934 | |||
| 94d8ea5ee9 | |||
| a879278713 | |||
| 8d92efa9fc | |||
| f7a3b6a537 | |||
| a7c532e3f9 | |||
| 337de79cf4 | |||
| f316c1b131 | |||
| d17cb6a910 | |||
| 1a5aa89be8 | |||
| ace55c6160 | |||
| d7b11e4a10 | |||
| 1f565c3d76 | |||
| d584cdf501 | |||
| ef632db9f3 | |||
| 5a9fe3ac62 | |||
| 5327f52c3c | |||
| 87ab7d76af | |||
| 315a6358b4 | |||
| 9951888af3 | |||
| 96357407e0 | |||
| ee2622ee7c | |||
| 7136ef756d | |||
| 0a20e65f92 | |||
| a045630c02 | |||
| a98e0a8cc4 | |||
| 6b8f5224b8 | |||
| 860fe50048 | |||
| 57dec507f0 | |||
| 29293ba51d | |||
| cd06703fa2 | |||
| 00bf6f1ebf | |||
| f29db92052 | |||
| 3f75fd575c | |||
| c81aed412a | |||
| bedde394fc | |||
| e12e724a42 | |||
| 12c3e04023 | |||
| f535335fe0 | |||
| 15acaec687 | |||
| f708b75d41 | |||
| 1a926c69a1 | |||
| 7a8397d60f | |||
| bbc2aca10f | |||
| a4c23463fb | |||
| 31c353543c | |||
| 3a1bbac3a7 | |||
| 39d02eeff4 | |||
| d78817c426 | |||
| 0674c31dbc | |||
| e0bc949219 | |||
| ea8770ad54 | |||
| 0427ec8ad6 | |||
| d9c626e686 | |||
| 505d3942b0 | |||
| 38e8771785 | |||
| 496686629a | |||
| 8484fe3147 | |||
| 0b94d3d3f8 | |||
| 37adadcc50 | |||
| cf4ac3b6b2 | |||
| 3f834cfe3e | |||
| 884d8af759 | |||
| 4a184b57c8 | |||
| d5c239404b | |||
| e5386b82c6 | |||
| e3adca1431 | |||
| f7ff5d1055 | |||
| 4cc8fe7561 | |||
| 91d8d0e502 | |||
| e726b9796d | |||
| 817cac7954 | |||
| 0bccec9fb5 | |||
| d3fe5f617a | |||
| 02549869b1 | |||
| 0404acfcc2 | |||
| 0035181c59 | |||
| 667aa81bee | |||
| a13def0fbe | |||
| 8bf7e79ee3 | |||
| d397c13d9c | |||
| 557f4d382b | |||
| ff64996542 | |||
| 88140938cb | |||
| 4ecad80725 | |||
| 10d197d77f | |||
| a631c21e5a | |||
| 8e7ae6cf69 | |||
| 37864337eb | |||
| e575c0639f | |||
| 1346fd02c9 | |||
| ef5e326215 | |||
| fae963a6d2 | |||
| 2fb25f1fe8 | |||
| a1010e5405 | |||
| e67e446950 | |||
| 8bd523fe36 | |||
| f25b39b7ce | |||
| 26800d5db8 | |||
| f3a0eb4b23 | |||
| e8eb7ab5ed | |||
| fe2d1c7cc9 | |||
| 7a4df9e2fc | |||
| 358859d2f3 | |||
| bc62feb583 | |||
| 2c6dd03229 | |||
| 1952bd0aa3 | |||
| cdfb64ce59 | |||
| d89ee675fc | |||
| 2cafb8d221 | |||
| 12fd193450 | |||
| 89a00e6385 | |||
| 6fe8e4d348 | |||
| 26d1c8c4be | |||
| 5738f8724a | |||
| baf2a05708 | |||
| 5e453555e3 | |||
| 7e0ff815b9 | |||
| eadd5644b0 | |||
| bd4bda95d0 | |||
| 2e75c21e4b |
20
.gitignore
vendored
@ -1,12 +1,30 @@
|
||||
# build products...
|
||||
*.py[co]
|
||||
build
|
||||
# Emacs backup files...
|
||||
*~
|
||||
.\#*
|
||||
# (s)rpm building stuff
|
||||
# RPM stuff...
|
||||
MANIFEST
|
||||
dist
|
||||
rpm-build
|
||||
# Eclipse/PyDev stuff...
|
||||
.project
|
||||
.pydevproject
|
||||
# PyCharm stuff...
|
||||
.idea
|
||||
# Mac OS X stuff...
|
||||
.DS_Store
|
||||
# manpage build stuff...
|
||||
docs/man/man3/*
|
||||
# Sublime stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
# docsite stuff...
|
||||
docsite/rst/modules
|
||||
docsite/*.html
|
||||
docsite/_static/*.gif
|
||||
docsite/_static/*.png
|
||||
docsite/_static/websupport.js
|
||||
# deb building stuff...
|
||||
debian/
|
||||
|
||||
99
CHANGELOG.md
@ -1,7 +1,104 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
0.7 "Panama" -- release pending -- I can barely see the roadmap from the heat coming off of it.
|
||||
0.8 "Cathedral" -- Oct 19, 2012
|
||||
|
||||
Highlighted Core Changes:
|
||||
|
||||
* fireball mode -- ansible can bootstrap a ephemeral 0mq (zeromq) daemon that runs as a given user and expires after X period of time. It is very fast.
|
||||
* playbooks with errors now return 2 on failure. 1 indicates a more fatal syntax error. Similar for /usr/bin/ansible
|
||||
* server side action code (template, etc) are now fully pluggable
|
||||
* ability to write lookup plugins, like the code powering "with_fileglob" (see below)
|
||||
|
||||
Other Core Changes:
|
||||
|
||||
* ansible config file can also go in '.ansible.cfg' in cwd in addition to ~/.ansible.cfg and /etc/ansible/ansible.cfg
|
||||
* fix for inventory hosts at API level when hosts spec is a list and not a colon delimited string
|
||||
* ansible-pull example now sets up logrotate for the ansible-pull cron job log
|
||||
* negative host matching (!hosts) fixed for external inventory script usage
|
||||
* internals: os.executable check replaced with utils function so it plays nice on AIX
|
||||
* Debian packaging now includes ansible-pull manpage
|
||||
* magic variable 'ansible_ssh_host' can override the hostname (great for usage with tunnels)
|
||||
* date command usage in build scripts fixed for OS X
|
||||
* don't use SSH agent with paramiko if a password is specified
|
||||
* make output be cleaner on multi-line command/shell errors
|
||||
* /usr/bin/ansible now prints things when tasks are skipped, like when creates= is used with -m command and /usr/bin/ansible
|
||||
* when trying to async a module that is not a 'normal' asyncable module, ansible will now let you know
|
||||
* ability to access inventory variables via 'hostvars' for hosts not yet included in any play, using on demand lookups
|
||||
* merged ansible-plugins, ansible-resources, and ansible-docs into the main project
|
||||
* you can set ANSIBLE_NOCOWS=1 if you want to disable cowsay if it is installed. Though no one should ever want to do this! Cows are great!
|
||||
* you can set ANSIBLE_FORCECOLOR=1 to force color mode even when running without a TTY
|
||||
* fatal errors are now properly colored red.
|
||||
* skipped messages are now cyan, to differentiate them from unchanged messages.
|
||||
* extensive documentation upgrades
|
||||
* delegate_action to localhost (aka local_action) will always use the local connection type
|
||||
|
||||
Highlighted playbook changes:
|
||||
|
||||
* is_set is available for use inside of an only_if expression: is_set('ansible_eth0'). We intend to further upgrade this with a 'when'
|
||||
keyword providing better options to 'only_if' in the next release. Also is_unset('ansible_eth0')
|
||||
* playbooks can import playbooks in other directories and then be able to import tasks relative to them
|
||||
* FILE($path) now allows access of contents of file in a path, very good for use with SSH keys
|
||||
* similarly PIPE($command) will run a local command and return the results of executing this command
|
||||
* if all hosts in a play fail, stop the playbook, rather than letting the console log spool on by
|
||||
* only_if using register variables that are booleans now works in a boolean way like you'd expect
|
||||
* task includes now work with with_items (such as: include: path/to/wordpress.yml user=$item)
|
||||
* when using a $list variable with $var or ${var} syntax it will automatically join with commas
|
||||
* setup is not run more than once when we know it is has already been run in a play that included another play, etc
|
||||
* can set/override sudo and sudo_user on individual tasks in a play, defaults to what is set in the play if not present
|
||||
* ability to use with_fileglob to iterate over local file patterns
|
||||
* templates now use Jinja2's 'trim_blocks=True' to avoid stray newlines, small changes to templates may
|
||||
be required in rare cases.
|
||||
|
||||
Other playbook changes:
|
||||
|
||||
* to_yaml and from_yaml are available as Jinja2 filters
|
||||
* $group and $group_names are now accessible in with_items
|
||||
* where 'stdout' is provided a new 'stdout_lines' variable (type == list) is now generated and usable with with_items
|
||||
* when local_action is used the transport is automatically overridden to the local type
|
||||
* output on failed playbook commands is now nicely split for stderr/stdout and syntax errors
|
||||
* if local_action is not used and delegate_to was 127.0.0.1 or localhost, use local connection regardless
|
||||
* when running a playbook, and the statement has changed, prints 'changed:' now versus 'ok:' so it is obvious without colored mode
|
||||
* variables now usable within vars_prompt (just not host/group vars)
|
||||
* setup facts are now retained across plays (dictionary just gets updated as needed)
|
||||
* --sudo-user now works with --extra-vars
|
||||
* fix for multi_line strings with only_if
|
||||
|
||||
New Modules:
|
||||
|
||||
* ini_file module for manipulating INI files
|
||||
* 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 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:
|
||||
|
||||
* removes= exists on command just like creates=
|
||||
* postgresql modules now take an optional port= parameter
|
||||
* /proc/cmdline info is now available in Linux facts
|
||||
* public host key detection for OS X
|
||||
* lineinfile module now uses 'search' not exact 'match' in regexes, making it much more intuitive and not needing regex syntax most of the time
|
||||
* added force=yes|no (default no) option for file module, which allows transition between files to directories and so on
|
||||
* additional facts for SunOS virtualization
|
||||
* copy module is now atomic when used across volumes
|
||||
* url_get module now returns 'dest' with the location of the file saved
|
||||
* fix for yum module when using local RPMs vs downloading
|
||||
* cleaner error messages with copy if destination directory does not exist
|
||||
* setup module now still works if PATH is not set
|
||||
* service module status now correct for services with 'subsys locked' status
|
||||
* misc fixes/upgrades to the wait_for module
|
||||
* git module now expands any "~" in provided destination paths
|
||||
* ignore stop error code failure for service module with state=restarted, always try to start
|
||||
* inline documentation for modules allows documentation source to built without pull requests to the ansible-docs project, among other things
|
||||
* variable '$ansible_managed' is now great to include at the top of your templates and includes useful information and a warning that it will be replaced
|
||||
* "~" now expanded in command module when using creates/removes
|
||||
* mysql module can do dumps and imports
|
||||
* selinux policy is only required if setting to not disabled
|
||||
* various fixes for yum module when working with packages not in any present repo
|
||||
|
||||
0.7 "Panama" -- Sept 6 2012
|
||||
|
||||
Module changes:
|
||||
|
||||
|
||||
59
CONTRIBUTING.md
Normal file
@ -0,0 +1,59 @@
|
||||
Contributing to Ansible
|
||||
=======================
|
||||
|
||||
It is required that you read the following information to learn how to contribute to this project.
|
||||
|
||||
Branch Info
|
||||
===========
|
||||
|
||||
Here's how to understand the branches.
|
||||
|
||||
* The devel branch corresponds to the latest ongoing release
|
||||
* Various release-X.Y branches exist for previous releases
|
||||
* All feature work happens on the development branch.
|
||||
* Major bug fixes will be made to the last release branch only
|
||||
* See CHANGELOG.md for release notes to track each release.
|
||||
|
||||
Patch Instructions
|
||||
==================
|
||||
|
||||
Contributions to the core and modules are greatly welcome.
|
||||
|
||||
* Required Process:
|
||||
* Submit github pull requests to the "ansible/devel" branch for features
|
||||
* Fixes for bugs may also be submitted to "ansible/release-X.Y" for the last release
|
||||
* Make sure "make tests" passes before submitting any requests.
|
||||
* Bonus points:
|
||||
* Joining the mailing list
|
||||
* Fixing bugs instead of sending bug reports.
|
||||
* Using squash merges
|
||||
* Updating the "rst/*" files in the "docsite/" directory and "docs/" manpage content
|
||||
* Adding more unit tests
|
||||
* Avoid:
|
||||
* Sending patches to the mailing list directly.
|
||||
* Sending feature pull requests to the 'release' branch instead of the devel branch
|
||||
* Sending pull requests to mpdehaan's personal ansible fork.
|
||||
* Sending pull requests about more than one feature in the same pull request.
|
||||
* Whitespace restructuring
|
||||
* Large scale refactoring without a discussion on the list
|
||||
|
||||
Coding Standards
|
||||
================
|
||||
|
||||
We're not too strict on style considerations, but we require:
|
||||
|
||||
* python 2.6 compliant code, unless in ansible modules, then python *2.4* compliant code (no 'with', etc)
|
||||
* 4-space indents, no tabs except in Makefiles
|
||||
* under_scores for method names and variables, not camelCase
|
||||
* GPLv3 license headers on all files, with copyright on new files with your name on it
|
||||
* no single-line if statements, deeply nested list comprehensions, or clever use of metaclasses -- keep it simple
|
||||
* comments where appropriate
|
||||
|
||||
Contributors License Agreement
|
||||
==============================
|
||||
|
||||
By contributing you agree that these contributions are your own (or approved by your employer) and you grant a full, complete, irrevocable
|
||||
copyright license to all users and developers of the project, present and future, persusant to the license of the project.
|
||||
|
||||
|
||||
|
||||
38
Makefile
@ -9,7 +9,7 @@
|
||||
# make deb ------------------ produce a DEB
|
||||
# make docs ----------------- rebuild the manpages (results are checked in)
|
||||
# make tests ---------------- run the tests
|
||||
# make pyflakes, make pep8 -- source code checks
|
||||
# make pyflakes, make pep8 -- source code checks
|
||||
|
||||
########################################################
|
||||
# variable section
|
||||
@ -37,7 +37,11 @@ endif
|
||||
ifeq ($(OS), FreeBSD)
|
||||
DATE := $(shell date -j -f "%Y-%m-%d %H:%M:%s" "$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
else
|
||||
DATE := $(shell date --date="$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
ifeq ($(OS), Darwin)
|
||||
DATE := $(shell date -j -f "%Y-%m-%d %H:%M:%S" "$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
else
|
||||
DATE := $(shell date --utc --date="$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
endif
|
||||
endif
|
||||
|
||||
# RPM build parameters
|
||||
@ -54,11 +58,11 @@ RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
|
||||
|
||||
all: clean python
|
||||
|
||||
tests:
|
||||
PYTHONPATH=./lib nosetests -v
|
||||
tests:
|
||||
PYTHONPATH=./lib nosetests -d -v
|
||||
|
||||
# To force a rebuild of the docs run 'touch VERSION && make docs'
|
||||
docs: $(MANPAGES)
|
||||
docs: $(MANPAGES) modulepages
|
||||
|
||||
# Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more
|
||||
# recently than %.1.asciidoc.
|
||||
@ -92,9 +96,10 @@ clean:
|
||||
@echo "Cleaning up editor backup files"
|
||||
find . -type f \( -name "*~" -or -name "#*" \) -delete
|
||||
find . -type f \( -name "*.swp" \) -delete
|
||||
@echo "Cleaning up asciidoc to man transformations and results"
|
||||
@echo "Cleaning up manpage stuff"
|
||||
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
|
||||
@echo "Cleaning up output from test runs"
|
||||
rm -rf test/test_data
|
||||
@echo "Cleaning up RPM building stuff"
|
||||
@ -102,6 +107,8 @@ clean:
|
||||
@echo "Cleaning up Debian building stuff"
|
||||
rm -rf debian
|
||||
rm -rf deb-build
|
||||
rm -rf docs/json
|
||||
rm -rf docs/js
|
||||
|
||||
python:
|
||||
python setup.py build
|
||||
@ -109,7 +116,7 @@ python:
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
sdist: clean
|
||||
sdist: clean docs
|
||||
python setup.py sdist -t MANIFEST.in
|
||||
|
||||
rpmcommon: sdist
|
||||
@ -156,3 +163,20 @@ deb: debian
|
||||
|
||||
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
|
||||
|
||||
modulepages:
|
||||
hacking/module_formatter.py -A $(VERSION) -t man -o docs/man/man3/ --module-dir=library --template-dir=hacking/templates
|
||||
|
||||
modulejson:
|
||||
mkdir -p docs/json
|
||||
hacking/module_formatter.py -A $(VERSION) -t json -o docs/json --module-dir=library --template-dir=hacking/templates
|
||||
|
||||
modulejs:
|
||||
mkdir -p docs/js
|
||||
make modulejson
|
||||
hacking/module_formatter.py -A $(VERSION) -t js -o docs/js --module-dir=docs/json --template-dir=hacking/templates
|
||||
|
||||
# because this requires Sphinx it is not run as part of every build, those building the RPM and so on can ignore this
|
||||
|
||||
webdocs:
|
||||
(cd docsite; make docs)
|
||||
|
||||
|
||||
29
README.md
@ -4,7 +4,7 @@ Ansible
|
||||
Ansible is a radically simple configuration-management, deployment, task-execution, and
|
||||
multinode orchestration framework.
|
||||
|
||||
Read the documentation at http://ansible.github.com
|
||||
Read the documentation and more at http://ansible.cc
|
||||
|
||||
Design Principles
|
||||
=================
|
||||
@ -28,32 +28,9 @@ Branch Info
|
||||
===========
|
||||
|
||||
* Releases are named after Van Halen songs.
|
||||
* The devel branch corresponds to release 0.7, "Panama".
|
||||
* The devel branch corresponds to the release actively under development.
|
||||
* Various release-X.Y branches exist for previous releases
|
||||
* All feature work happens on the development branch.
|
||||
* Major bug fixes will be made to the last release branch only
|
||||
* See CHANGELOG.md for release notes to track each release.
|
||||
|
||||
Patch Instructions
|
||||
==================
|
||||
|
||||
Contributions to the core and modules are greatly welcome.
|
||||
|
||||
* Required Process:
|
||||
* Submit github pull requests to the "ansible/devel" branch for features
|
||||
* Fixes for bugs may also be submitted to "ansible/release-X.Y" for the last release
|
||||
* Make sure "make tests" passes before submitting any requests.
|
||||
* Bonus points:
|
||||
* Joining the mailing list
|
||||
* Fixing bugs instead of sending bug reports.
|
||||
* Using squash merges
|
||||
* Updating the "rst/*" files in the docs project and "docs/" manpage content
|
||||
* Adding more unit tests
|
||||
* Avoid:
|
||||
* Sending patches to the mailing list directly.
|
||||
* Sending feature pull requests to the 'release' branch instead of the devel branch
|
||||
* Sending pull requests to mpdehaan's personal ansible fork.
|
||||
|
||||
* We'd love to have your contributions, read "CONTRIBUTING.md" for process notes.
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
Ansible Releases at a Glance
|
||||
============================
|
||||
|
||||
0.7 "Panama" ---------- X-XX-2012
|
||||
0.8 "Cathedral" ------- pending
|
||||
0.7 "Panama" ---------- 9-06-2012
|
||||
0.6 "Cabo" ------------ 8-06-2012
|
||||
0.5 "Amsterdam" ------- 7-04-2012
|
||||
0.4 "Unchained" ------- 5-23-2012
|
||||
|
||||
@ -127,6 +127,11 @@ if __name__ == '__main__':
|
||||
(options, args) = cli.parse()
|
||||
try:
|
||||
(runner, results) = cli.run(options, args)
|
||||
for result in results['contacted'].values():
|
||||
if 'failed' in result or result.get('rc', 0) != 0:
|
||||
sys.exit(2)
|
||||
if results['dark']:
|
||||
sys.exit(2)
|
||||
except errors.AnsibleError, e:
|
||||
# Generic handler for ansible specific errors
|
||||
print "ERROR: %s" % str(e)
|
||||
|
||||
@ -59,6 +59,8 @@ def main(args):
|
||||
help="only run plays and tasks tagged with these values")
|
||||
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
|
||||
help="dump out a list of hosts, each play will run against, does not run playbook!")
|
||||
parser.add_option('--syntax-check', dest='syntax', action='store_true',
|
||||
help="do a playbook syntax check on the playbook, do not execute the playbook")
|
||||
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
@ -121,10 +123,17 @@ def main(args):
|
||||
print ' %s' % host
|
||||
print '\n'
|
||||
return 0
|
||||
|
||||
if options.syntax:
|
||||
# if we've not exited by now then we are fine.
|
||||
print 'Playbook Syntax is fine'
|
||||
return 0
|
||||
|
||||
|
||||
try:
|
||||
|
||||
pb.run()
|
||||
|
||||
hosts = sorted(pb.stats.processed.keys())
|
||||
print callbacks.banner("PLAY RECAP")
|
||||
playbook_cb.on_stats(pb.stats)
|
||||
@ -138,6 +147,10 @@ def main(args):
|
||||
colorize('failed', t['failures'], 'red'))
|
||||
|
||||
print "\n"
|
||||
for h in hosts:
|
||||
stats = pb.stats.summarize(h)
|
||||
if stats['failures'] != 0 or stats['unreachable'] != 0:
|
||||
return 2
|
||||
|
||||
except errors.AnsibleError, e:
|
||||
print >>sys.stderr, "ERROR: %s" % e
|
||||
|
||||
109
bin/ansible-pull
@ -1,15 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ansible-pull is a script that runs ansible in local mode
|
||||
# after checking out a playbooks directory from git. There is an
|
||||
# example playbook to bootstrap this script in the examples/ dir which
|
||||
# installs ansible and sets it up to run on cron.
|
||||
#
|
||||
# usage:
|
||||
# ansible-pull -d /var/ansible/local -U http://wherever/content.git -C production
|
||||
#
|
||||
# the git repo must contain a playbook named 'local.yml'
|
||||
|
||||
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
@ -24,47 +14,130 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ansible-pull is a script that runs ansible in local mode
|
||||
# after checking out a playbooks directory from git. There is an
|
||||
# example playbook to bootstrap this script in the examples/ dir which
|
||||
# installs ansible and sets it up to run on cron.
|
||||
|
||||
# usage:
|
||||
# ansible-pull -d /var/lib/ansible \
|
||||
# -U http://example.net/content.git [-C production] \
|
||||
# [path/playbook.yml]
|
||||
#
|
||||
# the -d and -U arguments are required; the -C argument is optional.
|
||||
#
|
||||
# ansible-pull accepts an optional argument to specify a playbook
|
||||
# location underneath the workdir and then searches the git repo
|
||||
# for playbooks in the following order, stopping at the first match:
|
||||
#
|
||||
# 1. $workdir/path/playbook.yml, if specified
|
||||
# 2. $workdir/$hostname.yml
|
||||
# 3. $workdir/local.yml
|
||||
#
|
||||
# the git repo must contain at least one of these playbooks.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import datetime
|
||||
import socket
|
||||
from optparse import OptionParser
|
||||
|
||||
DEFAULT_PLAYBOOK = 'local.yml'
|
||||
PLAYBOOK_ERRORS = { 1: 'File does not exist',
|
||||
2: 'File is not readable' }
|
||||
|
||||
def _run(cmd):
|
||||
print >>sys.stderr, "Running: '%s'" % cmd
|
||||
cmd = subprocess.Popen(cmd, shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = cmd.communicate()
|
||||
print out
|
||||
if cmd.returncode != 0:
|
||||
print err
|
||||
print >>sys.stderr, err
|
||||
return cmd.returncode
|
||||
|
||||
def try_playbook(path):
|
||||
if not os.path.exists(path):
|
||||
return 1
|
||||
if not os.access(path, os.R_OK):
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def select_playbook(path, args):
|
||||
playbook = None
|
||||
if len(args) > 0 and args[0] is not None:
|
||||
playbook = "%s/%s" % (path, args[0])
|
||||
rc = try_playbook(playbook)
|
||||
if rc != 0:
|
||||
print >>sys.stderr, "%s: %s" % (playbook, PLAYBOOK_ERRORS[rc])
|
||||
return None
|
||||
return playbook
|
||||
else:
|
||||
hostpb = "%s/%s.yml" % (path, socket.getfqdn())
|
||||
localpb = "%s/%s" % (path, DEFAULT_PLAYBOOK)
|
||||
errors = []
|
||||
for pb in [hostpb, localpb]:
|
||||
rc = try_playbook(pb)
|
||||
if rc == 0:
|
||||
playbook = pb
|
||||
break
|
||||
else:
|
||||
errors.append("%s: %s" % (pb, PLAYBOOK_ERRORS[rc]))
|
||||
if playbook is None:
|
||||
print >>sys.stderr, "\n".join(errors)
|
||||
return playbook
|
||||
|
||||
def main(args):
|
||||
""" Set up and run a local playbook """
|
||||
usage = "%prog [options]"
|
||||
parser = OptionParser()
|
||||
usage = "%prog [options] [playbook.yml]"
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option('--purge', default=False, action='store_true',
|
||||
help='Purge git checkout after playbook run')
|
||||
parser.add_option('-d', '--directory', dest='dest', default=None,
|
||||
help='Directory to checkout git repository')
|
||||
parser.add_option('-U', '--url', dest='url',
|
||||
default=None,
|
||||
help='Directory to clone git repository to')
|
||||
parser.add_option('-U', '--url', dest='url', default=None,
|
||||
help='URL of git repository')
|
||||
parser.add_option('-C', '--checkout', dest='checkout',
|
||||
default="HEAD",
|
||||
help='Branch/Tag/Commit to checkout. Defaults to HEAD.')
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
if not options.dest:
|
||||
parser.error("Missing required directory argument")
|
||||
return 1
|
||||
|
||||
if not options.url:
|
||||
parser.error("URL for git repo not specified, use -h for help")
|
||||
return 1
|
||||
|
||||
now = datetime.datetime.now()
|
||||
print >>sys.stderr, now.strftime("Starting ansible-pull at %F %T")
|
||||
|
||||
git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout)
|
||||
cmd = 'ansible all -c local -m git -a "%s"' % git_opts
|
||||
print "cmd=%s" % cmd
|
||||
rc = _run(cmd)
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
playbook = select_playbook(options.dest, args)
|
||||
|
||||
if playbook is None:
|
||||
print >>sys.stderr, "Could not find a playbook to run."
|
||||
return 1
|
||||
|
||||
cmd = 'ansible-playbook -c local %s' % playbook
|
||||
os.chdir(options.dest)
|
||||
cmd = 'ansible-playbook -c local %s' % DEFAULT_PLAYBOOK
|
||||
rc = _run(cmd)
|
||||
|
||||
if options.purge:
|
||||
os.chdir('/')
|
||||
try:
|
||||
shutil.rmtree(options.dest)
|
||||
except Exception, e:
|
||||
print >>sys.stderr, "Failed to remove %s: %s" % (options.dest, str(e))
|
||||
|
||||
return rc
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
84
contrib/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
Ansible Resources
|
||||
=================
|
||||
|
||||
User contributed playbooks, modules, and articles. This is a small curated list, but growing. Everyone is encouraged to add to this document,
|
||||
just send in a pull request!
|
||||
|
||||
Ansible Modules
|
||||
===============
|
||||
|
||||
Ansible modules are a way of adding new client-side logic to ansible. They can be written in any language.
|
||||
|
||||
* [Official "core" ansible modules](http://ansible.cc/docs/modules.html) - various
|
||||
* [mercurial](https://github.com/bradobro/ansible-module-mercurial) - bradobro
|
||||
* [zypper (bash module example)](https://github.com/jpmens/ansible-zypp) - jp_mens
|
||||
* [homebrew](https://gist.github.com/3170079) - swehack
|
||||
* [network_facts](https://github.com/dagwieers/ansible-network-facts) - dagwieers
|
||||
* [vsphere_boot](https://github.com/dagwieers/ansible-vsphere-boot) - dagwieers
|
||||
|
||||
Python modules using 0.6 and later can use the common "AnsibleModule" class to dramatically reduce
|
||||
the amount of boilerplate code required. Not all modules above yet take advantage of this feature.
|
||||
See the official documentation for more details.
|
||||
|
||||
Selected Playbooks
|
||||
==================
|
||||
|
||||
[Playbooks](http://ansible.cc/docs/playbooks.html) are Ansible's configuration management language. It should
|
||||
be easy to write your own from scratch for most applications, but it's always helpful to look at what others have
|
||||
done for reference.
|
||||
|
||||
* [Hadoop](https://github.com/jkleint/ansible-hadoop) - jkleint
|
||||
* [LAMP](https://github.com/fourkitchens/server-playbooks) - [Four Kitchens](http://fourkitchens.com)
|
||||
* [Ganglia (demo)](https://github.com/mpdehaan/ansible-examples) - mpdehaan
|
||||
* [Nginx](http://www.capsunlock.net/2012/04/ansible-nginx-playbook.html) - cocoy
|
||||
* [OpenStack](http://github.com/lorin/openstack-ansible) - lorin
|
||||
* [Systems Configuration](https://github.com/cegeddin/ansible-contrib) - cegeddin
|
||||
* [Fedora Infrastructure](http://infrastructure.fedoraproject.org/cgit/ansible.git/tree/) - [Fedora](http://fedoraproject.org)
|
||||
|
||||
Callbacks and Plugins
|
||||
=====================
|
||||
|
||||
The Ansible project has a whole repo devoted to extending ansible with new connection types, logging/event callbacks, and
|
||||
inventory data storage. Talk to Cobbler and EC2, tweak the way things are logged, or even add sound effects.
|
||||
|
||||
* [Ansible-Plugins](https://github.com/ansible/ansible/tree/devel/plugins)
|
||||
|
||||
Scripts And Misc
|
||||
================
|
||||
|
||||
Ansible isn't just a program, it's also an API. Here's some examples of some clever integrations with the "Runner" and
|
||||
also Playbook APIs, and integrations with other interesting pieces of software.
|
||||
|
||||
* [Ansible Vagrant plugin](https://github.com/dsander/vagrant-ansible) - dsander
|
||||
* [Ansible+Vagrant Tutorial](https://github.com/mattupstate/vagrant-ansible-tutorial) - mattupstate -
|
||||
* [virt-install](http://fedorapeople.org/cgit/skvidal/public_git/scripts.git/tree/ansible/start-prov-boot.py) - skvidal
|
||||
* [rebooting hosts](http://fedorapeople.org/cgit/skvidal/public_git/scripts.git/tree/ansible/host-reboot) - skvidal
|
||||
* [uptime (API demo)](https://github.com/ansible/ansible/blob/devel/examples/scripts/uptime.py) - mpdehaan
|
||||
|
||||
Blogs & Articles
|
||||
================
|
||||
|
||||
* [HighScalability.com](http://highscalability.com/blog/2012/4/18/ansible-a-simple-model-driven-configuration-management-and-c.html) - mpdehaan
|
||||
* [ColoAndCloud.com interview](http://www.coloandcloud.com/editorial/an-interview-with-ansible-author-michael-dehaan/) - mpdehaan
|
||||
* [dzone](http://server.dzone.com/articles/ansible-cm-deployment-and-ad) - Mitch Pronschinske
|
||||
* [Configuration Management With Ansible](http://jpmens.net/2012/06/06/configuration-management-with-ansible/) - jp_mens
|
||||
* [Shell Scripts As Ansible Modules](http://jpmens.net/2012/07/05/shell-scripts-as-ansible-modules/) - jp_mens
|
||||
* [Ansible Facts](http://jpmens.net/2012/07/15/ansible-it-s-a-fact/) - jp_mens
|
||||
* [Infrastructure as Data](http://www.capsunlock.net/2012/04/ansible-infrastructure-as-data-not-infrastructure-as-code.html) - cocoy
|
||||
* [Ansible Pull Mode](http://www.capsunlock.net/2012/05/using-ansible-pull-and-user-data-to-setup-ec2-or-openstack-servers.html) - cocoy
|
||||
* [Exploring Configuration Management With Ansible](http://palominodb.com/blog/2012/08/01/exploring-configuration-management-ansible) - Palamino DB
|
||||
* [You Should Consider Using SSH Based Configuration Management](http://www.lshift.net/blog/2012/07/30/you-should-consider-using-ssh-based-configuration-management) - LShift Ltd
|
||||
* [Deploying Flask/uWSGI, Nginx, and Supervisorctl](http://mattupstate.github.com/python/devops/2012/08/07/flask-wsgi-application-deployment-with-ubuntu-ansible-nginx-supervisor-and-uwsgi.html) - mattupstate
|
||||
* [Infracoders Presentation](http://www.danielhall.me/2012/10/ansible-talk-infra-coders/) - Daniel Hall
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
|
||||
Modules and playbooks here may not be using the latest in Ansible features. When in doubt to the features of
|
||||
a particular version of Ansbile, always consult [ansible.cc](http://ansible.cc) and in particular
|
||||
see [Best Practices](http://ansible.cc/docs/bestpractices.html) for some tips and tricks that may be useful.
|
||||
|
||||
Ansible is (C) 2012, [Michael DeHaan](http://twitter.com/laserllama) and others and is available under the GPLv3 license. Content here is as specified
|
||||
by individual contributors.
|
||||
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
.\" Title: ansible-playbook
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 09/06/2012
|
||||
.\" Date: 10/12/2012
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 0.7
|
||||
.\" Source: Ansible 0.8
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "09/06/2012" "Ansible 0\&.7" "System administration commands"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "10/12/2012" "Ansible 0\&.8" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -120,6 +120,15 @@ The following environment variables may specified\&.
|
||||
ANSIBLE_HOSTS \(em Override the default ansible hosts file
|
||||
.sp
|
||||
ANSIBLE_LIBRARY \(em Override the default ansible module library path
|
||||
.SH "FILES"
|
||||
.sp
|
||||
/etc/ansible/hosts \(em Default inventory file
|
||||
.sp
|
||||
/usr/share/ansible/ \(em Default module library
|
||||
.sp
|
||||
/etc/ansible/ansible\&.conf \(em Config file, used if present
|
||||
.sp
|
||||
~/\&.ansible\&.config \(em User config file, overrides the default config if present
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
|
||||
@ -107,6 +107,16 @@ ANSIBLE_HOSTS -- Override the default ansible hosts file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
FILES
|
||||
-----
|
||||
|
||||
/etc/ansible/hosts -- Default inventory file
|
||||
|
||||
/usr/share/ansible/ -- Default module library
|
||||
|
||||
/etc/ansible/ansible.conf -- Config file, used if present
|
||||
|
||||
~/.ansible.config -- User config file, overrides the default config if present
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 08/14/2012
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 09/06/2012
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 0.7
|
||||
.\" Source: Ansible 0.8
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "08/14/2012" "Ansible 0\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.TH "ANSIBLE" "1" "09/06/2012" "Ansible 0\&.8" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -34,7 +25,7 @@ ansible-pull \- set up a remote copy of ansible on each managed node
|
||||
ansible \-d DEST \-U URL [ \-C CHECKOUT ]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
|
||||
.sp
|
||||
Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via git\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&.
|
||||
.sp
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
.\" Title: ansible
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 09/06/2012
|
||||
.\" Date: 10/12/2012
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 0.7
|
||||
.\" Source: Ansible 0.8
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "09/06/2012" "Ansible 0\&.7" "System administration commands"
|
||||
.TH "ANSIBLE" "1" "10/12/2012" "Ansible 0\&.8" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -141,11 +141,6 @@ Sudo to
|
||||
instead of root\&. Implies \-\-sudo\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-private\-key=\fR\fIPRIVATE_KEY_FILE\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
|
||||
.RS 4
|
||||
Connection type to use\&. Possible options are
|
||||
@ -171,6 +166,10 @@ Ranges of hosts are also supported\&. For more information and additional option
|
||||
/etc/ansible/hosts \(em Default inventory file
|
||||
.sp
|
||||
/usr/share/ansible/ \(em Default module library
|
||||
.sp
|
||||
/etc/ansible/ansible\&.conf \(em Config file, used if present
|
||||
.sp
|
||||
~/\&.ansible\&.config \(em User config file, overrides the default config if present
|
||||
.SH "ENVIRONMENT"
|
||||
.sp
|
||||
The following environment variables may specified\&.
|
||||
|
||||
@ -108,9 +108,6 @@ Use this remote 'USERNAME' instead of root.
|
||||
|
||||
Sudo to 'SUDO_USERNAME' instead of root. Implies --sudo.
|
||||
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
Use this file to authenticate the connection.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
@ -138,6 +135,9 @@ FILES
|
||||
|
||||
/usr/share/ansible/ -- Default module library
|
||||
|
||||
/etc/ansible/ansible.conf -- Config file, used if present
|
||||
|
||||
~/.ansible.config -- User config file, overrides the default config if present
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
0
docs/man/man3/.gitdir
Normal file
13
docsite/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Old compiled python stuff
|
||||
*.py[co]
|
||||
# package building stuff
|
||||
build
|
||||
# Emacs backup files...
|
||||
*~
|
||||
.\#*
|
||||
.doctrees
|
||||
# Generated docs stuff
|
||||
ansible*.xml
|
||||
.buildinfo
|
||||
objects.inv
|
||||
.doctrees
|
||||
0
docsite/.nojekyll
Normal file
1
docsite/DOCUMENTATION.yaml
Symbolic link
@ -0,0 +1 @@
|
||||
../examples/DOCUMENTATION.yaml
|
||||
31
docsite/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/make
|
||||
SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
|
||||
FORMATTER=../hacking/module_formatter.py
|
||||
|
||||
all: clean docs
|
||||
|
||||
docs: clean modules
|
||||
./build-site.py
|
||||
|
||||
viewdocs: clean
|
||||
./build-site.py view
|
||||
|
||||
htmldocs:
|
||||
./build-site.py rst
|
||||
|
||||
clean:
|
||||
-rm -f .buildinfo
|
||||
-rm -f *.inv
|
||||
-rm -rf *.doctrees
|
||||
@echo "Cleaning up byte compiled python stuff"
|
||||
find . -regex ".*\.py[co]$$" -delete
|
||||
@echo "Cleaning up editor backup files"
|
||||
find . -type f \( -name "*~" -or -name "#*" \) -delete
|
||||
find . -type f \( -name "*.swp" \) -delete
|
||||
|
||||
.PHONEY: docs clean
|
||||
|
||||
modules: $(FORMATTER) ../hacking/templates/rst.j2
|
||||
$(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../library -o rst/modules/ --includes-file=rst/modules/_list.rst
|
||||
|
||||
|
||||
23
docsite/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
Homepage and documentation source for the Ansible
|
||||
=================================================
|
||||
|
||||
This project hosts the source behind [ansible.cc/docs](http://ansible.cc/docs)
|
||||
|
||||
Contributions to the documentation are welcome. To make changes, submit a pull request
|
||||
that changes the restructured text files in the "rst/" directory only, and Michael can
|
||||
do a docs build and push the static files.
|
||||
|
||||
If you do not want to learn RST format (ReStructured Text), you can also file issues
|
||||
on the documentation project.
|
||||
|
||||
Note that module documentation can actually be generated from a DOCUMENTATION docstring
|
||||
in the modules directory, so corrections to modules written as much need to be made
|
||||
in the main project in the module source, rather than in the documentation for the module.
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
Michael DeHaan -- michael.dehaan@gmail.com
|
||||
|
||||
[http://michaeldehaan.net](http://michaeldehaan.net/)
|
||||
|
||||
540
docsite/_static/basic.css
Normal file
@ -0,0 +1,540 @@
|
||||
/*
|
||||
* basic.css
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable dl, table.indextable dd {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, .highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.refcount {
|
||||
color: #060;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: dotted 1px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
tt.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
55
docsite/_static/bootstrap-dropdown.js
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
/* ============================================================
|
||||
* bootstrap-dropdown.js v1.4.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdown
|
||||
* ============================================================
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.dropdown = function ( selector ) {
|
||||
return this.each(function () {
|
||||
$(this).delegate(selector || d, 'click', function (e) {
|
||||
var li = $(this).parent('li')
|
||||
, isActive = li.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
!isActive && li.toggleClass('open')
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
* =================================== */
|
||||
|
||||
var d = 'a.menu, .dropdown-toggle'
|
||||
|
||||
function clearMenus() {
|
||||
$(d).parent('li').removeClass('open')
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('html').bind("click", clearMenus)
|
||||
$('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
|
||||
})
|
||||
|
||||
}( window.jQuery || window.ender );
|
||||
107
docsite/_static/bootstrap-scrollspy.js
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/* =============================================================
|
||||
* bootstrap-scrollspy.js v1.4.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
|
||||
* =============================================================
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================== */
|
||||
|
||||
|
||||
!function ( $ ) {
|
||||
|
||||
"use strict"
|
||||
|
||||
var $window = $(window)
|
||||
|
||||
function ScrollSpy( topbar, selector ) {
|
||||
var processScroll = $.proxy(this.processScroll, this)
|
||||
this.$topbar = $(topbar)
|
||||
this.selector = selector || 'li > a'
|
||||
this.refresh()
|
||||
this.$topbar.delegate(this.selector, 'click', processScroll)
|
||||
$window.scroll(processScroll)
|
||||
this.processScroll()
|
||||
}
|
||||
|
||||
ScrollSpy.prototype = {
|
||||
|
||||
refresh: function () {
|
||||
this.targets = this.$topbar.find(this.selector).map(function () {
|
||||
var href = $(this).attr('href')
|
||||
return /^#\w/.test(href) && $(href).length ? href : null
|
||||
})
|
||||
|
||||
this.offsets = $.map(this.targets, function (id) {
|
||||
return $(id).offset().top
|
||||
})
|
||||
}
|
||||
|
||||
, processScroll: function () {
|
||||
var scrollTop = $window.scrollTop() + 10
|
||||
, offsets = this.offsets
|
||||
, targets = this.targets
|
||||
, activeTarget = this.activeTarget
|
||||
, i
|
||||
|
||||
for (i = offsets.length; i--;) {
|
||||
activeTarget != targets[i]
|
||||
&& scrollTop >= offsets[i]
|
||||
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
|
||||
&& this.activateButton( targets[i] )
|
||||
}
|
||||
}
|
||||
|
||||
, activateButton: function (target) {
|
||||
this.activeTarget = target
|
||||
|
||||
this.$topbar
|
||||
.find(this.selector).parent('.active')
|
||||
.removeClass('active')
|
||||
|
||||
this.$topbar
|
||||
.find(this.selector + '[href="' + target + '"]')
|
||||
.parent('li')
|
||||
.addClass('active')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* SCROLLSPY PLUGIN DEFINITION
|
||||
* =========================== */
|
||||
|
||||
$.fn.scrollSpy = function( options ) {
|
||||
var scrollspy = this.data('scrollspy')
|
||||
|
||||
if (!scrollspy) {
|
||||
return this.each(function () {
|
||||
$(this).data('scrollspy', new ScrollSpy( this, options ))
|
||||
})
|
||||
}
|
||||
|
||||
if ( options === true ) {
|
||||
return scrollspy
|
||||
}
|
||||
|
||||
if ( typeof options == 'string' ) {
|
||||
scrollspy[options]()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('body').scrollSpy('[data-scrollspy] li > a')
|
||||
})
|
||||
|
||||
}( window.jQuery || window.ender );
|
||||
15
docsite/_static/bootstrap-sphinx.css
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* bootstrap-sphinx.css
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- Twitter Bootstrap theme.
|
||||
*/
|
||||
|
||||
body {
|
||||
padding-top: 42px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
2467
docsite/_static/bootstrap.css
vendored
Normal file
256
docsite/_static/default.css
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* default.css_t
|
||||
* ~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- default theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
background-color: #11303d;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
background-color: #1c4e63;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 0 20px 30px 20px;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
padding: 9px 0 9px 0;
|
||||
text-align: center;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #ffffff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
background-color: #133f52;
|
||||
line-height: 30px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
color: #ffffff;
|
||||
font-size: 1.4em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
color: #ffffff;
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
margin: 5px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.topless {
|
||||
margin: 5px 10px 10px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #98dbcc;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -- hyperlink styles ------------------------------------------------------ */
|
||||
|
||||
a {
|
||||
color: #355f7c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #355f7c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
background-color: #f2f2f2;
|
||||
font-weight: normal;
|
||||
color: #20435c;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin: 20px -20px 10px -20px;
|
||||
padding: 3px 0 3px 10px;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; font-size: 200%; }
|
||||
div.body h2 { font-size: 160%; }
|
||||
div.body h3 { font-size: 140%; }
|
||||
div.body h4 { font-size: 120%; }
|
||||
div.body h5 { font-size: 110%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f;
|
||||
font-size: 0.8em;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #c60f0f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
text-align: justify;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.admonition p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.admonition pre {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.admonition ul, div.admonition ol {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 5px;
|
||||
background-color: #eeffcc;
|
||||
color: #333333;
|
||||
line-height: 120%;
|
||||
border: 1px solid #ac9;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
padding: 0 1px 0 1px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #ede;
|
||||
}
|
||||
|
||||
.warning tt {
|
||||
background: #efc2c2;
|
||||
}
|
||||
|
||||
.note tt {
|
||||
background: #d6d6d6;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
247
docsite/_static/doctools.js
Normal file
@ -0,0 +1,247 @@
|
||||
/*
|
||||
* doctools.js
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* select a different prefix for underscore
|
||||
*/
|
||||
$u = _.noConflict();
|
||||
|
||||
/**
|
||||
* make the code below compatible with browsers without
|
||||
* an installed firebug like debugger
|
||||
if (!window.console || !console.firebug) {
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
|
||||
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
|
||||
"profile", "profileEnd"];
|
||||
window.console = {};
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
window.console[names[i]] = function() {};
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* small helper function to urldecode strings
|
||||
*/
|
||||
jQuery.urldecode = function(x) {
|
||||
return decodeURIComponent(x).replace(/\+/g, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* small helper function to urlencode strings
|
||||
*/
|
||||
jQuery.urlencode = encodeURIComponent;
|
||||
|
||||
/**
|
||||
* This function returns the parsed url parameters of the
|
||||
* current request. Multiple values per key are supported,
|
||||
* it will always return arrays of strings for the value parts.
|
||||
*/
|
||||
jQuery.getQueryParameters = function(s) {
|
||||
if (typeof s == 'undefined')
|
||||
s = document.location.search;
|
||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var tmp = parts[i].split('=', 2);
|
||||
var key = jQuery.urldecode(tmp[0]);
|
||||
var value = jQuery.urldecode(tmp[1]);
|
||||
if (key in result)
|
||||
result[key].push(value);
|
||||
else
|
||||
result[key] = [value];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* small function to check if an array contains
|
||||
* a given item.
|
||||
*/
|
||||
jQuery.contains = function(arr, item) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i] == item)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* highlight a given string on a jquery object by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
jQuery.fn.highlightText = function(text, className) {
|
||||
function highlight(node) {
|
||||
if (node.nodeType == 3) {
|
||||
var val = node.nodeValue;
|
||||
var pos = val.toLowerCase().indexOf(text);
|
||||
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
|
||||
var span = document.createElement("span");
|
||||
span.className = className;
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
||||
document.createTextNode(val.substr(pos + text.length)),
|
||||
node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
}
|
||||
}
|
||||
else if (!jQuery(node).is("button, select, textarea")) {
|
||||
jQuery.each(node.childNodes, function() {
|
||||
highlight(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.each(function() {
|
||||
highlight(this);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
var Documentation = {
|
||||
|
||||
init : function() {
|
||||
this.fixFirefoxAnchorBug();
|
||||
this.highlightSearchWords();
|
||||
this.initIndexTable();
|
||||
},
|
||||
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
TRANSLATIONS : {},
|
||||
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
|
||||
LOCALE : 'unknown',
|
||||
|
||||
// gettext and ngettext don't access this so that the functions
|
||||
// can safely bound to a different name (_ = Documentation.gettext)
|
||||
gettext : function(string) {
|
||||
var translated = Documentation.TRANSLATIONS[string];
|
||||
if (typeof translated == 'undefined')
|
||||
return string;
|
||||
return (typeof translated == 'string') ? translated : translated[0];
|
||||
},
|
||||
|
||||
ngettext : function(singular, plural, n) {
|
||||
var translated = Documentation.TRANSLATIONS[singular];
|
||||
if (typeof translated == 'undefined')
|
||||
return (n == 1) ? singular : plural;
|
||||
return translated[Documentation.PLURALEXPR(n)];
|
||||
},
|
||||
|
||||
addTranslations : function(catalog) {
|
||||
for (var key in catalog.messages)
|
||||
this.TRANSLATIONS[key] = catalog.messages[key];
|
||||
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
|
||||
this.LOCALE = catalog.locale;
|
||||
},
|
||||
|
||||
/**
|
||||
* add context elements like header anchor links
|
||||
*/
|
||||
addContextElements : function() {
|
||||
$('div[id] > :header:first').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this headline')).
|
||||
appendTo(this);
|
||||
});
|
||||
$('dt[id]').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this definition')).
|
||||
appendTo(this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* workaround a firefox stupidity
|
||||
*/
|
||||
fixFirefoxAnchorBug : function() {
|
||||
if (document.location.hash && $.browser.mozilla)
|
||||
window.setTimeout(function() {
|
||||
document.location.href += '';
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* highlight the search words provided in the url in the text
|
||||
*/
|
||||
highlightSearchWords : function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
|
||||
if (terms.length) {
|
||||
var body = $('div.body');
|
||||
window.setTimeout(function() {
|
||||
$.each(terms, function() {
|
||||
body.highlightText(this.toLowerCase(), 'highlighted');
|
||||
});
|
||||
}, 10);
|
||||
$('<p class="highlight-link"><a href="javascript:Documentation.' +
|
||||
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
|
||||
.appendTo($('#searchbox'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* init the domain index toggle buttons
|
||||
*/
|
||||
initIndexTable : function() {
|
||||
var togglers = $('img.toggler').click(function() {
|
||||
var src = $(this).attr('src');
|
||||
var idnum = $(this).attr('id').substr(7);
|
||||
$('tr.cg-' + idnum).toggle();
|
||||
if (src.substr(-9) == 'minus.png')
|
||||
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
|
||||
else
|
||||
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
|
||||
}).css('display', '');
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
|
||||
togglers.click();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to hide the search marks again
|
||||
*/
|
||||
hideSearchWords : function() {
|
||||
$('#searchbox .highlight-link').fadeOut(300);
|
||||
$('span.highlighted').removeClass('highlighted');
|
||||
},
|
||||
|
||||
/**
|
||||
* make the url absolute
|
||||
*/
|
||||
makeURL : function(relativeURL) {
|
||||
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the current relative url
|
||||
*/
|
||||
getCurrentURL : function() {
|
||||
var path = document.location.pathname;
|
||||
var parts = path.split(/\//);
|
||||
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
|
||||
if (this == '..')
|
||||
parts.pop();
|
||||
});
|
||||
var url = parts.join('/');
|
||||
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// quick alias for translations
|
||||
_ = Documentation.gettext;
|
||||
|
||||
$(document).ready(function() {
|
||||
Documentation.init();
|
||||
});
|
||||
BIN
docsite/_static/favicon.ico
Normal file
|
After Width: | Height: | Size: 318 B |
BIN
docsite/_static/file.png
Normal file
|
After Width: | Height: | Size: 392 B |
154
docsite/_static/jquery.js
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*!
|
||||
* jQuery JavaScript Library v1.4.2
|
||||
* http://jquery.com/
|
||||
*
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
* http://sizzlejs.com/
|
||||
* Copyright 2010, The Dojo Foundation
|
||||
* Released under the MIT, BSD, and GPL Licenses.
|
||||
*
|
||||
* Date: Sat Feb 13 22:33:48 2010 -0500
|
||||
*/
|
||||
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
|
||||
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
|
||||
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
|
||||
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
|
||||
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
|
||||
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
|
||||
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
|
||||
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
|
||||
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
|
||||
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
|
||||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
|
||||
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
|
||||
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
|
||||
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
|
||||
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
|
||||
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
|
||||
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
|
||||
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
|
||||
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
|
||||
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
|
||||
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
|
||||
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
|
||||
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
|
||||
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
|
||||
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
|
||||
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
|
||||
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
|
||||
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
|
||||
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
|
||||
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
|
||||
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
|
||||
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
|
||||
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
|
||||
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
|
||||
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
|
||||
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
|
||||
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
|
||||
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
|
||||
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
|
||||
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
|
||||
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
|
||||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
|
||||
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
|
||||
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
|
||||
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
|
||||
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
|
||||
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
|
||||
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
|
||||
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
|
||||
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
|
||||
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
|
||||
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
|
||||
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
|
||||
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
|
||||
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
|
||||
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
|
||||
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
|
||||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
|
||||
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
|
||||
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
|
||||
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
|
||||
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
|
||||
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
|
||||
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
|
||||
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
|
||||
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
|
||||
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
|
||||
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
|
||||
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
|
||||
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
|
||||
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
|
||||
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
|
||||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
|
||||
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
|
||||
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
|
||||
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
|
||||
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
|
||||
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
|
||||
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
|
||||
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
|
||||
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
|
||||
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
|
||||
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
|
||||
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
|
||||
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
|
||||
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
|
||||
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
|
||||
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
|
||||
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
|
||||
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
|
||||
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
|
||||
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
|
||||
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
|
||||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
|
||||
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
|
||||
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
|
||||
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
|
||||
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
|
||||
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
|
||||
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
|
||||
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
|
||||
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
|
||||
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
|
||||
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
|
||||
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
|
||||
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
|
||||
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
|
||||
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
|
||||
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
|
||||
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
|
||||
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
|
||||
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
|
||||
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
|
||||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
|
||||
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
|
||||
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
|
||||
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
|
||||
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
|
||||
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
|
||||
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
|
||||
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
|
||||
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
|
||||
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
|
||||
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
|
||||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
|
||||
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
|
||||
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
|
||||
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
|
||||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
|
||||
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
|
||||
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
|
||||
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
|
||||
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
|
||||
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
|
||||
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
|
||||
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
|
||||
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
|
||||
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
|
||||
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
|
||||
BIN
docsite/_static/minus.png
Normal file
|
After Width: | Height: | Size: 199 B |
BIN
docsite/_static/plus.png
Normal file
|
After Width: | Height: | Size: 199 B |
62
docsite/_static/pygments.css
Normal file
@ -0,0 +1,62 @@
|
||||
.highlight .hll { background-color: #ffffcc }
|
||||
.highlight { background: #eeffcc; }
|
||||
.highlight .c { color: #408090; font-style: italic } /* Comment */
|
||||
.highlight .err { border: 1px solid #FF0000 } /* Error */
|
||||
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
|
||||
.highlight .o { color: #666666 } /* Operator */
|
||||
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #007020 } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
|
||||
.highlight .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #FF0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.highlight .go { color: #303030 } /* Generic.Output */
|
||||
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
|
||||
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #902000 } /* Keyword.Type */
|
||||
.highlight .m { color: #208050 } /* Literal.Number */
|
||||
.highlight .s { color: #4070a0 } /* Literal.String */
|
||||
.highlight .na { color: #4070a0 } /* Name.Attribute */
|
||||
.highlight .nb { color: #007020 } /* Name.Builtin */
|
||||
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #60add5 } /* Name.Constant */
|
||||
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
||||
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
||||
.highlight .ne { color: #007020 } /* Name.Exception */
|
||||
.highlight .nf { color: #06287e } /* Name.Function */
|
||||
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
|
||||
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
||||
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #bb60d5 } /* Name.Variable */
|
||||
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #208050 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
|
||||
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #235388 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
|
||||
560
docsite/_static/searchtools.js
Normal file
@ -0,0 +1,560 @@
|
||||
/*
|
||||
* searchtools.js_t
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilties for the full-text search.
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* helper function to return a node containing the
|
||||
* search summary for a given text. keywords is a list
|
||||
* of stemmed words, hlwords is the list of normal, unstemmed
|
||||
* words. the first one is used to find the occurance, the
|
||||
* latter for highlighting it.
|
||||
*/
|
||||
|
||||
jQuery.makeSearchSummary = function(text, keywords, hlwords) {
|
||||
var textLower = text.toLowerCase();
|
||||
var start = 0;
|
||||
$.each(keywords, function() {
|
||||
var i = textLower.indexOf(this.toLowerCase());
|
||||
if (i > -1)
|
||||
start = i;
|
||||
});
|
||||
start = Math.max(start - 120, 0);
|
||||
var excerpt = ((start > 0) ? '...' : '') +
|
||||
$.trim(text.substr(start, 240)) +
|
||||
((start + 240 - text.length) ? '...' : '');
|
||||
var rv = $('<div class="context"></div>').text(excerpt);
|
||||
$.each(hlwords, function() {
|
||||
rv = rv.highlightText(this, 'highlighted');
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Porter Stemmer
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
|
||||
var step2list = {
|
||||
ational: 'ate',
|
||||
tional: 'tion',
|
||||
enci: 'ence',
|
||||
anci: 'ance',
|
||||
izer: 'ize',
|
||||
bli: 'ble',
|
||||
alli: 'al',
|
||||
entli: 'ent',
|
||||
eli: 'e',
|
||||
ousli: 'ous',
|
||||
ization: 'ize',
|
||||
ation: 'ate',
|
||||
ator: 'ate',
|
||||
alism: 'al',
|
||||
iveness: 'ive',
|
||||
fulness: 'ful',
|
||||
ousness: 'ous',
|
||||
aliti: 'al',
|
||||
iviti: 'ive',
|
||||
biliti: 'ble',
|
||||
logi: 'log'
|
||||
};
|
||||
|
||||
var step3list = {
|
||||
icate: 'ic',
|
||||
ative: '',
|
||||
alize: 'al',
|
||||
iciti: 'ic',
|
||||
ical: 'ic',
|
||||
ful: '',
|
||||
ness: ''
|
||||
};
|
||||
|
||||
var c = "[^aeiou]"; // consonant
|
||||
var v = "[aeiouy]"; // vowel
|
||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
||||
var V = v + "[aeiou]*"; // vowel sequence
|
||||
|
||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
||||
|
||||
this.stemWord = function (w) {
|
||||
var stem;
|
||||
var suffix;
|
||||
var firstch;
|
||||
var origword = w;
|
||||
|
||||
if (w.length < 3)
|
||||
return w;
|
||||
|
||||
var re;
|
||||
var re2;
|
||||
var re3;
|
||||
var re4;
|
||||
|
||||
firstch = w.substr(0,1);
|
||||
if (firstch == "y")
|
||||
w = firstch.toUpperCase() + w.substr(1);
|
||||
|
||||
// Step 1a
|
||||
re = /^(.+?)(ss|i)es$/;
|
||||
re2 = /^(.+?)([^s])s$/;
|
||||
|
||||
if (re.test(w))
|
||||
w = w.replace(re,"$1$2");
|
||||
else if (re2.test(w))
|
||||
w = w.replace(re2,"$1$2");
|
||||
|
||||
// Step 1b
|
||||
re = /^(.+?)eed$/;
|
||||
re2 = /^(.+?)(ed|ing)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(fp[1])) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1];
|
||||
re2 = new RegExp(s_v);
|
||||
if (re2.test(stem)) {
|
||||
w = stem;
|
||||
re2 = /(at|bl|iz)$/;
|
||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re2.test(w))
|
||||
w = w + "e";
|
||||
else if (re3.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
else if (re4.test(w))
|
||||
w = w + "e";
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1c
|
||||
re = /^(.+?)y$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(s_v);
|
||||
if (re.test(stem))
|
||||
w = stem + "i";
|
||||
}
|
||||
|
||||
// Step 2
|
||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step2list[suffix];
|
||||
}
|
||||
|
||||
// Step 3
|
||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step3list[suffix];
|
||||
}
|
||||
|
||||
// Step 4
|
||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
||||
re2 = /^(.+?)(s|t)(ion)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
if (re.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1] + fp[2];
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re2.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
|
||||
// Step 5
|
||||
re = /^(.+?)e$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
re2 = new RegExp(meq1);
|
||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
||||
w = stem;
|
||||
}
|
||||
re = /ll$/;
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re.test(w) && re2.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
|
||||
// and turn initial Y back to y
|
||||
if (firstch == "y")
|
||||
w = firstch.toLowerCase() + w.substr(1);
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search Module
|
||||
*/
|
||||
var Search = {
|
||||
|
||||
_index : null,
|
||||
_queued_query : null,
|
||||
_pulse_status : -1,
|
||||
|
||||
init : function() {
|
||||
var params = $.getQueryParameters();
|
||||
if (params.q) {
|
||||
var query = params.q[0];
|
||||
$('input[name="q"]')[0].value = query;
|
||||
this.performSearch(query);
|
||||
}
|
||||
},
|
||||
|
||||
loadIndex : function(url) {
|
||||
$.ajax({type: "GET", url: url, data: null, success: null,
|
||||
dataType: "script", cache: true});
|
||||
},
|
||||
|
||||
setIndex : function(index) {
|
||||
var q;
|
||||
this._index = index;
|
||||
if ((q = this._queued_query) !== null) {
|
||||
this._queued_query = null;
|
||||
Search.query(q);
|
||||
}
|
||||
},
|
||||
|
||||
hasIndex : function() {
|
||||
return this._index !== null;
|
||||
},
|
||||
|
||||
deferQuery : function(query) {
|
||||
this._queued_query = query;
|
||||
},
|
||||
|
||||
stopPulse : function() {
|
||||
this._pulse_status = 0;
|
||||
},
|
||||
|
||||
startPulse : function() {
|
||||
if (this._pulse_status >= 0)
|
||||
return;
|
||||
function pulse() {
|
||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
||||
var dotString = '';
|
||||
for (var i = 0; i < Search._pulse_status; i++)
|
||||
dotString += '.';
|
||||
Search.dots.text(dotString);
|
||||
if (Search._pulse_status > -1)
|
||||
window.setTimeout(pulse, 500);
|
||||
};
|
||||
pulse();
|
||||
},
|
||||
|
||||
/**
|
||||
* perform a search for something
|
||||
*/
|
||||
performSearch : function(query) {
|
||||
// create the required interface elements
|
||||
this.out = $('#search-results');
|
||||
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
|
||||
this.dots = $('<span></span>').appendTo(this.title);
|
||||
this.status = $('<p style="display: none"></p>').appendTo(this.out);
|
||||
this.output = $('<ul class="search"/>').appendTo(this.out);
|
||||
|
||||
$('#search-progress').text(_('Preparing search...'));
|
||||
this.startPulse();
|
||||
|
||||
// index already loaded, the browser was quick!
|
||||
if (this.hasIndex())
|
||||
this.query(query);
|
||||
else
|
||||
this.deferQuery(query);
|
||||
},
|
||||
|
||||
query : function(query) {
|
||||
var stopwords = ["and","then","into","it","as","are","in","if","for","no","there","their","was","is","be","to","that","but","they","not","such","with","by","a","on","these","of","will","this","near","the","or","at"];
|
||||
|
||||
// Stem the searchterms and add them to the correct list
|
||||
var stemmer = new Stemmer();
|
||||
var searchterms = [];
|
||||
var excluded = [];
|
||||
var hlterms = [];
|
||||
var tmp = query.split(/\s+/);
|
||||
var objectterms = [];
|
||||
for (var i = 0; i < tmp.length; i++) {
|
||||
if (tmp[i] != "") {
|
||||
objectterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
|
||||
if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) ||
|
||||
tmp[i] == "") {
|
||||
// skip this "word"
|
||||
continue;
|
||||
}
|
||||
// stem the word
|
||||
var word = stemmer.stemWord(tmp[i]).toLowerCase();
|
||||
// select the correct list
|
||||
if (word[0] == '-') {
|
||||
var toAppend = excluded;
|
||||
word = word.substr(1);
|
||||
}
|
||||
else {
|
||||
var toAppend = searchterms;
|
||||
hlterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
// only add if not already in the list
|
||||
if (!$.contains(toAppend, word))
|
||||
toAppend.push(word);
|
||||
};
|
||||
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
|
||||
|
||||
// console.debug('SEARCH: searching for:');
|
||||
// console.info('required: ', searchterms);
|
||||
// console.info('excluded: ', excluded);
|
||||
|
||||
// prepare search
|
||||
var filenames = this._index.filenames;
|
||||
var titles = this._index.titles;
|
||||
var terms = this._index.terms;
|
||||
var fileMap = {};
|
||||
var files = null;
|
||||
// different result priorities
|
||||
var importantResults = [];
|
||||
var objectResults = [];
|
||||
var regularResults = [];
|
||||
var unimportantResults = [];
|
||||
$('#search-progress').empty();
|
||||
|
||||
// lookup as object
|
||||
for (var i = 0; i < objectterms.length; i++) {
|
||||
var others = [].concat(objectterms.slice(0,i),
|
||||
objectterms.slice(i+1, objectterms.length))
|
||||
var results = this.performObjectSearch(objectterms[i], others);
|
||||
// Assume first word is most likely to be the object,
|
||||
// other words more likely to be in description.
|
||||
// Therefore put matches for earlier words first.
|
||||
// (Results are eventually used in reverse order).
|
||||
objectResults = results[0].concat(objectResults);
|
||||
importantResults = results[1].concat(importantResults);
|
||||
unimportantResults = results[2].concat(unimportantResults);
|
||||
}
|
||||
|
||||
// perform the search on the required terms
|
||||
for (var i = 0; i < searchterms.length; i++) {
|
||||
var word = searchterms[i];
|
||||
// no match but word was a required one
|
||||
if ((files = terms[word]) == null)
|
||||
break;
|
||||
if (files.length == undefined) {
|
||||
files = [files];
|
||||
}
|
||||
// create the mapping
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
var file = files[j];
|
||||
if (file in fileMap)
|
||||
fileMap[file].push(word);
|
||||
else
|
||||
fileMap[file] = [word];
|
||||
}
|
||||
}
|
||||
|
||||
// now check if the files don't contain excluded terms
|
||||
for (var file in fileMap) {
|
||||
var valid = true;
|
||||
|
||||
// check if all requirements are matched
|
||||
if (fileMap[file].length != searchterms.length)
|
||||
continue;
|
||||
|
||||
// ensure that none of the excluded terms is in the
|
||||
// search result.
|
||||
for (var i = 0; i < excluded.length; i++) {
|
||||
if (terms[excluded[i]] == file ||
|
||||
$.contains(terms[excluded[i]] || [], file)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have still a valid result we can add it
|
||||
// to the result list
|
||||
if (valid)
|
||||
regularResults.push([filenames[file], titles[file], '', null]);
|
||||
}
|
||||
|
||||
// delete unused variables in order to not waste
|
||||
// memory until list is retrieved completely
|
||||
delete filenames, titles, terms;
|
||||
|
||||
// now sort the regular results descending by title
|
||||
regularResults.sort(function(a, b) {
|
||||
var left = a[1].toLowerCase();
|
||||
var right = b[1].toLowerCase();
|
||||
return (left > right) ? -1 : ((left < right) ? 1 : 0);
|
||||
});
|
||||
|
||||
// combine all results
|
||||
var results = unimportantResults.concat(regularResults)
|
||||
.concat(objectResults).concat(importantResults);
|
||||
|
||||
// print the results
|
||||
var resultCount = results.length;
|
||||
function displayNextItem() {
|
||||
// results left, load the summary and display it
|
||||
if (results.length) {
|
||||
var item = results.pop();
|
||||
var listItem = $('<li style="display:none"></li>');
|
||||
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') {
|
||||
// dirhtml builder
|
||||
var dirname = item[0] + '/';
|
||||
if (dirname.match(/\/index\/$/)) {
|
||||
dirname = dirname.substring(0, dirname.length-6);
|
||||
} else if (dirname == 'index/') {
|
||||
dirname = '';
|
||||
}
|
||||
listItem.append($('<a/>').attr('href',
|
||||
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
} else {
|
||||
// normal html builders
|
||||
listItem.append($('<a/>').attr('href',
|
||||
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
}
|
||||
if (item[3]) {
|
||||
listItem.append($('<span> (' + item[3] + ')</span>'));
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
|
||||
$.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' +
|
||||
item[0] + '.txt', function(data) {
|
||||
if (data != '') {
|
||||
listItem.append($.makeSearchSummary(data, searchterms, hlterms));
|
||||
Search.output.append(listItem);
|
||||
}
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}, "text");
|
||||
} else {
|
||||
// no source available, just display title
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}
|
||||
}
|
||||
// search finished, update title and status message
|
||||
else {
|
||||
Search.stopPulse();
|
||||
Search.title.text(_('Search Results'));
|
||||
if (!resultCount)
|
||||
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
|
||||
else
|
||||
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
|
||||
Search.status.fadeIn(500);
|
||||
}
|
||||
}
|
||||
displayNextItem();
|
||||
},
|
||||
|
||||
performObjectSearch : function(object, otherterms) {
|
||||
var filenames = this._index.filenames;
|
||||
var objects = this._index.objects;
|
||||
var objnames = this._index.objnames;
|
||||
var titles = this._index.titles;
|
||||
|
||||
var importantResults = [];
|
||||
var objectResults = [];
|
||||
var unimportantResults = [];
|
||||
|
||||
for (var prefix in objects) {
|
||||
for (var name in objects[prefix]) {
|
||||
var fullname = (prefix ? prefix + '.' : '') + name;
|
||||
if (fullname.toLowerCase().indexOf(object) > -1) {
|
||||
var match = objects[prefix][name];
|
||||
var objname = objnames[match[1]][2];
|
||||
var title = titles[match[0]];
|
||||
// If more than one term searched for, we require other words to be
|
||||
// found in the name/title/description
|
||||
if (otherterms.length > 0) {
|
||||
var haystack = (prefix + ' ' + name + ' ' +
|
||||
objname + ' ' + title).toLowerCase();
|
||||
var allfound = true;
|
||||
for (var i = 0; i < otherterms.length; i++) {
|
||||
if (haystack.indexOf(otherterms[i]) == -1) {
|
||||
allfound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allfound) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var descr = objname + _(', in ') + title;
|
||||
anchor = match[3];
|
||||
if (anchor == '')
|
||||
anchor = fullname;
|
||||
else if (anchor == '-')
|
||||
anchor = objnames[match[1]][1] + '-' + fullname;
|
||||
result = [filenames[match[0]], fullname, '#'+anchor, descr];
|
||||
switch (match[2]) {
|
||||
case 1: objectResults.push(result); break;
|
||||
case 0: importantResults.push(result); break;
|
||||
case 2: unimportantResults.push(result); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort results descending
|
||||
objectResults.sort(function(a, b) {
|
||||
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
|
||||
});
|
||||
|
||||
importantResults.sort(function(a, b) {
|
||||
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
|
||||
});
|
||||
|
||||
unimportantResults.sort(function(a, b) {
|
||||
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
|
||||
});
|
||||
|
||||
return [importantResults, objectResults, unimportantResults]
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
Search.init();
|
||||
});
|
||||
148
docsite/_static/sidebar.js
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* sidebar.js
|
||||
* ~~~~~~~~~~
|
||||
*
|
||||
* This script makes the Sphinx sidebar collapsible.
|
||||
*
|
||||
* .sphinxsidebar contains .sphinxsidebarwrapper. This script adds
|
||||
* in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
|
||||
* used to collapse and expand the sidebar.
|
||||
*
|
||||
* When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
|
||||
* and the width of the sidebar and the margin-left of the document
|
||||
* are decreased. When the sidebar is expanded the opposite happens.
|
||||
* This script saves a per-browser/per-session cookie used to
|
||||
* remember the position of the sidebar among the pages.
|
||||
* Once the browser is closed the cookie is deleted and the position
|
||||
* reset to the default (expanded).
|
||||
*
|
||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
$(function() {
|
||||
// global elements used by the functions.
|
||||
// the 'sidebarbutton' element is defined as global after its
|
||||
// creation, in the add_sidebar_button function
|
||||
var bodywrapper = $('.bodywrapper');
|
||||
var sidebar = $('.sphinxsidebar');
|
||||
var sidebarwrapper = $('.sphinxsidebarwrapper');
|
||||
|
||||
// original margin-left of the bodywrapper and width of the sidebar
|
||||
// with the sidebar expanded
|
||||
var bw_margin_expanded = bodywrapper.css('margin-left');
|
||||
var ssb_width_expanded = sidebar.width();
|
||||
|
||||
// margin-left of the bodywrapper and width of the sidebar
|
||||
// with the sidebar collapsed
|
||||
var bw_margin_collapsed = '.8em';
|
||||
var ssb_width_collapsed = '.8em';
|
||||
|
||||
// colors used by the current theme
|
||||
var dark_color = $('.related').css('background-color');
|
||||
var light_color = $('.document').css('background-color');
|
||||
|
||||
function sidebar_is_collapsed() {
|
||||
return sidebarwrapper.is(':not(:visible)');
|
||||
}
|
||||
|
||||
function toggle_sidebar() {
|
||||
if (sidebar_is_collapsed())
|
||||
expand_sidebar();
|
||||
else
|
||||
collapse_sidebar();
|
||||
}
|
||||
|
||||
function collapse_sidebar() {
|
||||
sidebarwrapper.hide();
|
||||
sidebar.css('width', ssb_width_collapsed);
|
||||
bodywrapper.css('margin-left', bw_margin_collapsed);
|
||||
sidebarbutton.css({
|
||||
'margin-left': '0',
|
||||
'height': bodywrapper.height()
|
||||
});
|
||||
sidebarbutton.find('span').text('»');
|
||||
sidebarbutton.attr('title', _('Expand sidebar'));
|
||||
document.cookie = 'sidebar=collapsed';
|
||||
}
|
||||
|
||||
function expand_sidebar() {
|
||||
bodywrapper.css('margin-left', bw_margin_expanded);
|
||||
sidebar.css('width', ssb_width_expanded);
|
||||
sidebarwrapper.show();
|
||||
sidebarbutton.css({
|
||||
'margin-left': ssb_width_expanded-12,
|
||||
'height': bodywrapper.height()
|
||||
});
|
||||
sidebarbutton.find('span').text('«');
|
||||
sidebarbutton.attr('title', _('Collapse sidebar'));
|
||||
document.cookie = 'sidebar=expanded';
|
||||
}
|
||||
|
||||
function add_sidebar_button() {
|
||||
sidebarwrapper.css({
|
||||
'float': 'left',
|
||||
'margin-right': '0',
|
||||
'width': ssb_width_expanded - 28
|
||||
});
|
||||
// create the button
|
||||
sidebar.append(
|
||||
'<div id="sidebarbutton"><span>«</span></div>'
|
||||
);
|
||||
var sidebarbutton = $('#sidebarbutton');
|
||||
light_color = sidebarbutton.css('background-color');
|
||||
// find the height of the viewport to center the '<<' in the page
|
||||
var viewport_height;
|
||||
if (window.innerHeight)
|
||||
viewport_height = window.innerHeight;
|
||||
else
|
||||
viewport_height = $(window).height();
|
||||
sidebarbutton.find('span').css({
|
||||
'display': 'block',
|
||||
'margin-top': (viewport_height - sidebar.position().top - 20) / 2
|
||||
});
|
||||
|
||||
sidebarbutton.click(toggle_sidebar);
|
||||
sidebarbutton.attr('title', _('Collapse sidebar'));
|
||||
sidebarbutton.css({
|
||||
'color': '#FFFFFF',
|
||||
'border-left': '1px solid ' + dark_color,
|
||||
'font-size': '1.2em',
|
||||
'cursor': 'pointer',
|
||||
'height': bodywrapper.height(),
|
||||
'padding-top': '1px',
|
||||
'margin-left': ssb_width_expanded - 12
|
||||
});
|
||||
|
||||
sidebarbutton.hover(
|
||||
function () {
|
||||
$(this).css('background-color', dark_color);
|
||||
},
|
||||
function () {
|
||||
$(this).css('background-color', light_color);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function set_position_from_cookie() {
|
||||
if (!document.cookie)
|
||||
return;
|
||||
var items = document.cookie.split(';');
|
||||
for(var k=0; k<items.length; k++) {
|
||||
var key_val = items[k].split('=');
|
||||
var key = key_val[0];
|
||||
if (key == 'sidebar') {
|
||||
var value = key_val[1];
|
||||
if ((value == 'collapsed') && (!sidebar_is_collapsed()))
|
||||
collapse_sidebar();
|
||||
else if ((value == 'expanded') && (sidebar_is_collapsed()))
|
||||
expand_sidebar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_sidebar_button();
|
||||
var sidebarbutton = $('#sidebarbutton');
|
||||
set_position_from_cookie();
|
||||
});
|
||||
23
docsite/_static/underscore.js
Normal file
@ -0,0 +1,23 @@
|
||||
// Underscore.js 0.5.5
|
||||
// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
// Underscore is freely distributable under the terms of the MIT license.
|
||||
// Portions of Underscore are inspired by or borrowed from Prototype.js,
|
||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||
// For all details and documentation:
|
||||
// http://documentcloud.github.com/underscore/
|
||||
(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e<f;e++)c.call(d,
|
||||
a[e],e,a);else{var g=b.keys(a);f=g.length;for(e=0;e<f;e++)c.call(d,a[g[e]],g[e],a)}}catch(h){if(h!=m)throw h;}return a};b.map=function(a,c,d){if(a&&b.isFunction(a.map))return a.map(c,d);var e=[];b.each(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(a&&b.isFunction(a.reduce))return a.reduce(b.bind(d,e),c);b.each(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(a&&b.isFunction(a.reduceRight))return a.reduceRight(b.bind(d,e),c);
|
||||
var f=b.clone(b.toArray(a)).reverse();b.each(f,function(g,h){c=d.call(e,c,g,h,a)});return c};b.detect=function(a,c,d){var e;b.each(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.select=function(a,c,d){if(a&&b.isFunction(a.filter))return a.filter(c,d);var e=[];b.each(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];b.each(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.all=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.every))return a.every(c,
|
||||
d);var e=true;b.each(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.any=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.some))return a.some(c,d);var e=false;b.each(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(b.isArray(a))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck=
|
||||
function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,
|
||||
function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){e=e.criteria;f=f.criteria;return e<f?-1:e>f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return k.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?k.call(a,
|
||||
0,c):a[0]};b.rest=function(a,c,d){return k.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d,
|
||||
e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(a.indexOf)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a.lastIndexOf)return a.lastIndexOf(c);for(var d=
|
||||
a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;1;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});
|
||||
return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);
|
||||
var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;
|
||||
if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==
|
||||
0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&
|
||||
a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g,
|
||||
" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments);
|
||||
o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})();
|
||||
5
docsite/_themes/bootstrap/globaltoc.html
Normal file
@ -0,0 +1,5 @@
|
||||
<li class="dropdown" data-dropdown="dropdown">
|
||||
<a href="{{ pathto(master_doc) }}"
|
||||
class="dropdown-toggle">Chapter</a>
|
||||
<span class="globaltoc">{{ toctree(maxdepth=1) }}</span>
|
||||
</li>
|
||||
138
docsite/_themes/bootstrap/layout.html
Normal file
@ -0,0 +1,138 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
{% set script_files = script_files + ['_static/bootstrap-dropdown.js', '_static/bootstrap-scrollspy.js'] %}
|
||||
{% set css_files = ['_static/bootstrap.css', '_static/bootstrap-sphinx.css'] + css_files %}
|
||||
|
||||
{# Sidebar: Rework into our Boostrap nav section. #}
|
||||
{% macro navBar() %}
|
||||
<div class="topbar" data-scrollspy="scrollspy" >
|
||||
<div class="topbar-inner">
|
||||
<div class="container">
|
||||
<!-- <a class="brand" href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a> -->
|
||||
<ul class="nav">
|
||||
{% block sidebartoc %}
|
||||
<li><a href="/">Home</A>
|
||||
{% include "globaltoc.html" %}
|
||||
{% include "localtoc.html" %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="nav secondary-nav">
|
||||
{% block sidebarsearch %}
|
||||
{% include "searchbox.html" %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
<p></p>
|
||||
|
||||
{%- block extrahead %}
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
/**
|
||||
* Patch TOC list.
|
||||
*
|
||||
* Will mutate the underlying span to have a correct ul for nav.
|
||||
*
|
||||
* @param $span: Span containing nested UL's to mutate.
|
||||
* @param minLevel: Starting level for nested lists. (1: global, 2: local).
|
||||
*/
|
||||
var patchToc = function ($span, minLevel) {
|
||||
var $tocList = $("<ul/>").attr('class', "dropdown-menu"),
|
||||
findA;
|
||||
|
||||
// Find all a "internal" tags, traversing recursively.
|
||||
findA = function ($elem, level) {
|
||||
var level = level || 0,
|
||||
$items = $elem.find("> li > a.internal, > ul, > li > ul");
|
||||
|
||||
// Iterate everything in order.
|
||||
$items.each(function (index, item) {
|
||||
var $item = $(item),
|
||||
tag = item.tagName.toLowerCase(),
|
||||
pad = 10 + ((level - minLevel) * 10);
|
||||
|
||||
if (tag === 'a' && level >= minLevel) {
|
||||
// Add to existing padding.
|
||||
$item.css('padding-left', pad + "px");
|
||||
// Add list element.
|
||||
$tocList.append($("<li/>").append($item));
|
||||
} else if (tag === 'ul') {
|
||||
// Recurse.
|
||||
findA($item, level + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Start construction and return.
|
||||
findA($span);
|
||||
|
||||
// Wipe out old list and patch in new one.
|
||||
return $span.empty("ul").append($tocList);
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
// Patch the global and local TOC's to be bootstrap-compliant.
|
||||
patchToc($("span.globaltoc"), 1);
|
||||
patchToc($("span.localtoc"), 2);
|
||||
|
||||
// Activate.
|
||||
$('#topbar').dropdown();
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-29861888-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type =
|
||||
'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' :
|
||||
'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}{{ navBar() }}{% endblock %}
|
||||
|
||||
{# Silence the sidebar's, relbar's #}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}{% endblock %}
|
||||
|
||||
{%- block content %}
|
||||
|
||||
<div class="container">
|
||||
{% block body %} {% endblock %}
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
{%- block footer %}
|
||||
<footer class="footer">
|
||||
<p>
|
||||
{%- if show_copyright %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
{% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}<br/>
|
||||
{%- else %}
|
||||
{% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}<br/>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- if last_updated %}
|
||||
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}<br/>
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
{%- endblock %}
|
||||
|
||||
5
docsite/_themes/bootstrap/localtoc.html
Normal file
@ -0,0 +1,5 @@
|
||||
<li class="dropdown" data-dropdown="dropdown">
|
||||
<a href="#"
|
||||
class="dropdown-toggle">{{ _('Page') }}</a>
|
||||
<span class="localtoc">{{ toc }}</span>
|
||||
</li>
|
||||
8
docsite/_themes/bootstrap/relations.html
Normal file
@ -0,0 +1,8 @@
|
||||
{%- if prev %}
|
||||
<li><a href="{{ prev.link|e }}"
|
||||
title="{{ _('previous chapter') }}">{{ "«"|safe }} {{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li><a href="{{ next.link|e }}"
|
||||
title="{{ _('next chapter') }}">{{ next.title }} {{ "»"|safe }}</a></li>
|
||||
{%- endif %}
|
||||
7
docsite/_themes/bootstrap/searchbox.html
Normal file
@ -0,0 +1,7 @@
|
||||
{%- if pagename != "search" %}
|
||||
<form class="pull-left" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" placeholder="Search" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
{%- endif %}
|
||||
4
docsite/_themes/bootstrap/sourcelink.html
Normal file
@ -0,0 +1,4 @@
|
||||
{%- if show_source and has_source and sourcename %}
|
||||
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
|
||||
rel="nofollow">{{ _('Source') }}</a></li>
|
||||
{%- endif %}
|
||||
55
docsite/_themes/bootstrap/static/bootstrap-dropdown.js
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
/* ============================================================
|
||||
* bootstrap-dropdown.js v1.4.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdown
|
||||
* ============================================================
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.dropdown = function ( selector ) {
|
||||
return this.each(function () {
|
||||
$(this).delegate(selector || d, 'click', function (e) {
|
||||
var li = $(this).parent('li')
|
||||
, isActive = li.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
!isActive && li.toggleClass('open')
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
* =================================== */
|
||||
|
||||
var d = 'a.menu, .dropdown-toggle'
|
||||
|
||||
function clearMenus() {
|
||||
$(d).parent('li').removeClass('open')
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('html').bind("click", clearMenus)
|
||||
$('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
|
||||
})
|
||||
|
||||
}( window.jQuery || window.ender );
|
||||
107
docsite/_themes/bootstrap/static/bootstrap-scrollspy.js
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/* =============================================================
|
||||
* bootstrap-scrollspy.js v1.4.0
|
||||
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
|
||||
* =============================================================
|
||||
* Copyright 2011 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================== */
|
||||
|
||||
|
||||
!function ( $ ) {
|
||||
|
||||
"use strict"
|
||||
|
||||
var $window = $(window)
|
||||
|
||||
function ScrollSpy( topbar, selector ) {
|
||||
var processScroll = $.proxy(this.processScroll, this)
|
||||
this.$topbar = $(topbar)
|
||||
this.selector = selector || 'li > a'
|
||||
this.refresh()
|
||||
this.$topbar.delegate(this.selector, 'click', processScroll)
|
||||
$window.scroll(processScroll)
|
||||
this.processScroll()
|
||||
}
|
||||
|
||||
ScrollSpy.prototype = {
|
||||
|
||||
refresh: function () {
|
||||
this.targets = this.$topbar.find(this.selector).map(function () {
|
||||
var href = $(this).attr('href')
|
||||
return /^#\w/.test(href) && $(href).length ? href : null
|
||||
})
|
||||
|
||||
this.offsets = $.map(this.targets, function (id) {
|
||||
return $(id).offset().top
|
||||
})
|
||||
}
|
||||
|
||||
, processScroll: function () {
|
||||
var scrollTop = $window.scrollTop() + 10
|
||||
, offsets = this.offsets
|
||||
, targets = this.targets
|
||||
, activeTarget = this.activeTarget
|
||||
, i
|
||||
|
||||
for (i = offsets.length; i--;) {
|
||||
activeTarget != targets[i]
|
||||
&& scrollTop >= offsets[i]
|
||||
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
|
||||
&& this.activateButton( targets[i] )
|
||||
}
|
||||
}
|
||||
|
||||
, activateButton: function (target) {
|
||||
this.activeTarget = target
|
||||
|
||||
this.$topbar
|
||||
.find(this.selector).parent('.active')
|
||||
.removeClass('active')
|
||||
|
||||
this.$topbar
|
||||
.find(this.selector + '[href="' + target + '"]')
|
||||
.parent('li')
|
||||
.addClass('active')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* SCROLLSPY PLUGIN DEFINITION
|
||||
* =========================== */
|
||||
|
||||
$.fn.scrollSpy = function( options ) {
|
||||
var scrollspy = this.data('scrollspy')
|
||||
|
||||
if (!scrollspy) {
|
||||
return this.each(function () {
|
||||
$(this).data('scrollspy', new ScrollSpy( this, options ))
|
||||
})
|
||||
}
|
||||
|
||||
if ( options === true ) {
|
||||
return scrollspy
|
||||
}
|
||||
|
||||
if ( typeof options == 'string' ) {
|
||||
scrollspy[options]()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('body').scrollSpy('[data-scrollspy] li > a')
|
||||
})
|
||||
|
||||
}( window.jQuery || window.ender );
|
||||
24
docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* bootstrap-sphinx.css
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- Twitter Bootstrap theme.
|
||||
*/
|
||||
|
||||
body {
|
||||
padding-top: 42px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
.topbar h3 a, .topbar .brand {
|
||||
background: transparent url("{{ logo }}") no-repeat 22px 3px;
|
||||
padding-left: 62px;
|
||||
}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
2467
docsite/_themes/bootstrap/static/bootstrap.css
vendored
Normal file
5
docsite/_themes/bootstrap/theme.conf
Normal file
@ -0,0 +1,5 @@
|
||||
# Twitter Bootstrap Theme
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = basic.css
|
||||
pygments_style = tango
|
||||
BIN
docsite/ansible-logo.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docsite/ansible_arch.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
docsite/ansible_arch2.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
97
docsite/build-site.py
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of the Ansible Documentation
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from sphinx.application import Sphinx
|
||||
import os
|
||||
|
||||
|
||||
class SphinxBuilder(object):
|
||||
"""
|
||||
Creates HTML documentation using Sphinx.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Run the DocCommand.
|
||||
"""
|
||||
print "Creating html documentation ..."
|
||||
|
||||
try:
|
||||
buildername = 'html'
|
||||
|
||||
outdir = os.path.abspath(os.getcwd())
|
||||
# Create the output directory if it doesn't exist
|
||||
if not os.access(outdir, os.F_OK):
|
||||
os.mkdir(outdir)
|
||||
|
||||
doctreedir = os.path.join('./', '.doctrees')
|
||||
|
||||
confdir = os.path.abspath('./')
|
||||
srcdir = os.path.abspath('rst')
|
||||
freshenv = True
|
||||
|
||||
# Create the builder
|
||||
app = Sphinx(srcdir,
|
||||
confdir,
|
||||
outdir,
|
||||
doctreedir,
|
||||
buildername,
|
||||
{},
|
||||
sys.stdout,
|
||||
sys.stderr,
|
||||
freshenv)
|
||||
|
||||
app.builder.build_all()
|
||||
|
||||
except ImportError, ie:
|
||||
traceback.print_exc()
|
||||
except Exception, ex:
|
||||
print >> sys.stderr, "FAIL! exiting ... (%s)" % ex
|
||||
|
||||
def build_docs(self):
|
||||
self.app.builder.build_all()
|
||||
|
||||
|
||||
def build_rst_docs():
|
||||
docgen = SphinxBuilder()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '-h' in sys.argv or '--help' in sys.argv:
|
||||
print "This script builds the html documentation from rst/asciidoc sources.\n"
|
||||
print " Run 'make docs' to build everything."
|
||||
print " Run 'make viewdocs' to build and then preview in a web browser."
|
||||
sys.exit(0)
|
||||
|
||||
# The 'htmldocs' make target will call this scrip twith the 'rst'
|
||||
# parameter' We don't need to run the 'htmlman' target then.
|
||||
if "rst" in sys.argv:
|
||||
build_rst_docs()
|
||||
else:
|
||||
# By default, preform the rst->html transformation and then
|
||||
# the asciidoc->html trasnformation
|
||||
build_rst_docs()
|
||||
|
||||
if "view" in sys.argv:
|
||||
import webbrowser
|
||||
if not webbrowser.open('index.html'):
|
||||
print >> sys.stderr, "Could not open on your webbrowser."
|
||||
206
docsite/conf.py
Normal file
@ -0,0 +1,206 @@
|
||||
# -*- 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
|
||||
|
||||
# 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='0.01'
|
||||
AUTHOR='Michael DeHaan'
|
||||
|
||||
|
||||
# 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 = "2012 Michael DeHaan"
|
||||
|
||||
# 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'
|
||||
|
||||
|
||||
# Options for HTML output
|
||||
# -----------------------
|
||||
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'bootstrap'
|
||||
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 = 'default.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 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'
|
||||
BIN
docsite/github.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
31
docsite/man/ansible-playbook.1.html
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>ansible-playbook</title><link rel="stylesheet" href="./docbook-xsl.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.75.2" /></head><body><div xml:lang="en" class="refentry" title="ansible-playbook" lang="en"><a id="id474207"></a><div class="titlepage"></div><div class="refnamediv"><h2>Name</h2><p>ansible-playbook — run an ansible playbook</p></div><div class="refsynopsisdiv" title="Synopsis"><a id="_synopsis"></a><h2>Synopsis</h2><p>ansible-playbook <filename.yml> … [options]</p></div><div class="refsect1" title="DESCRIPTION"><a id="_description"></a><h2>DESCRIPTION</h2><p><span class="strong"><strong>Ansible playbooks</strong></span> are a configuration and multinode deployment
|
||||
system. Ansible-playbook is the tool used to run them. See the
|
||||
project home page (link below) for more information.</p></div><div class="refsect1" title="ARGUMENTS"><a id="_arguments"></a><h2>ARGUMENTS</h2><div class="variablelist"><dl><dt><span class="term">
|
||||
<span class="strong"><strong>filename.yml</strong></span>
|
||||
</span></dt><dd>
|
||||
The names of one or more YAML format files to run as ansible playbooks.
|
||||
</dd></dl></div></div><div class="refsect1" title="OPTIONS"><a id="_options"></a><h2>OPTIONS</h2><div class="variablelist"><dl><dt><span class="term">
|
||||
<span class="strong"><strong>-i</strong></span> <span class="emphasis"><em>PATH</em></span>, <span class="strong"><strong>--inventory=</strong></span><span class="emphasis"><em>PATH</em></span>
|
||||
</span></dt><dd>
|
||||
The <span class="emphasis"><em>PATH</em></span> to the inventory hosts file, which defaults to <span class="emphasis"><em>/etc/ansible/hosts</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-M</strong></span> <span class="emphasis"><em>DIRECTORY</em></span>, <span class="strong"><strong>--module-path=</strong></span><span class="emphasis"><em>DIRECTORY</em></span>
|
||||
</span></dt><dd>
|
||||
The <span class="emphasis"><em>DIRECTORY</em></span> to load modules from. The default is <span class="emphasis"><em>/usr/share/ansible</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-f</strong></span> <span class="emphasis"><em>NUM</em></span>, <span class="strong"><strong>--forks=</strong></span><span class="emphasis"><em>NUM</em></span>
|
||||
</span></dt><dd>
|
||||
Level of parallelism. <span class="emphasis"><em>NUM</em></span> is specified as an integer, the default is 5.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-k</strong></span>, <span class="strong"><strong>--ask-pass</strong></span>
|
||||
</span></dt><dd>
|
||||
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-T</strong></span> <span class="emphasis"><em>SECONDS</em></span>, <span class="strong"><strong>--timeout=</strong></span><span class="emphasis"><em>SECONDS</em></span>
|
||||
</span></dt><dd>
|
||||
Connection timeout to use when trying to talk to hosts, in <span class="emphasis"><em>SECONDS</em></span>.
|
||||
</dd></dl></div></div><div class="refsect1" title="ENVIRONMENT"><a id="_environment"></a><h2>ENVIRONMENT</h2><p>The following environment variables may specified.</p><p>ANSIBLE_HOSTS — Override the default ansible hosts file</p><p>ANSIBLE_LIBRARY — Override the default ansible module library path</p></div><div class="refsect1" title="AUTHOR"><a id="_author"></a><h2>AUTHOR</h2><p>Ansible was originally written by Michael DeHaan. See the AUTHORS file
|
||||
for a complete list of contributors.</p></div><div class="refsect1" title="COPYRIGHT"><a id="_copyright"></a><h2>COPYRIGHT</h2><p>Copyright © 2012, Michael DeHaan</p><p>Ansible is released under the terms of the GPLv3 License.</p></div><div class="refsect1" title="SEE ALSO"><a id="_see_also"></a><h2>SEE ALSO</h2><p><span class="strong"><strong>ansible</strong></span>(1)</p><p>Extensive documentation as well as IRC and mailing list info
|
||||
is available on the ansible home page: <a class="ulink" href="https://ansible.github.com/" target="_top">https://ansible.github.com/</a></p></div></div></body></html>
|
||||
63
docsite/man/ansible.1.html
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>ansible</title><link rel="stylesheet" href="./docbook-xsl.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.75.2" /></head><body><div xml:lang="en" class="refentry" title="ansible" lang="en"><a id="id392901"></a><div class="titlepage"></div><div class="refnamediv"><h2>Name</h2><p>ansible — run a command somewhere else</p></div><div class="refsynopsisdiv" title="Synopsis"><a id="_synopsis"></a><h2>Synopsis</h2><p>ansible <host-pattern> [-f forks] [-m module_name] [-a args]</p></div><div class="refsect1" title="DESCRIPTION"><a id="_description"></a><h2>DESCRIPTION</h2><p><span class="strong"><strong>Ansible</strong></span> is an extra-simple tool/framework/API for doing 'remote things' over
|
||||
SSH.</p></div><div class="refsect1" title="ARGUMENTS"><a id="_arguments"></a><h2>ARGUMENTS</h2><div class="variablelist"><dl><dt><span class="term">
|
||||
<span class="strong"><strong>host-pattern</strong></span>
|
||||
</span></dt><dd>
|
||||
A name of a group in the inventory file, a shell-like glob selecting
|
||||
hosts in inventory file, or any combination of the two separated by
|
||||
semicolons.
|
||||
</dd></dl></div></div><div class="refsect1" title="OPTIONS"><a id="_options"></a><h2>OPTIONS</h2><div class="variablelist"><dl><dt><span class="term">
|
||||
<span class="strong"><strong>-i</strong></span> <span class="emphasis"><em>PATH</em></span>, <span class="strong"><strong>--inventory=</strong></span><span class="emphasis"><em>PATH</em></span>
|
||||
</span></dt><dd>
|
||||
The <span class="emphasis"><em>PATH</em></span> to the inventory hosts file, which defaults to <span class="emphasis"><em>/etc/ansible/hosts</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-f</strong></span> <span class="emphasis"><em>NUM</em></span>, <span class="strong"><strong>--forks=</strong></span><span class="emphasis"><em>NUM</em></span>
|
||||
</span></dt><dd>
|
||||
Level of parallelism. <span class="emphasis"><em>NUM</em></span> is specified as an integer, the default is 5.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-m</strong></span> <span class="emphasis"><em>NAME</em></span>, <span class="strong"><strong>--module-name=</strong></span><span class="emphasis"><em>NAME</em></span>
|
||||
</span></dt><dd>
|
||||
Execute the module called <span class="emphasis"><em>NAME</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-M</strong></span> <span class="emphasis"><em>DIRECTORY</em></span>, <span class="strong"><strong>--module-path=</strong></span><span class="emphasis"><em>DIRECTORY</em></span>
|
||||
</span></dt><dd>
|
||||
The <span class="emphasis"><em>DIRECTORY</em></span> to load modules from. The default is <span class="emphasis"><em>/usr/share/ansible</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-a</strong></span> '<span class="emphasis"><em>ARGUMENTS</em></span>', <span class="strong"><strong>--args=</strong></span>'<span class="emphasis"><em>ARGUMENTS</em></span>'
|
||||
</span></dt><dd>
|
||||
The <span class="emphasis"><em>ARGUMENTS</em></span> to pass to the module.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-k</strong></span>, <span class="strong"><strong>--ask-pass</strong></span>
|
||||
</span></dt><dd>
|
||||
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-o</strong></span>, <span class="strong"><strong>--one-line</strong></span>
|
||||
</span></dt><dd>
|
||||
Try to output everything on one line.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-t</strong></span> <span class="emphasis"><em>DIRECTORY</em></span>, <span class="strong"><strong>--tree=</strong></span><span class="emphasis"><em>DIRECTORY</em></span>
|
||||
</span></dt><dd>
|
||||
Save contents in this output <span class="emphasis"><em>DIRECTORY</em></span>, with the results saved in a
|
||||
file named after each host.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-T</strong></span> <span class="emphasis"><em>SECONDS</em></span>, <span class="strong"><strong>--timeout=</strong></span><span class="emphasis"><em>SECONDS</em></span>
|
||||
</span></dt><dd>
|
||||
Connection timeout to use when trying to talk to hosts, in <span class="emphasis"><em>SECONDS</em></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-B</strong></span> <span class="emphasis"><em>NUM</em></span>, <span class="strong"><strong>--background=</strong></span><span class="emphasis"><em>NUM</em></span>
|
||||
</span></dt><dd>
|
||||
Run commands in the background, killing the task after <span class="emphasis"><em>NUM</em></span> seconds.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-P</strong></span> <span class="emphasis"><em>NUM</em></span>, <span class="strong"><strong>--poll=</strong></span><span class="emphasis"><em>NUM</em></span>
|
||||
</span></dt><dd>
|
||||
Poll a background job every <span class="emphasis"><em>NUM</em></span> seconds. Requires <span class="strong"><strong>-B</strong></span>.
|
||||
</dd><dt><span class="term">
|
||||
<span class="strong"><strong>-u</strong></span> <span class="emphasis"><em>USERNAME</em></span>, <span class="strong"><strong>--remote-user=</strong></span><span class="emphasis"><em>USERNAME</em></span>
|
||||
</span></dt><dd>
|
||||
Use this remote <span class="emphasis"><em>USERNAME</em></span> instead of root.
|
||||
</dd></dl></div></div><div class="refsect1" title="INVENTORY"><a id="_inventory"></a><h2>INVENTORY</h2><p>Ansible stores the hosts it can potentially operate on in an inventory
|
||||
file. The syntax is one host per line. Groups headers are allowed and
|
||||
are included on their own line, enclosed in square brackets.</p></div><div class="refsect1" title="FILES"><a id="_files"></a><h2>FILES</h2><p>/etc/ansible/hosts — Default inventory file</p><p>/usr/share/ansible/ — Default module library</p></div><div class="refsect1" title="ENVIRONMENT"><a id="_environment"></a><h2>ENVIRONMENT</h2><p>The following environment variables may specified.</p><p>ANSIBLE_HOSTS — Override the default ansible hosts file</p><p>ANSIBLE_LIBRARY — Override the default ansible module library path</p></div><div class="refsect1" title="AUTHOR"><a id="_author"></a><h2>AUTHOR</h2><p>Ansible was originally written by Michael DeHaan. See the AUTHORS file
|
||||
for a complete list of contributors.</p></div><div class="refsect1" title="COPYRIGHT"><a id="_copyright"></a><h2>COPYRIGHT</h2><p>Copyright © 2012, Michael DeHaan</p><p>Ansible is released under the terms of the GPLv3 License.</p></div><div class="refsect1" title="SEE ALSO"><a id="_see_also"></a><h2>SEE ALSO</h2><p><span class="strong"><strong>ansible-playbook</strong></span>(1)</p><p>Extensive documentation as well as IRC and mailing list info
|
||||
is available on the ansible home page: <a class="ulink" href="https://ansible.github.com/" target="_top">https://ansible.github.com/</a></p></div></div></body></html>
|
||||
108
docsite/rst/YAMLSyntax.rst
Normal file
@ -0,0 +1,108 @@
|
||||
YAML Syntax
|
||||
===========
|
||||
|
||||
This page provides a basic overview of correct YAML syntax, which is how Ansible
|
||||
playbooks (our configuration management language) are expressed.
|
||||
|
||||
We use YAML because it is easier to read and write for humans than other common
|
||||
data formats like XML or JSON. Further, there are libraries available for reading
|
||||
and writing YAML in most programming languages.
|
||||
|
||||
You may also wish to read :doc:`playbooks` at the same time to see how this
|
||||
is used in practice.
|
||||
|
||||
|
||||
YAML Basics
|
||||
-----------
|
||||
|
||||
For `ansible`, nearly every YAML file starts with a list.
|
||||
Each item in the list is a list of key/value pairs, commonly
|
||||
called a "hash" or a "dictionary". So, we need to know how
|
||||
to write lists and dictionaries in YAML.
|
||||
|
||||
There's another small quirk to YAML. All YAML files (regardless of their association with
|
||||
`ansible` or not) should start with ``---``. This is just a YAML
|
||||
format thing that means "this is the start of a document".
|
||||
|
||||
All members of a list are lines beginning at the same indentation level starting
|
||||
with a ``-`` (dash) character::
|
||||
|
||||
---
|
||||
# A list of tasty fruits
|
||||
- Apple
|
||||
- Orange
|
||||
- Strawberry
|
||||
- Mango
|
||||
|
||||
A dictionary is represented in a simple ``key:`` and ``value`` form::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
name: Example Developer
|
||||
job: Developer
|
||||
skill: Elite
|
||||
|
||||
Dictionaries can also be represented in an abbreviated form if you really want to::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
{name: Example Developer, job: Developer, skill: Elite}
|
||||
|
||||
.. _truthiness:
|
||||
|
||||
Ansible doesn't really use these too much, but you can also specify a
|
||||
boolean value (true/false) in several forms::
|
||||
|
||||
---
|
||||
knows_oop: True
|
||||
likes_emacs: TRUE
|
||||
uses_cvs: false
|
||||
|
||||
Let's combine what we learned so far in an arbitary YAML example. This really
|
||||
has nothing to do with Ansible, but will give you a feel for the format::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
name: Example Developer
|
||||
job: Developer
|
||||
skill: Elite
|
||||
employed: True
|
||||
foods:
|
||||
- Apple
|
||||
- Orange
|
||||
- Strawberry
|
||||
- Mango
|
||||
languages:
|
||||
ruby: Elite
|
||||
python: Elite
|
||||
dotnet: Lame
|
||||
|
||||
That's all you really need to know about YAML to get started writing
|
||||
`Ansible` playbooks.
|
||||
|
||||
Gotchas
|
||||
-------
|
||||
|
||||
While YAML is generally friendly, the following is going to be a YAML syntax error:
|
||||
|
||||
foo: somebody said I should put a colon here: so I did
|
||||
|
||||
You will want to quote any hash values using colons, like so:
|
||||
|
||||
foo: "somebody said I should put a colon here: so I did"
|
||||
|
||||
And then the colon will be preserved.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`playbooks`
|
||||
Learn what playbooks can do and how to write/run them.
|
||||
`YAMLLint <http://yamllint.com/>`_
|
||||
YAML Lint (online) helps you debug YAML syntax if you are having problems
|
||||
`Github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_
|
||||
Complete playbook files from the github project source
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
333
docsite/rst/api.rst
Normal file
@ -0,0 +1,333 @@
|
||||
API & Integrations
|
||||
==================
|
||||
|
||||
There are several interesting ways to use Ansible from an API perspective. You can use
|
||||
the Ansible python API to control nodes, you can extend Ansible to respond to various python events,
|
||||
and you can plug in inventory data from external data sources. Ansible is written in its own
|
||||
API so you have a considerable amount of power across the board.
|
||||
|
||||
.. contents:: `Table of contents`
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
The Python API is very powerful, and is how the ansible CLI and ansible-playbook
|
||||
are implemented.
|
||||
|
||||
It's pretty simple::
|
||||
|
||||
import ansible.runner
|
||||
|
||||
runner = ansible.runner.Runner(
|
||||
module_name='ping',
|
||||
module_args='',
|
||||
pattern='web*',
|
||||
forks=10
|
||||
)
|
||||
datastructure = runner.run()
|
||||
|
||||
The run method returns results per host, grouped by whether they
|
||||
could be contacted or not. Return types are module specific, as
|
||||
expressed in the 'ansible-modules' documentation.::
|
||||
|
||||
{
|
||||
"dark" : {
|
||||
"web1.example.com" : "failure message"
|
||||
}
|
||||
"contacted" : {
|
||||
"web2.example.com" : 1
|
||||
}
|
||||
}
|
||||
|
||||
A module can return any type of JSON data it wants, so Ansible can
|
||||
be used as a framework to rapidly build powerful applications and scripts.
|
||||
|
||||
Detailed API Example
|
||||
````````````````````
|
||||
|
||||
The following script prints out the uptime information for all hosts::
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
import ansible.runner
|
||||
import sys
|
||||
|
||||
# construct the ansible runner and execute on all hosts
|
||||
results = ansible.runner.Runner(
|
||||
pattern='*', forks=10,
|
||||
module_name='command', module_args='/usr/bin/uptime',
|
||||
).run()
|
||||
|
||||
if results is None:
|
||||
print "No hosts found"
|
||||
sys.exit(1)
|
||||
|
||||
print "UP ***********"
|
||||
for (hostname, result) in results['contacted'].items():
|
||||
if not 'failed' in result:
|
||||
print "%s >>> %s" % (hostname, result['stdout'])
|
||||
|
||||
print "FAILED *******"
|
||||
for (hostname, result) in results['contacted'].items():
|
||||
if 'failed' in result:
|
||||
print "%s >>> %s" % (hostname, result['msg'])
|
||||
|
||||
print "DOWN *********"
|
||||
for (hostname, result) in results['dark'].items():
|
||||
print "%s >>> %s" % (hostname, result)
|
||||
|
||||
Advanced programmers may also wish to read the source to ansible itself, for
|
||||
it uses the Runner() API (with all available options) to implement the
|
||||
command line tools ``ansible`` and ``ansible-playbook``.
|
||||
|
||||
Plugins Available Online
|
||||
------------------------
|
||||
|
||||
The remainder of features in the API docs have components available in `ansible-plugins <https://github.com/ansible/ansible/blob/devel/plugins>`_. Send us a github pull request if you develop any interesting features.
|
||||
|
||||
External Inventory Scripts
|
||||
--------------------------
|
||||
|
||||
Often a user of a configuration management system will want to keep inventory
|
||||
in a different system. Frequent examples include LDAP, `Cobbler <http://cobbler.github.com>`_,
|
||||
or a piece of expensive enterprisey CMDB software. Ansible easily supports all
|
||||
of these options via an external inventory system. The plugins directory contains some of these already -- including options for EC2/Eucalyptus and OpenStack, which will be detailed below.
|
||||
|
||||
It's possible to write an external inventory script in any language. If you are familiar with Puppet terminology, this concept is basically the same as 'external nodes', with the slight difference that it also defines which hosts are managed.
|
||||
|
||||
Script Conventions
|
||||
``````````````````
|
||||
|
||||
When the external node script is called with the single argument '--list', the script must return a JSON hash/dictionary of all the groups to be managed, with a list of each host/IP as the value for each hash/dictionary element, like so::
|
||||
|
||||
{
|
||||
'databases' : [ 'host1.example.com', 'host2.example.com' ],
|
||||
'webservers' : [ 'host2.example.com', 'host3.example.com' ],
|
||||
'atlanta' : [ 'host1.example.com', 'host4.example.com', 'host5.example.com' ]
|
||||
}
|
||||
|
||||
When called with the arguments '--host <hostname>' (where <hostname> is a host from above), the script must return either an empty JSON
|
||||
hash/dictionary, or a list of key/value variables to make available to templates or playbooks. Returning variables is optional,
|
||||
if the script does not wish to do this, returning an empty hash/dictionary is the way to go::
|
||||
|
||||
{
|
||||
'favcolor' : 'red',
|
||||
'ntpserver' : 'wolf.example.com',
|
||||
'monitoring' : 'pack.example.com'
|
||||
}
|
||||
|
||||
Example: The Cobbler External Inventory Script
|
||||
``````````````````````````````````````````````
|
||||
|
||||
It is expected that many Ansible users will also be `Cobbler <http://cobbler.github.com>`_ users. Cobbler has a generic
|
||||
layer that allows it to represent data for multiple configuration management systems (even at the same time), and has
|
||||
been referred to as a 'lightweight CMDB' by some admins. This particular script will communicate with Cobbler
|
||||
using Cobbler's XMLRPC API.
|
||||
|
||||
To tie Ansible's inventory to Cobbler (optional), copy `this script <https://raw.github.com/ansible/ansible/devel/plugins/inventory/cobbler.py>`_ to /etc/ansible/hosts and `chmod +x` the file. cobblerd will now need
|
||||
to be running when you are using Ansible.
|
||||
|
||||
Test the file by running `./etc/ansible/hosts` directly. You should see some JSON data output, but it may not have
|
||||
anything in it just yet.
|
||||
|
||||
Let's explore what this does. In cobbler, assume a scenario somewhat like the following::
|
||||
|
||||
cobbler profile add --name=webserver --distro=CentOS6-x86_64
|
||||
cobbler profile edit --name=webserver --mgmt-classes="webserver" --ksmeta="a=2 b=3"
|
||||
cobbler system edit --name=foo --dns-name="foo.example.com" --mgmt-classes="atlanta" --ksmeta="c=4"
|
||||
cobbler system edit --name=bar --dns-name="bar.example.com" --mgmt-classes="atlanta" --ksmeta="c=5"
|
||||
|
||||
In the example above, the system 'foo.example.com' will be addressable by ansible directly, but will also be addressable when using the group names 'webserver' or 'atlanta'. Since Ansible uses SSH, we'll try to contract system foo over 'foo.example.com', only, never just 'foo'. Similarly, if you try "ansible foo" it wouldn't find the system... but "ansible 'foo*'" would, because the system DNS name starts with 'foo'.
|
||||
|
||||
The script doesn't just provide host and group info. In addition, as a bonus, when the 'setup' module is run (which happens automatically when using playbooks), the variables 'a', 'b', and 'c' will all be auto-populated in the templates::
|
||||
|
||||
# file: /srv/motd.j2
|
||||
Welcome, I am templated with a value of a={{ a }}, b={{ b }}, and c={{ c }}
|
||||
|
||||
Which could be executed just like this::
|
||||
|
||||
ansible webserver -m setup
|
||||
ansible webserver -m template -a "src=/tmp/motd.j2 dest=/etc/motd"
|
||||
|
||||
.. note::
|
||||
The name 'webserver' came from cobbler, as did the variables for
|
||||
the config file. You can still pass in your own variables like
|
||||
normal in Ansible, but variables from the external inventory script
|
||||
will override any that have the same name.
|
||||
|
||||
So, with the template above (motd.j2), this would result in the following data being written to /etc/motd for system 'foo'::
|
||||
|
||||
Welcome, I am templated with a value of a=2, b=3, and c=4
|
||||
|
||||
And on system 'bar' (bar.example.com)::
|
||||
|
||||
Welcome, I am templated with a value of a=2, b=3, and c=5
|
||||
|
||||
And technically, though there is no major good reason to do it, this also works too::
|
||||
|
||||
ansible webserver -m shell -a "echo {{ a }}"
|
||||
|
||||
So in other words, you can use those variables in arguments/actions as well. You might use this to name
|
||||
a conf.d file appropriately or something similar. Who knows?
|
||||
|
||||
So that's the Cobbler integration support -- using the cobbler script as an example, it should be trivial to adapt Ansible to pull inventory, as well as variable information, from any data source. If you create anything interesting, please share with the mailing list, and we can keep it in the source code tree for others to use.
|
||||
|
||||
Example: AWS EC2 External Inventory Script
|
||||
``````````````````````````````````````````
|
||||
|
||||
If you use Amazon Web Services EC2, maintaining an inventory file might not be the best approach. For this reason, you can use the `EC2 external inventory <https://raw.github.com/ansible/ansible/devel/plugins/inventory/ec2.py>`_ script.
|
||||
|
||||
You can use this script in one of two ways. The easiest is to use Ansible's ``-i`` command line option and specify the path to the script.
|
||||
|
||||
ansible -i ec2.py -u ubuntu us-east-1d -m ping
|
||||
|
||||
The second option is to copy the script to `/etc/ansible/hosts` and `chmod +x` it. You will also need to copy the ``ec2.ini`` file to `/etc/ansible/ec2.ini`. Then you can run ansible as you would normally.
|
||||
|
||||
To successfully make an API call to AWS, you will need to configure Boto (the Python interface to AWS). There are a `variety of methods <http://docs.pythonboto.org/en/latest/boto_config_tut.html>`_ available, but the simplest is just to export two environment variables:
|
||||
|
||||
export AWS_ACCESS_KEY_ID='AK123'
|
||||
export AWS_SECRET_ACCESS_KEY='abc123'
|
||||
|
||||
You can test the script by itself to make sure your config is correct
|
||||
|
||||
cd examples/scripts
|
||||
./ec2_external_inventory.py --list
|
||||
|
||||
After a few moments, you should see your entire EC2 inventory across all regions in JSON.
|
||||
|
||||
Since each region requires its own API call, if you are only using a small set of regions, feel free to edit ``ec2.ini`` and list only the regions you are interested in. There are other config options in ``ec2.ini`` including cache control, and destination variables.
|
||||
|
||||
At their heart, inventory files are simply a mapping from some name to a destination address. The default ``ec2.ini`` settings are configured for running Ansible from outside EC2 (from your laptop for example). If you are running Ansible from within EC2, internal DNS names and IP addresses may make more sense than public DNS names. In this case, you can modify the ``destination_variable`` in ``ec2.ini`` to be the private DNS name of an instance. This is particularly important when running Ansible within a private subnet inside a VPC, where the only way to access an instance is via its private IP address. For VPC instances, `vpc_destination_variable` in ``ec2.ini`` provides a means of using which ever `boto.ec2.instance variable <http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance>`_ makes the most sense for your use case.
|
||||
|
||||
The EC2 external inventory provides mappings to instances from several groups:
|
||||
|
||||
Instance ID
|
||||
These are groups of one since instance IDs are unique.
|
||||
e.g.
|
||||
``i-00112233``
|
||||
``i-a1b1c1d1``
|
||||
|
||||
Region
|
||||
A group of all instances in an AWS region.
|
||||
e.g.
|
||||
``us-east-1``
|
||||
``us-west-2``
|
||||
|
||||
Availability Zone
|
||||
A group of all instances in an availability zone.
|
||||
e.g.
|
||||
``us-east-1a``
|
||||
``us-east-1b``
|
||||
|
||||
Security Group
|
||||
Instances belong to one or more security groups. A group is created for each security group, with all characters except alphanumerics, dashes (-) converted to underscores (_). Each group is prefixed by ``security_group_``
|
||||
e.g.
|
||||
``security_group_default``
|
||||
``security_group_webservers``
|
||||
``security_group_Pete_s_Fancy_Group``
|
||||
|
||||
Tags
|
||||
Each instance can have a variety of key/value pairs associated with it called Tags. The most common tag key is 'Name', though anything is possible. Each key/value pair is its own group of instances, again with special characters converted to underscores, in the format ``tag_KEY_VALUE``
|
||||
e.g.
|
||||
``tag_Name_Web``
|
||||
``tag_Name_redis-master-001``
|
||||
``tag_aws_cloudformation_logical-id_WebServerGroup``
|
||||
|
||||
When the Ansible is interacting with a specific server, the EC2 inventory script is called again with the ``--host HOST`` option. This looks up the HOST in the index cache to get the instance ID, and then makes an API call to AWS to get information about that specific instance. It then makes information about that instance available as variables to your playbooks. Each variable is prefixed by ``ec2_``. Here are some of the variables available:
|
||||
|
||||
- ec2_architecture
|
||||
- ec2_description
|
||||
- ec2_dns_name
|
||||
- ec2_id
|
||||
- ec2_image_id
|
||||
- ec2_instance_type
|
||||
- ec2_ip_address
|
||||
- ec2_kernel
|
||||
- ec2_key_name
|
||||
- ec2_launch_time
|
||||
- ec2_monitored
|
||||
- ec2_ownerId
|
||||
- ec2_placement
|
||||
- ec2_platform
|
||||
- ec2_previous_state
|
||||
- ec2_private_dns_name
|
||||
- ec2_private_ip_address
|
||||
- ec2_public_dns_name
|
||||
- ec2_ramdisk
|
||||
- ec2_region
|
||||
- ec2_root_device_name
|
||||
- ec2_root_device_type
|
||||
- ec2_security_group_ids
|
||||
- ec2_security_group_names
|
||||
- ec2_spot_instance_request_id
|
||||
- ec2_state
|
||||
- ec2_state_code
|
||||
- ec2_state_reason
|
||||
- ec2_status
|
||||
- ec2_subnet_id
|
||||
- ec2_tag_Name
|
||||
- ec2_tenancy
|
||||
- ec2_virtualization_type
|
||||
- ec2_vpc_id
|
||||
|
||||
Both ``ec2_security_group_ids`` and ``ec2_security_group_names`` are comma-separated lists of all security groups. Each EC2 tag is a variable in the format ``ec2_tag_KEY``.
|
||||
|
||||
To see the complete list of variables available for an instance, run the script by itself::
|
||||
|
||||
cd examples/scripts
|
||||
./ec2_external_inventory.py --host ec2-12-12-12-12.compute-1.amazonaws.com
|
||||
|
||||
Example: OpenStack Inventory Script
|
||||
```````````````````````````````````
|
||||
|
||||
Though not detailed here in as much depth as the EC2 module, there's also a OpenStack Nova external inventory source in the plugins directory. See the inline comments in the module source for how to use it.
|
||||
|
||||
Callback Plugins
|
||||
----------------
|
||||
|
||||
Ansible can be configured via code to respond to external events. This can include enhancing logging, signalling an external software
|
||||
system, or even (yes, really) making sound effects. Some examples are contained in the plugins directory.
|
||||
|
||||
Connection Type Plugins
|
||||
-----------------------
|
||||
|
||||
By default, ansible ships with a 'paramiko' SSH, native ssh (just called 'ssh'), and 'local' connection type. Release 0.8 also
|
||||
added an accelerated connection type named 'fireball'. All of these can be used
|
||||
in playbooks and with /usr/bin/ansible to decide how you want to talk to remote machines. The basics of these connection types
|
||||
are covered in the 'getting started' section. Should you want to extend Ansible to support other transports (SNMP? Message bus?
|
||||
Carrier Pigeon?) it's as simple as copying the format of one of the existing modules and dropping it into the connection plugins
|
||||
directory.
|
||||
|
||||
Lookup Plugins
|
||||
--------------
|
||||
|
||||
Language constructs like "with_fileglob" are implemnted via lookup plugins. Just like other plugin types, you can write your own.
|
||||
|
||||
Distributing Plugins
|
||||
--------------------
|
||||
|
||||
.. versionadded: 0.8
|
||||
|
||||
Plugins are loaded from both Python's site_packages (those that ship with ansible) and a configured plugins directory, which defaults
|
||||
to /usr/share/ansible/plugins, in a subfolder for each plugin type::
|
||||
|
||||
* action_plugins
|
||||
* lookup_plugins
|
||||
* callback_plugins
|
||||
* connection_plugins
|
||||
|
||||
To change this path, edit the ansible configuration file.
|
||||
|
||||
In addition, plugins can be shipped in a subdirectory relative to a top-level playbook, in folders named the same as indicated above.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
List of built-in modules
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
143
docsite/rst/bestpractices.rst
Normal file
@ -0,0 +1,143 @@
|
||||
Best Practices
|
||||
==============
|
||||
|
||||
Here are some tips for making the most of Ansible.
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Always Mention State
|
||||
++++++++++++++++++++
|
||||
|
||||
The 'state' parameter is optional to a lot of modules. Whether
|
||||
'state=present' or 'state=absent', it's always best to leave that
|
||||
parameter in your playbooks to make it clear, especially as some
|
||||
modules support additional states.
|
||||
|
||||
Group By Roles
|
||||
++++++++++++++
|
||||
|
||||
A system can be in multiple groups. See :doc:`patterns`. Having groups named after things like
|
||||
*webservers* and *dbservers* is repeated in the examples because it's a very powerful concept.
|
||||
|
||||
This allows playbooks to target machines based on role, as well as to assign role specific variables
|
||||
using the group variable system.
|
||||
|
||||
Directory Organization
|
||||
++++++++++++++++++++++
|
||||
|
||||
Playbooks should be organized like this::
|
||||
|
||||
# root of source control repository
|
||||
├── acme/
|
||||
│ ├── setup.yml
|
||||
│ └── stop.yml
|
||||
├── files/
|
||||
│ └── some_file_path_foo.conf
|
||||
├── handlers/
|
||||
│ └── main.yml
|
||||
├── tasks/
|
||||
│ ├── setup.yml
|
||||
│ └── stop.yml
|
||||
├── templates/
|
||||
│ ├── etc_acme_conf_acme.conf
|
||||
│ └── etc_other_conf_other.conf
|
||||
├── vars/
|
||||
│ └── main.yml
|
||||
└── global_vars.yml
|
||||
|
||||
Any directories or files not needed can be omitted. Not all modules
|
||||
may require 'vars' or 'files' sections, though most will require
|
||||
'handlers', 'tasks', and 'templates'. To review what each of
|
||||
these sections do, see :doc:`playbooks` and :doc:`playbooks2`.
|
||||
|
||||
The acme/setup.yml playbook would be as simple as::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
user: root
|
||||
|
||||
vars_files:
|
||||
- ../global_vars.yml
|
||||
- vars/main.yml
|
||||
tasks:
|
||||
- include: tasks/setup.yml
|
||||
handlers:
|
||||
- include: handlers/main.yml
|
||||
|
||||
The tasks are individually broken out in 'acme/tasks/setup.yml', and handlers, which are common to all task files,
|
||||
are contained in 'acme/handlers/main.yml'. As a reminder, handlers are mostly just used to notify services to restart
|
||||
when things change, and these are described in :doc:`playbooks`.
|
||||
|
||||
Including more than one setup file or more than one handlers file is of course legal.
|
||||
|
||||
Bundling Ansible Modules With Playbooks
|
||||
+++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
If a playbook has a "./library" directory relative to it's YAML file,
|
||||
this directory can be used to add ansible modules that will
|
||||
automatically be in the ansible module path. This is a great way to
|
||||
keep modules that go with a playbook together.
|
||||
|
||||
Miscellaneous Tips
|
||||
++++++++++++++++++
|
||||
|
||||
When you can do something simply, do something simply. Do not reach
|
||||
to use every feature of Ansible together, all at once. Use what works
|
||||
for you. For example, you should probably not need 'vars',
|
||||
'vars_files', 'vars_prompt' and '--extra-vars' all at once,
|
||||
while also using an external inventory file.
|
||||
|
||||
Optimize for readability. Whitespace between sections of YAML
|
||||
documents and in between tasks is strongly encouraged, as is usage of
|
||||
YAML comments, which start with '#'. It is also useful to comment
|
||||
at the top of each file the purpose of the individual file and the
|
||||
author, including email address.
|
||||
|
||||
It is possible to leave off the 'name' for a given task, though it
|
||||
is recommended to provide a descriptive comment about why something is
|
||||
being done instead.
|
||||
|
||||
Use version control. Keep your playbooks and inventory file in git
|
||||
(or another version control system), and commit when you make changes
|
||||
to them. This way you have an audit trail describing when and why you
|
||||
changed the rules automating your infrastructure.
|
||||
|
||||
Resist the urge to write the same playbooks and configuration files
|
||||
for heterogeneous distributions. While lots of software packages
|
||||
claim to make this easy on you, the configuration files are often
|
||||
quite different, to the point where it would be easier to treat them
|
||||
as different playbooks. This is why, for example, Ansible has a
|
||||
separate 'yum' and 'apt' module. Yum and apt have different
|
||||
capabilities, and we don't want to code for the least common
|
||||
denominator.
|
||||
|
||||
Use variables for user tunable settings versus having constants in the
|
||||
tasks file or templates, so that it is easy to reconfigure a playbook.
|
||||
Think about this as exposing the knobs to things you would like to
|
||||
tweak.
|
||||
|
||||
Since a system can be in more than one group, if you have multiple
|
||||
datacenters or sites, consider putting systems into groups by role,
|
||||
but also different groups by geography. This allows you to assign
|
||||
different variables to different geographies.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`YAMLSyntax`
|
||||
Learn about YAML syntax
|
||||
:doc:`playbooks`
|
||||
Review the basic playbook features
|
||||
:doc:`modules`
|
||||
Learn about available modules
|
||||
:doc:`moduledev`
|
||||
Learn how to extend Ansible by writing your own modules
|
||||
:doc:`patterns`
|
||||
Learn about how to select hosts
|
||||
`Github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_
|
||||
Complete playbook files from the github project source
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
263
docsite/rst/examples.rst
Normal file
@ -0,0 +1,263 @@
|
||||
Command Line Examples And Next Steps
|
||||
====================================
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
The following examples show how to use `/usr/bin/ansible` for running
|
||||
ad hoc tasks. Start here.
|
||||
|
||||
For configuration management and deployments, you'll want to pick up on
|
||||
using `/usr/bin/ansible-playbook` -- the concepts port over directly.
|
||||
(See :doc:`playbooks` for more information about those)
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
|
||||
Parallelism and Shell Commands
|
||||
``````````````````````````````
|
||||
|
||||
Let's use ansible's command line tool to reboot all web servers in Atlanta, 10 at a time. First, let's
|
||||
set up SSH-agent so it can remember our credentials::
|
||||
|
||||
$ ssh-agent bash
|
||||
$ ssh-add ~/.ssh/id_rsa.pub
|
||||
|
||||
If you don't want to use ssh-agent and want to instead SSH with a
|
||||
password instead of keys, you can with ``--ask-pass`` (``-k``), but
|
||||
it's much better to just use ssh-agent.
|
||||
|
||||
Now to run the command on all servers in a group, in this case,
|
||||
*atlanta*, in 10 parallel forks::
|
||||
|
||||
$ ansible atlanta -a "/sbin/reboot" -f 10
|
||||
|
||||
In 0.7 and later, this will default to running from your user account. If you do not like this
|
||||
behavior, pass in "-u username". (In 0.6 and before, it defaulted to root. Most folks prefered
|
||||
defaulting to the current user, so we changed it).
|
||||
|
||||
If you want to run commands as a different user, it looks like this::
|
||||
|
||||
$ ansible atlanta -a "/usr/bin/foo" -u username
|
||||
|
||||
If you want to run commands through sudo::
|
||||
|
||||
$ ansible atlanta -a "/usr/bin/foo" -u username --sudo [--ask-sudo-pass]
|
||||
|
||||
Use ``--ask-sudo-pass`` (``-K``) if you are not using passwordless
|
||||
sudo. This will interactively prompt you for the password to use.
|
||||
Use of passwordless sudo makes things easier to automate, but it's not
|
||||
required.
|
||||
|
||||
It is also possible to sudo to a user other than root using
|
||||
``--sudo-user`` (``-U``)::
|
||||
|
||||
$ ansible atlanta -a "/usr/bin/foo" -u username -U otheruser [--ask-sudo-pass]
|
||||
|
||||
Ok, so those are basics. If you didn't read about patterns and groups yet, go back and read :doc:`patterns`.
|
||||
|
||||
The ``-f 10`` in the above specifies the usage of 10 simultaneous
|
||||
processes. Normally commands also take a ``-m`` for module name, but
|
||||
the default module name is 'command', so we didn't need to
|
||||
specify that all of the time. We'll use ``-m`` in later examples to
|
||||
run some other :doc:`modules`.
|
||||
|
||||
.. note::
|
||||
The :ref:`command` module requires absolute paths and does not
|
||||
support shell variables. If we want to execute a module using a
|
||||
shell, we can do those things, and also use pipe and redirection
|
||||
operators. Read more about the differences on the :doc:`modules`
|
||||
page.
|
||||
|
||||
Using the :ref:`shell` module looks like this::
|
||||
|
||||
$ ansible raleigh -m shell -a 'echo $TERM'
|
||||
|
||||
When running any command with the ansible *ad hoc* CLI (as opposed to
|
||||
:doc:`playbooks`), pay particular attention to shell quoting rules, so
|
||||
the shell doesn't eat a variable before it gets passed to Ansible.
|
||||
For example, using double vs single quotes in the above example would
|
||||
evaluate the variable on the box you were on.
|
||||
|
||||
So far we've been demoing simple command execution, but most Ansible modules usually do not work like
|
||||
simple scripts. They make the remote system look like you state, and run the commands necessary to
|
||||
get it there. This is commonly referred to as 'idempotence', and is a core design goal of ansible.
|
||||
However, we also recognize that running *ad hoc* commands is equally important, so Ansible easily supports both.
|
||||
|
||||
|
||||
File Transfer
|
||||
`````````````
|
||||
|
||||
Here's another use case for the `/usr/bin/ansible` command line. Ansible can SCP lots of files to multiple machines in parallel.
|
||||
|
||||
To transfer a file directly to many different servers::
|
||||
|
||||
$ ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"
|
||||
|
||||
If you use playbooks, you can also take advantage of the ``template`` module,
|
||||
which takes this another step further. (See module and playbook documentation).
|
||||
|
||||
The ``file`` module allows changing ownership and permissions on files. These
|
||||
same options can be passed directly to the ``copy`` module as well::
|
||||
|
||||
$ ansible webservers -m file -a "dest=/srv/foo/a.txt mode=600"
|
||||
$ ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
|
||||
|
||||
The ``file`` module can also create directories, similar to ``mkdir -p``::
|
||||
|
||||
$ ansible webservers -m file -a "dest=/path/to/c mode=644 owner=mdehaan group=mdehaan state=directory"
|
||||
|
||||
As well as delete directories (recursively) and delete files::
|
||||
|
||||
$ ansible webservers -m file -a "dest=/path/to/c state=absent"
|
||||
|
||||
|
||||
Managing Packages
|
||||
`````````````````
|
||||
|
||||
There are modules available for yum and apt. Here are some examples
|
||||
with yum.
|
||||
|
||||
Ensure a package is installed, but don't update it::
|
||||
|
||||
$ ansible webservers -m yum -a "pkg=acme state=installed"
|
||||
|
||||
Ensure a package is installed to a specific version::
|
||||
|
||||
$ ansible webservers -m yum -a "pkg=acme-1.5 state=installed"
|
||||
|
||||
Ensure a package is at the latest version::
|
||||
|
||||
$ ansible webservers -m yum -a "pkg=acme state=latest"
|
||||
|
||||
Ensure a package is not installed::
|
||||
|
||||
$ ansible webservers -m yum -a "pkg=acme state=removed"
|
||||
|
||||
Currently Ansible only has modules for managing packages with yum and apt. You can install
|
||||
for other packages for now using the command module or (better!) contribute a module
|
||||
for other package managers. Stop by the mailing list for info/details.
|
||||
|
||||
Users and Groups
|
||||
````````````````
|
||||
|
||||
The 'user' module allows easy creation and manipulation of
|
||||
existing user accounts, as well as removal of user accounts that may
|
||||
exist::
|
||||
|
||||
$ ansible all -m user -a "name=foo password=<crypted password here>"
|
||||
|
||||
$ ansible all -m user -a "name=foo state=absent"
|
||||
|
||||
See the :doc:`modules` section for details on all of the available options, including
|
||||
how to manipulate groups and group membership.
|
||||
|
||||
Deploying From Source Control
|
||||
`````````````````````````````
|
||||
|
||||
Deploy your webapp straight from git::
|
||||
|
||||
$ ansible webservers -m git -a "repo=git://foo.example.org/repo.git dest=/srv/myapp version=HEAD"
|
||||
|
||||
Since ansible modules can notify change handlers it is possible to
|
||||
tell ansible to run specific tasks when the code is updated, such as
|
||||
deploying Perl/Python/PHP/Ruby directly from git and then restarting
|
||||
apache.
|
||||
|
||||
Managing Services
|
||||
`````````````````
|
||||
|
||||
Ensure a service is started on all webservers::
|
||||
|
||||
$ ansible webservers -m service -a "name=httpd state=started"
|
||||
|
||||
Alternatively, restart a service on all webservers::
|
||||
|
||||
$ ansible webservers -m service -a "name=httpd state=restarted"
|
||||
|
||||
Ensure a service is stopped::
|
||||
|
||||
$ ansible webservers -m service -a "name=httpd state=stopped"
|
||||
|
||||
Time Limited Background Operations
|
||||
``````````````````````````````````
|
||||
|
||||
Long running operations can be backgrounded, and their status can be
|
||||
checked on later. The same job ID is given to the same task on all
|
||||
hosts, so you won't lose track. If you kick hosts and don't want
|
||||
to poll, it looks like this::
|
||||
|
||||
$ ansible all -B 3600 -a "/usr/bin/long_running_operation --do-stuff"
|
||||
|
||||
If you do decide you want to check on the job status later, you can::
|
||||
|
||||
$ ansible all -m async_status -a "jid=123456789"
|
||||
|
||||
Polling is built-in and looks like this::
|
||||
|
||||
$ ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff"
|
||||
|
||||
The above example says "run for 30 minutes max (``-B``: 30*60=1800),
|
||||
poll for status (``-P``) every 60 seconds".
|
||||
|
||||
Poll mode is smart so all jobs will be started before polling will begin on any machine.
|
||||
Be sure to use a high enough ``--forks`` value if you want to get all of your jobs started
|
||||
very quickly. After the time limit (in seconds) runs out (``-B``), the process on
|
||||
the remote nodes will be terminated.
|
||||
|
||||
Typically you'll be only be backgrounding long-running
|
||||
shell commands or software upgrades only. Backgrounding the copy module does not do a background file transfer. :doc:`playbooks` also support polling, and have a simplified syntax for this.
|
||||
|
||||
Limiting Selected Hosts
|
||||
```````````````````````
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
What hosts you select to manage can be additionally constrained by using the '--limit' parameter or
|
||||
by using 'batch' (or 'range') selectors.
|
||||
|
||||
As mentioned above, patterns can be strung together to select hosts in more than one group::
|
||||
|
||||
$ ansible webservers:dbservers -m command -a "/bin/foo xyz"
|
||||
|
||||
This is an "or" condition. If you want to further constrain the selection, use --limit, which
|
||||
also works with ``ansible-playbook``::
|
||||
|
||||
$ ansible webservers:dbservers -m command -a "/bin/foo xyz" --limit region
|
||||
|
||||
Now let's talk about range selection. Suppose you have 1000 servers in group 'datacenter', but only want to target one at a time. This is also easy::
|
||||
|
||||
$ ansible webservers[0-99] -m command -a "/bin/foo xyz"
|
||||
$ ansible webservers[100-199] -m command -a "/bin/foo xyz"
|
||||
|
||||
This will select the first 100, then the second 100, host entries in the webservers group. (It does not matter
|
||||
what their names or IP addresses are).
|
||||
|
||||
Both of these methods can be used at the same time, and ranges can also be passed to the --limit parameter.
|
||||
|
||||
Configuration & Defaults
|
||||
````````````````````````
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Ansible has an optional configuration file that can be used to tune settings and also eliminate the need to pass various command line flags. Ansible will look for the config file in the following order, using
|
||||
the first config file it finds present:
|
||||
|
||||
1. File specified by the ``ANSIBLE_CONFIG`` environment variable
|
||||
2. ``ansible.cfg`` in the current working directory. (version 0.8 and up)
|
||||
3. ``~/.ansible.cfg``
|
||||
4. ``/etc/ansible/ansible.cfg``
|
||||
|
||||
For those running from source, a sample configuration file lives in the examples/ directory. The RPM will install configuration into /etc/ansible/ansible.cfg automatically.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
A list of available modules
|
||||
:doc:`playbooks`
|
||||
Using ansible for configuration management & deployment
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
247
docsite/rst/gettingstarted.rst
Normal file
@ -0,0 +1,247 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Requirements
|
||||
````````````
|
||||
|
||||
Requirements for Ansible are extremely minimal.
|
||||
|
||||
Ansible is written for Python 2.6. If you are running Python 2.5 on an "Enterprise Linux" variant,
|
||||
your distribution can easily install 2.6 (see instructions in the next section). Newer versions
|
||||
of Linux and OS X should already have 2.6.
|
||||
|
||||
In additon to Python 2.6, you will want the following packages:
|
||||
|
||||
* ``paramiko``
|
||||
* ``PyYAML``
|
||||
* ``python-jinja2``
|
||||
|
||||
On the managed nodes, you only need Python 2.4 or later, but if you are are running less than Python 2.6 on them, you will
|
||||
also need:
|
||||
|
||||
* ``python-simplejson``
|
||||
|
||||
.. note::
|
||||
|
||||
Ansible's "raw" module (for executing commands in a quick and dirty
|
||||
way) and the copy module -- some of the most basic features in
|
||||
ansible -- don't even need that. So technically, you can use
|
||||
Ansible to install python-simplejson using the raw module, which
|
||||
then allows you to use everything else. (That's jumping ahead
|
||||
though.)
|
||||
|
||||
Python 2.6 EPEL instructions for RHEL and CentOS 5
|
||||
``````````````````````````````````````````````````
|
||||
|
||||
These distributions don't have Python 2.6 by default, but it is easily
|
||||
installable. If you have not already done so, `configure EPEL
|
||||
<http://fedoraproject.org/wiki/EPEL>`_
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ yum install python26 python26-PyYAML python26-paramiko python26-jinja2
|
||||
|
||||
Getting Ansible
|
||||
```````````````
|
||||
|
||||
If you are interested in using all the latest features, you may wish to keep up to date
|
||||
with the development branch of the git checkout. This also makes it easiest to contribute
|
||||
back to the project.
|
||||
|
||||
Instructions for installing from source are below.
|
||||
|
||||
Ansible's release cycles are about one month long. Due to this
|
||||
short release cycle, any bugs will generally be fixed in the next release versus maintaining
|
||||
backports on the stable branch.
|
||||
|
||||
You may also wish to follow the `Github project <https://github.com/ansible/ansible>`_ if
|
||||
you have a github account. This is also where we keep the issue tracker for sharing
|
||||
bugs and feature ideas.
|
||||
|
||||
|
||||
Running From Checkout
|
||||
+++++++++++++++++++++
|
||||
|
||||
Ansible is trivially easy to run from a checkout, root permissions are not required
|
||||
to use it:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone git://github.com/ansible/ansible.git
|
||||
$ cd ./ansible
|
||||
$ source ./hacking/env-setup
|
||||
|
||||
You can optionally specify an inventory file (see :doc:`patterns`) other than /etc/ansible/hosts:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ echo "127.0.0.1" > ~/ansible_hosts
|
||||
$ export ANSIBLE_HOSTS=~/ansible_hosts
|
||||
|
||||
Now let's test things:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ansible all -m ping --ask-pass
|
||||
|
||||
|
||||
Make Install
|
||||
++++++++++++
|
||||
|
||||
If you are not working from a distribution where Ansible is packaged yet, you can install Ansible
|
||||
using "make install". This is done through `python-distutils`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone git://github.com/ansible/ansible.git
|
||||
$ cd ./ansible
|
||||
$ sudo make install
|
||||
|
||||
|
||||
Via RPM
|
||||
+++++++
|
||||
|
||||
RPMs for the last Ansible release are available for `EPEL
|
||||
<http://fedoraproject.org/wiki/EPEL>`_ 6 and currently supported
|
||||
Fedora distributions. Ansible itself can manage earlier operating
|
||||
systems that contain python 2.4 or higher.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# install the epel-release RPM if needed on CentOS, RHEL, or Scientific Linux
|
||||
$ sudo yum install ansible
|
||||
|
||||
You can also use the ``make rpm`` command to build an RPM you can
|
||||
distribute and install:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone git://github.com/ansible/ansible.git
|
||||
$ cd ./ansible
|
||||
$ make rpm
|
||||
$ sudo rpm -Uvh ~/rpmbuild/ansible-*.noarch.rpm
|
||||
|
||||
Debian, Gentoo, Arch, Others
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
Ubuntu builds are available `in a PPA here <https://launchpad.net/~rquillo/+archive/ansible>`_
|
||||
|
||||
Debian/Ubuntu package recipes can also be built from the source checkout, run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ make debian
|
||||
|
||||
Gentoo eBuilds are available `on github here <https://github.com/uu/ubuilds>`_
|
||||
|
||||
An Arch PKGBUILD is available on `AUR <https://aur.archlinux.org/packages.php?ID=58621>`_
|
||||
If you have python3 installed on Arch, you probably want to symlink python to python2:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo ln -sf /usr/bin/python2 /usr/bin/python
|
||||
|
||||
If you would like to package Ansible for Homebrew, BSD, or others,
|
||||
please stop by the mailing list and say hi!
|
||||
|
||||
|
||||
Tagged Releases
|
||||
+++++++++++++++
|
||||
|
||||
Tagged releases are available as tar.gz files from the Ansible github
|
||||
project page:
|
||||
|
||||
* `Ansible/downloads <https://github.com/ansible/ansible/downloads>`_
|
||||
|
||||
Choosing Between Paramiko and Native SSH
|
||||
````````````````````````````````````````
|
||||
|
||||
By default, ansible uses paramiko to talk to managed nodes over SSH. Paramiko is fast, works
|
||||
very transparently, requires no configuration, and is a good choice for most users.
|
||||
However, it does not support some advanced SSH features that folks will want to use.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
If you want to leverage more advanced SSH features (such as Kerberized
|
||||
SSH or jump hosts), pass the flag "--connection=ssh" to any ansible
|
||||
command, or set the ANSIBLE_TRANSPORT environment variable to
|
||||
'ssh'. This will cause Ansible to use openssh tools instead.
|
||||
|
||||
If ANSIBLE_SSH_ARGS are not set, ansible will try to use some sensible ControlMaster options
|
||||
by default. You are free to override this environment variable, but should still pass ControlMaster
|
||||
options to ensure performance of this transport. With ControlMaster in use, both transports
|
||||
are roughly the same speed. Without CM, the binary ssh transport is signficantly slower.
|
||||
|
||||
If none of this makes sense to you, the default paramiko option is probably fine.
|
||||
|
||||
Your first commands
|
||||
```````````````````
|
||||
|
||||
Now that you've installed Ansible, it's time to test it.
|
||||
|
||||
Edit (or create) /etc/ansible/hosts and put one or more remote systems in it, for
|
||||
which you have your SSH key in ``authorized_keys``::
|
||||
|
||||
192.168.1.50
|
||||
aserver.example.org
|
||||
bserver.example.org
|
||||
|
||||
Set up SSH agent to avoid retyping passwords:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ssh-agent bash
|
||||
$ ssh-add ~/.ssh/id_rsa
|
||||
|
||||
(Depending on your setup, you may wish to ansible's --private-key-file option to specify a pem file instead)
|
||||
|
||||
Now ping all your nodes:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ansible all -m ping
|
||||
|
||||
In Ansible 0.7 and later, ansible will attempt to remote connect to the machines using your current
|
||||
user name, just like SSH would. In 0.6 and before, this actually defaults to 'root' (we liked the current
|
||||
user behavior better). To override the remote user name, just use the '-u' parameter.
|
||||
|
||||
If you would like to access sudo mode, there are also flags to do that:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# as bruce
|
||||
$ ansible all -m ping -u bruce
|
||||
# as bruce, sudoing to root
|
||||
$ ansible all -m ping -u bruce --sudo
|
||||
# as bruce, sudoing to batman
|
||||
$ ansible all -m ping -u bruce --sudo --sudo-user batman
|
||||
|
||||
Now run a live command on all of your nodes:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ansible all -a "/bin/echo hello"
|
||||
|
||||
Congratulations. You've just contacted your nodes with Ansible. It's
|
||||
now time to read some of the more real-world :doc:`examples`, and explore
|
||||
what you can do with different modules, as well as the Ansible
|
||||
:doc:`playbooks` language. Ansible is not just about running commands, it
|
||||
also has powerful configuration management and deployment features. There's more to
|
||||
explore, but you already have a fully working infrastructure!
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`examples`
|
||||
Examples of basic commands
|
||||
:doc:`playbooks`
|
||||
Learning ansible's configuration management language
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
68
docsite/rst/index.rst
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
|
||||
Ansible Documentation
|
||||
`````````````````````
|
||||
|
||||
This page contains documentation about how to use `Ansible <http://ansible.cc>`_.
|
||||
|
||||
Before we dive into playbooks, configuration management, deployment, and orchestration, learn how to get Ansible installed and some
|
||||
basic information. We'll go over how to execute ad-hoc commands in parallel across your nodes using /usr/bin/ansible. We'll also see
|
||||
what sort of modules are available in Ansible's core (though you can also write your own, which we'll also show later).
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
gettingstarted
|
||||
patterns
|
||||
examples
|
||||
modules
|
||||
|
||||
|
||||
Overview
|
||||
````````
|
||||
|
||||
.. image:: http://ansible.cc/img/ansible_arch.png
|
||||
:alt: ansible architecture diagram
|
||||
:width: 566px
|
||||
:height: 439px
|
||||
|
||||
Playbooks
|
||||
`````````
|
||||
|
||||
Playbooks are Ansible's orchestration language. At a basic level, playbooks can be used to manage configurations and deployments
|
||||
of remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions
|
||||
to other hosts, interacting with monitoring servers and load balancers along the way. You can start small and pick up more features
|
||||
over time as you need them. Playbooks are designed to be human-readable and are developed in a basic text language. There are multiple
|
||||
ways to organize playbooks and the files they include, and we'll offer up some suggestions on that and making the most out of Ansible.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
playbooks
|
||||
playbooks2
|
||||
bestpractices
|
||||
YAMLSyntax
|
||||
|
||||
|
||||
Developer Information
|
||||
`````````````````````
|
||||
|
||||
Learn how to build modules of your own in any language. Explore Ansible's Python API and write Python plugins to integrate
|
||||
with other solutions in your environment.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
moduledev
|
||||
|
||||
Miscellaneous
|
||||
`````````````
|
||||
|
||||
A list of some people using Ansible.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
who_uses_ansible
|
||||
|
||||
412
docsite/rst/moduledev.rst
Normal file
@ -0,0 +1,412 @@
|
||||
Module Development
|
||||
==================
|
||||
|
||||
Ansible modules are reusable units of magic that can be used by the Ansible API,
|
||||
or by the `ansible` or `ansible-playbook` programs.
|
||||
|
||||
Modules can be written in any language and are found in the path specified
|
||||
by `ANSIBLE_LIBRARY_PATH` or the ``--module-path`` command line option.
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
|
||||
Tutorial
|
||||
````````
|
||||
Let's build a module to get and set the system time. For starters, let's build
|
||||
a module that just outputs the current time.
|
||||
|
||||
We are going to use Python here but any language is possible. Only File I/O and outputing to standard
|
||||
out are required. So, bash, C++, clojure, Python, Ruby, whatever you want
|
||||
is fine.
|
||||
|
||||
Now Python Ansible modules contain some extremely powerful shortcuts (that all the core modules use)
|
||||
but first we are going to build a module the very hard way. The reason we do this is because modules
|
||||
written in any language OTHER than Python are going to have to do exactly this. We'll show the easy
|
||||
way later.
|
||||
|
||||
So, here's an example. You would never really need to build a module to set the system time,
|
||||
the 'command' module could already be used to do this. Though we're going to make one.
|
||||
|
||||
Reading the modules that come with ansible (linked above) is a great way to learn how to write
|
||||
modules. Keep in mind, though, that some modules in ansible's source tree are internalisms,
|
||||
so look at `service` or `yum`, and don't stare too close into things like `async_wrapper` or
|
||||
you'll turn to stone. Nobody ever executes async_wrapper directly.
|
||||
|
||||
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `time`::
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
date = str(datetime.datetime.now())
|
||||
print json.dumps({
|
||||
"time" : date
|
||||
})
|
||||
|
||||
Testing Modules
|
||||
```````````````
|
||||
|
||||
There's a useful test script in the source checkout for ansible::
|
||||
|
||||
git clone git@github.com:ansible/ansible.git
|
||||
chmod +x ansible/hacking/test-module
|
||||
|
||||
Let's run the script you just wrote with that::
|
||||
|
||||
ansible/hacking/test-module -m ./time
|
||||
|
||||
You should see output that looks something like this::
|
||||
|
||||
{u'time': u'2012-03-14 22:13:48.539183'}
|
||||
|
||||
If you did not, you might have a typo in your module, so recheck it and try again.
|
||||
|
||||
Reading Input
|
||||
`````````````
|
||||
|
||||
Let's modify the module to allow setting the current time. We'll do this by seeing
|
||||
if a key value pair in the form `time=<string>` is passed in to the module.
|
||||
|
||||
Ansible internally saves arguments to an arguments file. So we must read the file
|
||||
and parse it. The arguments file is just a string, so any form of arguments are legal.
|
||||
Here we'll do some basic parsing to treat the input as key=value.
|
||||
|
||||
The example usage we are trying to achieve to set the time is::
|
||||
|
||||
time time="March 14 22:10"
|
||||
|
||||
If no time parameter is set, we'll just leave the time as is and return the current time.
|
||||
|
||||
.. note:
|
||||
This is obviously an unrealistic idea for a module. You'd most likely just
|
||||
use the shell module. However, it probably makes a decent tutorial.
|
||||
|
||||
Let's look at the code. Read the comments as we'll explain as we go. Note that this
|
||||
is highly verbose because it's intended as an educational example. You can write modules
|
||||
a lot shorter than this::
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
# import some python modules that we'll use. These are all
|
||||
# available in Python's core
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
|
||||
# read the argument string from the arguments file
|
||||
args_file = sys.argv[1]
|
||||
args_data = file(args_file).read()
|
||||
|
||||
# for this module, we're going to do key=value style arguments
|
||||
# this is up to each module to decide what it wants, but all
|
||||
# core modules besides 'command' and 'shell' take key=value
|
||||
# so this is highly recommended
|
||||
|
||||
arguments = shlex.split(args_data)
|
||||
for arg in arguments:
|
||||
|
||||
# ignore any arguments without an equals in it
|
||||
if arg.find("=") != -1:
|
||||
|
||||
(key, value) = arg.split("=")
|
||||
|
||||
# if setting the time, the key 'time'
|
||||
# will contain the value we want to set the time to
|
||||
|
||||
if key == "time":
|
||||
|
||||
# now we'll affect the change. Many modules
|
||||
# will strive to be 'idempotent', meaning they
|
||||
# will only make changes when the desired state
|
||||
# expressed to the module does not match
|
||||
# the current state. Look at 'service'
|
||||
# or 'yum' in the main git tree for an example
|
||||
# of how that might look.
|
||||
|
||||
rc = os.system("date -s \"%s\"" % value)
|
||||
|
||||
# always handle all possible errors
|
||||
#
|
||||
# when returning a failure, include 'failed'
|
||||
# in the return data, and explain the failure
|
||||
# in 'msg'. Both of these conventions are
|
||||
# required however additional keys and values
|
||||
# can be added.
|
||||
|
||||
if rc != 0:
|
||||
print json.dumps({
|
||||
"failed" : True,
|
||||
"msg" : "failed setting the time"
|
||||
})
|
||||
sys.exit(1)
|
||||
|
||||
# when things do not fail, we do not
|
||||
# have any restrictions on what kinds of
|
||||
# data are returned, but it's always a
|
||||
# good idea to include whether or not
|
||||
# a change was made, as that will allow
|
||||
# notifiers to be used in playbooks.
|
||||
|
||||
date = str(datetime.datetime.now())
|
||||
print json.dumps({
|
||||
"time" : date,
|
||||
"changed" : True
|
||||
})
|
||||
sys.exit(0)
|
||||
|
||||
# if no parameters are sent, the module may or
|
||||
# may not error out, this one will just
|
||||
# return the time
|
||||
|
||||
date = str(datetime.datetime.now())
|
||||
print json.dumps({
|
||||
"time" : date
|
||||
})
|
||||
|
||||
Let's test that module::
|
||||
|
||||
ansible/hacking/test-module -m ./time -a time=\"March 14 12:23\"
|
||||
|
||||
This should return something like::
|
||||
|
||||
{"changed": True, "time": "2012-03-14 12:23:00.000307"}
|
||||
|
||||
Module Provided 'Facts'
|
||||
```````````````````````
|
||||
|
||||
The 'setup' module that ships with Ansible provides many variables about a system that can be used in playbooks
|
||||
and templates. However, it's possible to also add your own facts without modifying the system module. To do
|
||||
this, just have the module return a `ansible_facts` key, like so, along with other return data::
|
||||
|
||||
{
|
||||
"changed" : True,
|
||||
"rc" : 5,
|
||||
"ansible_facts" : {
|
||||
"leptons" : 5000
|
||||
"colors" : {
|
||||
"red" : "FF0000",
|
||||
"white" : "FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
These 'facts' will be available to all statements called after that module (but not before) in the playbook.
|
||||
A good idea might be make a module called 'site_facts' and always call it at the top of each playbook, though
|
||||
we're always open to improving the selection of core facts in Ansible as well.
|
||||
|
||||
Common Module Boilerplate
|
||||
`````````````````````````
|
||||
|
||||
As mentioned, if you are writing a module in Python, there are some very powerful shortcuts you can use.
|
||||
Modules are still transferred as one file, but an arguments file is no longer needed, so these are not
|
||||
only shorter in terms of code, they are actually FASTER in terms of execution time.
|
||||
|
||||
Rather than mention these here, the best way to learn is to read some of the `source of the modules <https://github.com/ansible/ansible/tree/devel/library>`_ that come with Ansible.
|
||||
|
||||
The 'group' and 'user' modules are reasonably non-trival and showcase what this looks like.
|
||||
|
||||
Key parts include always ending the module file with::
|
||||
|
||||
# include magic from lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
||||
|
||||
And instantiating the module class like::
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
state = dict(default='present', choices=['present', 'absent']),
|
||||
name = dict(required=True),
|
||||
enabled = dict(required=True, choices=BOOLEANS),
|
||||
something = dict(aliases=['whatever'])
|
||||
)
|
||||
)
|
||||
|
||||
The AnsibleModule provides lots of common code for handling returns, parses your arguments
|
||||
for you, and allows you to check inputs.
|
||||
|
||||
Successful returns are made like this::
|
||||
|
||||
module.exit_json(changed=True, something_else=12345)
|
||||
|
||||
And failures are just as simple (where 'msg' is a required parameter to explain the error)::
|
||||
|
||||
module.fail_json(msg="Something fatal happened")
|
||||
|
||||
There are also other useful functions in the module class, such as module.md5(path). See
|
||||
lib/ansible/module_common.py in the source checkout for implementation details.
|
||||
|
||||
Again, modules developed this way are best tested with the hacking/test-module script in the git
|
||||
source checkout. Because of the magic involved, this is really the only way the scripts
|
||||
can function outside of Ansible.
|
||||
|
||||
If submitting a module to ansible's core code, which we encourage, use of the AnsibleModule
|
||||
class is required.
|
||||
|
||||
Common Pitfalls
|
||||
```````````````
|
||||
|
||||
You should also never do this in a module::
|
||||
|
||||
print "some status message"
|
||||
|
||||
Because the output is supposed to be valid JSON. Except that's not quite true,
|
||||
but we'll get to that later.
|
||||
|
||||
Modules must not output anything on standard error, because the system will merge
|
||||
standard out with standard error and prevent the JSON from parsing. Capturing standard
|
||||
error and returning it as a variable in the JSON on standard out is fine, and is, in fact,
|
||||
how the command module is implemented.
|
||||
|
||||
If a module returns stderr or otherwise fails to produce valid JSON, the actual output
|
||||
will still be shown in Ansible, but the command will not succeed.
|
||||
|
||||
Always use the hacking/test-module script when developing modules and it will warn
|
||||
you about these kind of things.
|
||||
|
||||
Conventions/Recomendations
|
||||
``````````````````````````
|
||||
|
||||
As a reminder from the example code above, here are some basic conventions
|
||||
and guidelines:
|
||||
|
||||
* If the module is addressing an object, the parameter for that object should be called 'name' whenever possible, or accept 'name' as an alias.
|
||||
|
||||
* If you have a company module that returns facts specific to your installations, a good name for this module is `site_facts`.
|
||||
|
||||
* Modules accepting boolean status should generally accept 'yes', 'no', 'true', 'false', or anything else a user may likely throw at them. The AnsibleModule common code supports this with "choices=BOOLEANS" and a module.boolean(value) casting function.
|
||||
|
||||
* Include a minimum of dependencies if possible. If there are dependencies, document them at the top of the module file, and have the module raise JSON error messages when the import fails.
|
||||
|
||||
* Modules must be self contained in one file to be auto-transferred by ansible.
|
||||
|
||||
* If packaging modules in an RPM, they only need to be installed on the control machine and should be dropped into /usr/share/ansible. This is entirely optional and up to you.
|
||||
|
||||
* Modules should return JSON or key=value results all on one line. JSON is best if you can do JSON. All return types must be hashes (dictionaries) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary.
|
||||
|
||||
* In the event of failure, a key of 'failed' should be included, along with a string explanation in 'msg'. Modules that raise tracebacks (stacktraces) are generally considered 'poor' modules, though Ansible can deal with these returns and will automatically convert anything unparseable into a failed result. If you are using the AnsibleModule common Python code, the 'failed' element will be included for you automatically when you call 'fail_json'.
|
||||
|
||||
* Return codes from modules are not actually not signficant, but continue on with 0=success and non-zero=failure for reasons of future proofing.
|
||||
|
||||
* As results from many hosts will be aggregrated at once, modules should return only relevant output. Returning the entire contents of a log file is generally bad form.
|
||||
|
||||
Shorthand Vs JSON
|
||||
`````````````````
|
||||
|
||||
To make it easier to write modules in bash and in cases where a JSON
|
||||
module might not be available, it is acceptable for a module to return
|
||||
key=value output all on one line, like this. The Ansible parser
|
||||
will know what to do::
|
||||
|
||||
somekey=1 somevalue=2 rc=3 favcolor=red
|
||||
|
||||
If you're writing a module in Python or Ruby or whatever, though, returning
|
||||
JSON is probably the simplest way to go.
|
||||
|
||||
|
||||
Documenting Your Module
|
||||
```````````````````````
|
||||
|
||||
All modules included in the CORE distribution must have a
|
||||
``DOCUMENTATION`` string. This string MUST be a valid YAML document
|
||||
which conforms to the schema defined below. You may find it easier to
|
||||
start writing your ``DOCUMENTATION`` string in an editor with YAML
|
||||
syntax highlighting before you include it in your Python file.
|
||||
|
||||
Example
|
||||
+++++++
|
||||
|
||||
Here's a correctly formatted YAML document we could use for a
|
||||
``DOCUMENTATION`` string:
|
||||
|
||||
.. literalinclude:: ../DOCUMENTATION.yaml
|
||||
|
||||
This is available in the 'examples/' directory of the of the Ansible
|
||||
github repository, and you can have that generated with
|
||||
``./hacking/module_formatter.py -G``. You can copy it into your module and use
|
||||
it as a starting point when writing your own docs.
|
||||
|
||||
Include it in your module file like this::
|
||||
|
||||
#!/usr/bin/env python
|
||||
# Copyright header....
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: modulename
|
||||
short_description: This is a sentence describing the module
|
||||
# ... snip ...
|
||||
examples:
|
||||
- code: modulename opt1=arg1 opt2=arg2
|
||||
description: Optional words describing this example
|
||||
'''
|
||||
|
||||
Building & Testing
|
||||
++++++++++++++++++
|
||||
|
||||
Put your completed module file into the 'library' directory and then
|
||||
run the command: ``make webdocs``. The new 'modules.html' file will be
|
||||
built and appear in the 'docsite/' directory.
|
||||
|
||||
You can also test-build your docs one-by-one using the
|
||||
``module_formatter.py`` script:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ./hacking/module_formatter.py -t man -M library/ -m git > ansible-git.1
|
||||
$ man ./ansible-git.1
|
||||
|
||||
This will build a manpage for the git module, and look in the
|
||||
'library/' directory for the module source. To see all the other
|
||||
output formats available:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ./hacking/module_formatter.py -t --help
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
If you're having a problem with the syntax of your YAML you can
|
||||
validate it on the `YAML Lint <http://www.yamllint.com/>`_ website.
|
||||
|
||||
|
||||
Sharing Your Module
|
||||
```````````````````
|
||||
|
||||
If you think your module is generally useful to others, a good place to share it
|
||||
is in `Ansible Resources <https://github.com/ansible/ansible/tree/devel/contrib>`_. This is maintained
|
||||
as a simple repo with pointers to other github projects.
|
||||
|
||||
Contrib modules here can be implemented in a variety of languages.
|
||||
We would like to build up as many of these as possible in as many languages as possible.
|
||||
|
||||
`Ansible Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
|
||||
Getting Your Module Into Core
|
||||
`````````````````````````````
|
||||
|
||||
High-quality modules with minimal dependencies
|
||||
can be included in the core, but core modules (just due to the programming
|
||||
preferences of the developers) will need to be implemented in Python and use
|
||||
the AnsibleModule common code, and should generally use consistent arguments with the rest of
|
||||
the program. Stop by the mailing list to inquire about requirements.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
Learn about available modules
|
||||
`Ansible Resources <https://github.com/ansible/ansible/tree/devel/contrib>`_
|
||||
User contributed playbooks, modules, and articles
|
||||
`Github modules directory <https://github.com/ansible/ansible/tree/devel/library>`_
|
||||
Browse source of core modules
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
74
docsite/rst/modules.rst
Normal file
@ -0,0 +1,74 @@
|
||||
Ansible Modules
|
||||
===============
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Introduction
|
||||
````````````
|
||||
|
||||
|
||||
Ansible ships with a number of modules (called the 'module library')
|
||||
that can be executed directly on remote hosts or through :doc:`playbooks`.
|
||||
Users can also write their own modules. These modules can control system
|
||||
resources, like services, packages, or files (anything really), or
|
||||
handle executing system commands.
|
||||
|
||||
Let's review how we execute three different modules from the command line::
|
||||
|
||||
ansible webservers -m service -a "name=httpd state=running"
|
||||
ansible webservers -m ping
|
||||
ansible webservers -m command -a "/sbin/reboot -t now"
|
||||
|
||||
Each module supports taking arguments. Nearly all modules take ``key=value``
|
||||
arguments, space delimited. Some modules take no arguments, and the
|
||||
command/shell modules simply take the string of the command you want to run.
|
||||
|
||||
From playbooks, Ansible modules are executed in a very similar way::
|
||||
|
||||
- name: reboot the servers
|
||||
action: command /sbin/reboot -t now
|
||||
|
||||
All modules technically return JSON format data, though if you are using the
|
||||
command line or playbooks, you don't really need to know much about
|
||||
that. If you're writing your own module, you care, and this means you do
|
||||
not have to write modules in any particular language -- you get to choose.
|
||||
|
||||
Modules are `idempotent`, meaning they will seek to avoid changes to the system unless a change needs to be made. When using Ansible
|
||||
playbooks, these modules can trigger 'change events' in the form of notifying 'handlers'
|
||||
to run additional tasks.
|
||||
|
||||
Let's see what's available in the Ansible module library, out of the box:
|
||||
|
||||
|
||||
.. include:: modules/_list.rst
|
||||
|
||||
|
||||
Additional Contrib Modules
|
||||
``````````````````````````
|
||||
|
||||
In addition to the following built-in modules, community modules are available at `Ansible Resources <http://github.com/ansible/ansible-resources>`_.
|
||||
|
||||
Writing your own modules
|
||||
````````````````````````
|
||||
|
||||
See :doc:`moduledev`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Ansible Resources (Contrib) <https://github.com/ansible/ansible-resources>`_
|
||||
User contributed playbooks, modules, and articles
|
||||
:doc:`examples`
|
||||
Examples of using modules in /usr/bin/ansible
|
||||
:doc:`playbooks`
|
||||
Examples of using modules with /usr/bin/ansible-playbook
|
||||
:doc:`moduledev`
|
||||
How to write your own modules
|
||||
:doc:`api`
|
||||
Examples of using modules with the Python API
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
0
docsite/rst/modules/.gitdir
Normal file
215
docsite/rst/patterns.rst
Normal file
@ -0,0 +1,215 @@
|
||||
.. _patterns:
|
||||
|
||||
Inventory & Patterns
|
||||
====================
|
||||
|
||||
Ansible works against multiple systems in your infrastructure at the
|
||||
same time. It does this by selecting portions of systems listed in
|
||||
Ansible's inventory file, which defaults to /etc/ansible/hosts.
|
||||
|
||||
.. contents:: `Table of contents`
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
.. _inventoryformat:
|
||||
|
||||
Hosts and Groups
|
||||
++++++++++++++++
|
||||
|
||||
The format for /etc/ansible/hosts is an INI format and looks like this::
|
||||
|
||||
mail.example.com
|
||||
|
||||
[webservers]
|
||||
foo.example.com
|
||||
bar.example.com
|
||||
|
||||
[dbservers]
|
||||
one.example.com
|
||||
two.example.com
|
||||
three.example.com
|
||||
|
||||
The things in brackets are group names. You don't have to have them,
|
||||
but they are useful.
|
||||
|
||||
If you have hosts that run on non-standard SSH ports you can put the port number
|
||||
after the hostname with a colon.
|
||||
|
||||
four.example.com:5309
|
||||
|
||||
In 0.6 and later, if you have a lot of hosts following similar patterns you can do this rather than listing each hostname::
|
||||
|
||||
[webservers]
|
||||
www[01:50].example.com
|
||||
|
||||
Leading zeros can be included or removed, as desired, and the ranges are inclusive.
|
||||
|
||||
Selecting Targets
|
||||
+++++++++++++++++
|
||||
|
||||
We'll go over how to use the command line in :doc:`examples` section, however, basically it looks like this::
|
||||
|
||||
ansible <pattern_goes_here> -m <module_name> -a <arguments>
|
||||
|
||||
Such as::
|
||||
|
||||
ansible webservers -m service -a "name=httpd state=restarted"
|
||||
|
||||
Within :doc:`playbooks`, these patterns can be used for even greater purposes.
|
||||
|
||||
Anyway, to use Ansible, you'll first need to know how to tell Ansible which hosts in your inventory file to talk to.
|
||||
This is done by designating particular host names or groups of hosts.
|
||||
|
||||
The following patterns target all hosts in the inventory file::
|
||||
|
||||
all
|
||||
*
|
||||
|
||||
Basically 'all' is an alias for '*'. It is also possible to address a specific host or hosts::
|
||||
|
||||
one.example.com
|
||||
one.example.com:two.example.com
|
||||
192.168.1.50
|
||||
192.168.1.*
|
||||
|
||||
The following patterns address one or more groups, which are denoted
|
||||
with the aforementioned bracket headers in the inventory file::
|
||||
|
||||
webservers
|
||||
webservers:dbservers
|
||||
|
||||
You can exclude groups as well, for instance, all webservers not in Phoenix::
|
||||
|
||||
webservers:!phoenix
|
||||
|
||||
Individual host names (or IPs), but not groups, can also be referenced using
|
||||
wildcards::
|
||||
|
||||
*.example.com
|
||||
*.com
|
||||
|
||||
It's also ok to mix wildcard patterns and groups at the same time::
|
||||
|
||||
one*.com:dbservers
|
||||
|
||||
Easy enough. See :doc:`examples` and then :doc:`playbooks` for how to do things to selected hosts.
|
||||
|
||||
Host Variables
|
||||
++++++++++++++
|
||||
|
||||
It is easy to assign variables to hosts that will be used later in playbooks::
|
||||
|
||||
[atlanta]
|
||||
host1 http_port=80 maxRequestsPerChild=808
|
||||
host2 http_port=303 maxRequestsPerChild=909
|
||||
|
||||
|
||||
Group Variables
|
||||
+++++++++++++++
|
||||
|
||||
Variables can also be applied to an entire group at once::
|
||||
|
||||
[atlanta]
|
||||
host1
|
||||
host2
|
||||
|
||||
[atlanta:vars]
|
||||
ntp_server=ntp.atlanta.example.com
|
||||
proxy=proxy.atlanta.example.com
|
||||
|
||||
Groups of Groups, and Group Variables
|
||||
+++++++++++++++++++++++++++++++++++++
|
||||
|
||||
It is also possible to make groups of groups and assign
|
||||
variables to groups. These variables can be used by /usr/bin/ansible-playbook, but not
|
||||
/usr/bin/ansible::
|
||||
|
||||
[atlanta]
|
||||
host1
|
||||
host2
|
||||
|
||||
[raleigh]
|
||||
host2
|
||||
host3
|
||||
|
||||
[southeast:children]
|
||||
atlanta
|
||||
raleigh
|
||||
|
||||
[southeast:vars]
|
||||
some_server=foo.southeast.example.com
|
||||
halon_system_timeout=30
|
||||
self_destruct_countdown=60
|
||||
escape_pods=2
|
||||
|
||||
[usa:children]
|
||||
southeast
|
||||
northeast
|
||||
southwest
|
||||
southeast
|
||||
|
||||
If you need to store lists or hash data, or prefer to keep host and group specific variables
|
||||
seperate from the inventory file, see the next section.
|
||||
|
||||
Splitting Out Host and Group Specific Data
|
||||
++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
In addition to the storing variables directly in the INI file, host
|
||||
and group variables can be stored in individual files relative to the
|
||||
inventory file. These variable files are in YAML format.
|
||||
|
||||
Assuming the inventory file path is::
|
||||
|
||||
/etc/ansible/hosts
|
||||
|
||||
If the host is named 'foosball', and in groups 'raleigh' and 'webservers', variables
|
||||
in YAML files at the following locations will be made available to the host::
|
||||
|
||||
/etc/ansible/group_vars/raleigh
|
||||
/etc/ansible/group_vars/webservers
|
||||
/etc/ansible/host_vars/foosball
|
||||
|
||||
For instance, suppose you have hosts grouped by datacenter, and each datacenter
|
||||
uses some different servers. The data in the groupfile '/etc/ansible/group_vars/raleigh' for
|
||||
the 'raleigh' group might look like::
|
||||
|
||||
---
|
||||
ntp_server: acme.example.org
|
||||
database_server: storage.example.org
|
||||
|
||||
It is ok if these files do not exist, this is an optional feature.
|
||||
|
||||
Tip: Keeping your inventory file and variables in a git repo (or other version control)
|
||||
is an excellent way to track changes to your inventory and host variables.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
If you ever have two python interpreters on a system, set a
|
||||
variable called 'ansible_python_interpreter' to the Python
|
||||
interpreter path you would like to use.
|
||||
|
||||
YAML Inventory
|
||||
++++++++++++++
|
||||
|
||||
.. deprecated:: 0.7
|
||||
|
||||
Ansible's YAML inventory format is deprecated and will be removed in
|
||||
Ansible 0.7. Ansible 0.6 includes a `conversion script
|
||||
<https://github.com/ansible/ansible/blob/devel/examples/scripts/yaml_to_ini.py>`_.
|
||||
|
||||
Usage::
|
||||
|
||||
yaml_to_ini.py /etc/ansible/hosts
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`examples`
|
||||
Examples of basic commands
|
||||
:doc:`playbooks`
|
||||
Learning ansible's configuration management language
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
436
docsite/rst/playbooks.rst
Normal file
@ -0,0 +1,436 @@
|
||||
Playbooks
|
||||
=========
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Introduction
|
||||
````````````
|
||||
|
||||
Playbooks are a completely different way to use ansible than in task execution mode, and are
|
||||
particularly powerful. Simply put, playbooks are the basis for a really simple
|
||||
configuration management and multi-machine deployment system,
|
||||
unlike any that already exist, and one that is very well suited to deploying complex applications.
|
||||
|
||||
Playbooks can declare configurations, but they can also orchestrate steps of
|
||||
any manual ordered process, even as different steps must bounce back and forth
|
||||
between sets of machines in particular orders. They can launch tasks
|
||||
synchronously or asynchronously.
|
||||
|
||||
While you might run the main /usr/bin/ansible program for ad-hoc
|
||||
tasks, playbooks are more likely to be kept in source control and used
|
||||
to push out your configuration or assure the configurations of your
|
||||
remote systems are in spec.
|
||||
|
||||
Let's dive in and see how they work. As you go, you may wish to open
|
||||
the `github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_ in
|
||||
another tab, so you can apply the theory to what things look like in practice.
|
||||
|
||||
Playbook Language Example
|
||||
`````````````````````````
|
||||
|
||||
Playbooks are expressed in YAML format and have a minimum of syntax.
|
||||
Each playbook is composed of one or more 'plays' in a list.
|
||||
|
||||
The goal of a play is map a group of hosts to some well defined roles, represented by
|
||||
things ansible called tasks. At the basic level, a task is nothing more than a call
|
||||
to an ansible module, which you should have learned about in earlier chapters.
|
||||
|
||||
By composing a playbook of multiple 'plays', it is possible to
|
||||
orchestrate multi-machine deployments, running certain steps on all
|
||||
machines in the webservers group, then certain steps on the database
|
||||
server group, then more commands back on the webservers group, etc.
|
||||
|
||||
For starters, here's a playbook that contains just one play::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
vars:
|
||||
http_port: 80
|
||||
max_clients: 200
|
||||
user: root
|
||||
tasks:
|
||||
- name: ensure apache is at the latest version
|
||||
action: yum pkg=httpd state=latest
|
||||
- name: write the apache config file
|
||||
action: template src=/srv/httpd.j2 dest=/etc/httpd.conf
|
||||
notify:
|
||||
- restart apache
|
||||
- name: ensure apache is running
|
||||
action: service name=httpd state=started
|
||||
handlers:
|
||||
- name: restart apache
|
||||
action: service name=httpd state=restarted
|
||||
|
||||
Below, we'll break down what the various features of the playbook language are.
|
||||
|
||||
Basics
|
||||
``````
|
||||
|
||||
Hosts and Users
|
||||
+++++++++++++++
|
||||
|
||||
For each play in a playbook, you get to choose which machines in your infrastructure
|
||||
to target and what remote user to complete the steps (called tasks) as.
|
||||
|
||||
The `hosts` line is a list of one or more groups or host patterns,
|
||||
separated by colons, as described in the :ref:`patterns`
|
||||
documentation. The `user` is just the name of the user account::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
user: root
|
||||
|
||||
|
||||
Support for running things from sudo is also available::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
user: yourname
|
||||
sudo: True
|
||||
|
||||
You can also login as you, and then sudo to different users than root::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
user: yourname
|
||||
sudo: True
|
||||
sudo_user: postgres
|
||||
|
||||
If you need to specify a password to sudo, run `ansible-playbook` with ``--ask-sudo-pass`` (`-K`).
|
||||
If you run a sudo playbook and the playbook seems to hang, it's probably stuck at the sudo prompt.
|
||||
Just `Control-C` to kill it and run it again with `-K`.
|
||||
|
||||
.. important::
|
||||
|
||||
When using `sudo_user` to a user other than root, the module
|
||||
arguments are briefly written into a random tempfile in /tmp.
|
||||
These are deleted immediately after the command is executed. This
|
||||
only occurs when sudoing from a user like 'bob' to 'timmy', not
|
||||
when going from 'bob' to 'root', or logging in directly as 'bob' or
|
||||
'root'. If this concerns you that this data is briefly readable
|
||||
(not writeable), avoid transferring uncrypted passwords with
|
||||
`sudo_user` set. In other cases, '/tmp' is not used and this does
|
||||
not come into play. Ansible also takes care to not log password
|
||||
parameters.
|
||||
|
||||
Vars section
|
||||
++++++++++++
|
||||
|
||||
The `vars` section contains a list of variables and values that can be used in the plays, like this::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
user: root
|
||||
vars:
|
||||
http_port: 80
|
||||
van_halen_port: 5150
|
||||
other: 'magic'
|
||||
|
||||
These variables can be used later in the playbook like this::
|
||||
|
||||
$varname or ${varname}
|
||||
|
||||
The later is useful in the event you need to do something like ${other}_some_string.
|
||||
|
||||
Inside templates, the full power of the `Jinja2 <http://jinja.pocoo.org/docs/>`_ templating language is also available, which looks like this::
|
||||
|
||||
{{ varname }}
|
||||
|
||||
The Jinja2 documentation provides information about how to construct loops and conditionals for those
|
||||
who which to use more advanced templating. This is optional and the $varname format still works in template
|
||||
files.
|
||||
|
||||
If there are discovered variables about the system, called 'facts', these variables bubble up back into the
|
||||
playbook, and can be used on each system just like explicitly set variables. Ansible provides several
|
||||
of these, prefixed with 'ansible', and are documented under 'setup' in the module documentation. Additionally,
|
||||
facts can be gathered by ohai and facter if they are installed. Facter variables are prefixed with ``facter_`` and Ohai
|
||||
variables are prefixed with ``ohai_``.
|
||||
|
||||
So for instance, if I wanted
|
||||
to write the hostname into the /etc/motd file, I could say::
|
||||
|
||||
- name: write the motd
|
||||
action: template src=/srv/templates/motd.j2 dest=/etc/motd
|
||||
|
||||
And in /srv/templates/motd.j2::
|
||||
|
||||
You are logged into {{ facter_hostname }}
|
||||
|
||||
But we're getting ahead of ourselves. Let's talk about tasks.
|
||||
|
||||
Tasks list
|
||||
++++++++++
|
||||
|
||||
Each play contains a list of tasks. Tasks are executed in order, one
|
||||
at a time, against all machines matched by the host pattern,
|
||||
before moving on to the next task. It is important to understand that, within a play,
|
||||
all hosts are going to get the same task directives. It is the purpose of a play to map
|
||||
a selection of hosts to tasks.
|
||||
|
||||
When running the playbook, which runs top to bottom, hosts with failed tasks are
|
||||
taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun.
|
||||
|
||||
The goal of each task is to execute a module, with very specific arguments.
|
||||
Variables, as mentioned above, can be used in arguments to modules.
|
||||
|
||||
Modules are 'idempotent', meaning if you run them
|
||||
again, they will make the changes they are told to make to bring the
|
||||
system to the desired state. This makes it very safe to rerun
|
||||
the same playbook multiple times. They won't change things
|
||||
unless they have to change things.
|
||||
|
||||
The `command` and `shell` modules will typically rerun the same command again,
|
||||
which is totally ok if the command is something like
|
||||
'chmod' or 'setsebool', etc. Though there is a 'creates' flag available which can
|
||||
be used to make these modules also idempotent.
|
||||
|
||||
Every task should have a `name`, which is included in the output from
|
||||
running the playbook. This is output for humans, so it is
|
||||
nice to have reasonably good descriptions of each task step. If the name
|
||||
is not provided though, the string fed to 'action' will be used for
|
||||
output.
|
||||
|
||||
Here is what a basic task looks like, as with most modules,
|
||||
the service module takes key=value arguments::
|
||||
|
||||
tasks:
|
||||
- name: make sure apache is running
|
||||
action: service name=httpd state=running
|
||||
|
||||
The `command` and `shell` modules are the one modules that just takes a list
|
||||
of arguments, and don't use the key=value form. This makes
|
||||
them work just like you would expect. Simple::
|
||||
|
||||
tasks:
|
||||
- name: disable selinux
|
||||
action: command /sbin/setenforce 0
|
||||
|
||||
The command and shell module care about return codes, so if you have a command
|
||||
who's successful exit code is not zero, you may wish to do this::
|
||||
|
||||
tasks:
|
||||
- name: run this command and ignore the result
|
||||
action: shell /usr/bin/somecommand && /bin/true
|
||||
|
||||
If the action line is getting too long for comfort you can break it on
|
||||
a space and indent any continuation lines::
|
||||
|
||||
tasks:
|
||||
- name: Copy ansible inventory file to client
|
||||
action: copy src=/etc/ansible/hosts dest=/etc/ansible/hosts
|
||||
owner=root group=root mode=0644
|
||||
|
||||
Variables can be used in action lines. Suppose you defined
|
||||
a variable called 'vhost' in the 'vars' section, you could do this::
|
||||
|
||||
tasks:
|
||||
- name: create a virtual host file for $vhost
|
||||
action: template src=somefile.j2 dest=/etc/httpd/conf.d/$vhost
|
||||
|
||||
Those same variables are usable in templates, which we'll get to later.
|
||||
|
||||
Now in a very basic playbook all the tasks will be listed directly in that play, though it will usually
|
||||
make more sense to break up tasks using the 'include:' directive. We'll show that a bit later.
|
||||
|
||||
Action Shorthand
|
||||
````````````````
|
||||
|
||||
.. versionadded: 0.8
|
||||
|
||||
Rather than listing out the explicit word, "action:", like so::
|
||||
|
||||
action: template src=templates/foo.j2 dest=/etc/foo.conf
|
||||
|
||||
It is also possible to say:
|
||||
|
||||
template: src=templates/foo.j2 dest=/etc/foo.conf
|
||||
|
||||
The name of the module is simply followed by a colon and the arguments to that module. We think this is a lot more intuitive.
|
||||
Our documentation has not converted over to this new format just yet as many users may still be using older versions.
|
||||
You'll be able to use both formats forever.
|
||||
|
||||
Running Operations On Change
|
||||
````````````````````````````
|
||||
|
||||
As we've mentioned, modules are written to be 'idempotent' and can relay when
|
||||
they have made a change on the remote system. Playbooks recognize this and
|
||||
have a basic event system that can be used to respond to change.
|
||||
|
||||
These 'notify' actions are triggered at the end of each 'play' in a playbook, and
|
||||
trigger only once each. For instance, multiple resources may indicate
|
||||
that apache needs to be restarted, but apache will only be bounced once.
|
||||
|
||||
Here's an example of restarting two services when the contents of a file
|
||||
change, but only if the file changes::
|
||||
|
||||
- name: template configuration file
|
||||
action: template src=template.j2 dest=/etc/foo.conf
|
||||
notify:
|
||||
- restart memcached
|
||||
- restart apache
|
||||
|
||||
The things listed in the 'notify' section of a task are called
|
||||
handlers.
|
||||
|
||||
Handlers are lists of tasks, not really any different from regular
|
||||
tasks, that are referenced by name. Handlers are what notifiers
|
||||
notify. If nothing notifies a handler, it will not run. Regardless
|
||||
of how many things notify a handler, it will run only once, after all
|
||||
of the tasks complete in a particular play.
|
||||
|
||||
Here's an example handlers section::
|
||||
|
||||
handlers:
|
||||
- name: restart memcached
|
||||
action: service name=memcached state=restarted
|
||||
- name: restart apache
|
||||
action: service name=apache state=restarted
|
||||
|
||||
Handlers are best used to restart services and trigger reboots. You probably
|
||||
won't need them for much else.
|
||||
|
||||
.. note::
|
||||
Notify handlers are always run in the order written.
|
||||
|
||||
|
||||
Include Files And Encouraging Reuse
|
||||
```````````````````````````````````
|
||||
|
||||
Suppose you want to reuse lists of tasks between plays or playbooks. You can use
|
||||
include files to do this. Use of included task lists is a great way to define a role
|
||||
that system is going to fulfill. Remember, the goal of a play in a playbook is to map
|
||||
a group of systems into multiple roles. Let's see what this looks like...
|
||||
|
||||
A task include file simply contains a flat list of tasks, like so::
|
||||
|
||||
---
|
||||
# possibly saved as tasks/foo.yml
|
||||
- name: placeholder foo
|
||||
action: command /bin/foo
|
||||
- name: placeholder bar
|
||||
action: command /bin/bar
|
||||
|
||||
Include directives look like this, and can be mixed in with regular tasks in a playbook::
|
||||
|
||||
tasks:
|
||||
- include: tasks/foo.yml
|
||||
|
||||
You can also pass variables into includes. We call this a 'parameterized include'.
|
||||
|
||||
For instance, if deploying multiple wordpress instances, I could
|
||||
contain all of my wordpress tasks in a single wordpress.yml file, and use it like so::
|
||||
|
||||
tasks:
|
||||
- include: wordpress.yml user=timmy
|
||||
- include: wordpress.yml user=alice
|
||||
- include: wordpress.yml user=bob
|
||||
|
||||
Variables passed in can then be used in the included files. You can reference them like this::
|
||||
|
||||
$user
|
||||
|
||||
(In addition to the explicitly passed in parameters, all variables from
|
||||
the vars section are also available for use here as well.)
|
||||
|
||||
.. note::
|
||||
Task include statements are only usable one-level deep.
|
||||
This means task includes can not include other
|
||||
task includes. This may change in a later release.
|
||||
|
||||
Includes can also be used in the 'handlers' section, for instance, if you
|
||||
want to define how to restart apache, you only have to do that once for all
|
||||
of your playbooks. You might make a handlers.yml that looks like::
|
||||
|
||||
---
|
||||
# this might be in a file like handlers/handlers.yml
|
||||
- name: restart apache
|
||||
action: service name=apache state=restarted
|
||||
|
||||
And in your main playbook file, just include it like so, at the bottom
|
||||
of a play::
|
||||
|
||||
handlers:
|
||||
- include: handlers/handlers.yml
|
||||
|
||||
You can mix in includes along with your regular non-included tasks and handlers.
|
||||
|
||||
Includes can also be used to import one playbook file into another. This allows
|
||||
you to define a top-level playbook that is composed of other playbooks.
|
||||
|
||||
For example::
|
||||
|
||||
- name: this is a play at the top level of a file
|
||||
hosts: all
|
||||
user: root
|
||||
tasks:
|
||||
- name: say hi
|
||||
tags: foo
|
||||
action: shell echo "hi..."
|
||||
|
||||
- include: load_balancers.yml
|
||||
- include: webservers.yml
|
||||
- include: dbservers.yml
|
||||
|
||||
Note that you cannot do variable substitution when including one playbook
|
||||
inside another.
|
||||
|
||||
.. note::
|
||||
|
||||
You can not conditionally path the location to an include file,
|
||||
like you can with 'vars_files'. If you find yourself needing to do
|
||||
this, consider how you can restructure your playbook to be more
|
||||
class/role oriented. This is to say you cannot use a 'fact' to
|
||||
decide what include file to use. All hosts contained within the
|
||||
play are going to get the same tasks. ('only_if' provides some
|
||||
ability for hosts to conditionally skip tasks).
|
||||
|
||||
Executing A Playbook
|
||||
````````````````````
|
||||
|
||||
Now that you've learned playbook syntax, how do you run a playbook? It's simple.
|
||||
Let's run a playbook using a parallelism level of 10::
|
||||
|
||||
ansible-playbook playbook.yml -f 10
|
||||
|
||||
Tips and Tricks
|
||||
```````````````
|
||||
|
||||
Look at the bottom of the playbook execution for a summary of the nodes that were executed
|
||||
and how they performed. General failures and fatal "unreachable" communication attempts are
|
||||
kept seperate in the counts.
|
||||
|
||||
If you ever want to see detailed output from successful modules as well as unsuccessful ones,
|
||||
use the '--verbose' flag. This is available in Ansible 0.5 and later.
|
||||
|
||||
Also, in version 0.5 and later, Ansible playbook output is vastly upgraded if the cowsay
|
||||
package is installed. Try it!
|
||||
|
||||
In version 0.7 and later, to see what hosts would be affected by a playbook before you run it, you
|
||||
can do this::
|
||||
|
||||
ansible-playbook playbook.yml --list-hosts.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`YAMLSyntax`
|
||||
Learn about YAML syntax
|
||||
:doc:`playbooks`
|
||||
Review the basic Playbook language features
|
||||
:doc:`playbooks2`
|
||||
Learn about Advanced Playbook Features
|
||||
:doc:`bestpractices`
|
||||
Various tips about managing playbooks in the real world
|
||||
:doc:`modules`
|
||||
Learn about available modules
|
||||
:doc:`moduledev`
|
||||
Learn how to extend Ansible by writing your own modules
|
||||
:doc:`patterns`
|
||||
Learn about how to select hosts
|
||||
`Github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_
|
||||
Complete playbook files from the github project source
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
|
||||
|
||||
700
docsite/rst/playbooks2.rst
Normal file
@ -0,0 +1,700 @@
|
||||
Advanced Playbooks
|
||||
==================
|
||||
|
||||
Here are some advanced features of the playbooks language. Using all of these features
|
||||
are not neccessary, but many of them will prove useful. If a feature doesn't seem immediately
|
||||
relevant, feel free to skip it. For many people, the features documented in `playbooks` will
|
||||
be 90% or more of what they use in Ansible.
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:backlinks: top
|
||||
|
||||
Tags
|
||||
````
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
If you have a large playbook it may become useful to be able to run a
|
||||
specific part of the configuration. Both plays and tasks support a
|
||||
"tags:" attribute for this reason.
|
||||
|
||||
Example::
|
||||
|
||||
tasks:
|
||||
|
||||
- action: yum name=$item state=installed
|
||||
with_items:
|
||||
- httpd
|
||||
- memcached
|
||||
tags:
|
||||
- packages
|
||||
|
||||
- action: template src=templates/src.j2 dest=/etc/foo.conf
|
||||
tags:
|
||||
- configuration
|
||||
|
||||
If you wanted to just run the "configuration" and "packages" part of a very long playbook, you could do this::
|
||||
|
||||
ansible-playbook example.yml --tags "configuration,packages"
|
||||
|
||||
Playbooks Including Playbooks
|
||||
`````````````````````````````
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
To further advance the concept of include files, playbook files can
|
||||
include other playbook files. Suppose you define the behavior of all
|
||||
your webservers in "webservers.yml" and all your database servers in
|
||||
"dbservers.yml". You can create a "site.yml" that would reconfigure
|
||||
all of your systems like this::
|
||||
|
||||
----
|
||||
- include: playbooks/webservers.yml
|
||||
- include: playbooks/dbservers.yml
|
||||
|
||||
This concept works great with tags to rapidly select exactly what plays you want to run, and exactly
|
||||
what parts of those plays.
|
||||
|
||||
Ignoring Failed Commands
|
||||
````````````````````````
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
Generally playbooks will stop executing any more steps on a host that
|
||||
has a failure. Sometimes, though, you want to continue on. To do so,
|
||||
write a task that looks like this::
|
||||
|
||||
- name: this will not be counted as a failure
|
||||
action: command /bin/false
|
||||
ignore_errors: True
|
||||
|
||||
Accessing Complex Variable Data
|
||||
```````````````````````````````
|
||||
|
||||
Some provided facts, like networking information, are made available as nested data structures. To access
|
||||
them a simple '$foo' is not sufficient, but it is still easy to do. Here's how we get an IP address::
|
||||
|
||||
${ansible_eth0.ipv4.address}
|
||||
|
||||
It is also possible to access variables whose elements are arrays::
|
||||
|
||||
${somelist[0]}
|
||||
|
||||
And the array and hash reference syntaxes can be mixed.
|
||||
|
||||
In templates, the simple access form still holds, but they can also be accessed from Jinja2 in more Python-native ways if
|
||||
that is preferred::
|
||||
|
||||
{{ ansible_eth0["ipv4"]["address"] }}
|
||||
|
||||
Accessing Information About Other Hosts
|
||||
```````````````````````````````````````
|
||||
|
||||
If your database server wants to check the value of a 'fact' from another node, or an inventory variable
|
||||
assigned to another node, it's easy to do so within a template or even an action line::
|
||||
|
||||
${hostvars.hostname.factname}
|
||||
|
||||
.. note::
|
||||
No database or other complex system is required to exchange data
|
||||
between hosts. The hosts that you want to reference data from must
|
||||
be included in either the current play or any previous play if you
|
||||
are using a version prior to 0.8. If you are using 0.8, and you have
|
||||
not yet contacted the host, you'll be able to read inventory variables
|
||||
but not fact variables. Speak to the host by including it in a play
|
||||
to make fact information available.
|
||||
|
||||
Additionally, *group_names* is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2 syntax to make template source files that vary based on the group membership (or role) of the host::
|
||||
|
||||
{% if 'webserver' in group_names %}
|
||||
# some part of a configuration file that only applies to webservers
|
||||
{% endif %}
|
||||
|
||||
*groups* is a list of all the groups (and hosts) in the inventory. This can be used to enumerate all hosts within a group.
|
||||
For example::
|
||||
|
||||
{% for host in groups['app_servers'] %}
|
||||
# something that applies to all app servers.
|
||||
{% endfor %}
|
||||
|
||||
Use cases include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc.
|
||||
|
||||
*inventory_hostname* is the name of the hostname as configured in Ansible's inventory host file. This can
|
||||
be useful for when you don't want to rely on the discovered hostname `ansible_hostname` or for other mysterious
|
||||
reasons. If you have a long FQDN, *inventory_hostname_short* (in Ansible 0.6) also contains the part up to the first
|
||||
period.
|
||||
|
||||
Don't worry about any of this unless you think you need it. You'll know when you do.
|
||||
|
||||
Variable File Seperation
|
||||
````````````````````````
|
||||
|
||||
It's a great idea to keep your playbooks under source control, but
|
||||
you may wish to make the playbook source public while keeping certain
|
||||
important variables private. Similarly, sometimes you may just
|
||||
want to keep certain information in different files, away from
|
||||
the main playbook.
|
||||
|
||||
You can do this by using an external variables file, or files, just like this::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
user: root
|
||||
vars:
|
||||
favcolor: blue
|
||||
vars_files:
|
||||
- /vars/external_vars.yml
|
||||
tasks:
|
||||
- name: this is just a placeholder
|
||||
action: command /bin/echo foo
|
||||
|
||||
This removes the risk of sharing sensitive data with others when
|
||||
sharing your playbook source with them.
|
||||
|
||||
The contents of each variables file is a simple YAML dictionary, like this::
|
||||
|
||||
---
|
||||
# in the above example, this would be vars/external_vars.yml
|
||||
somevar: somevalue
|
||||
password: magic
|
||||
|
||||
.. note::
|
||||
It's also possible to keep per-host and per-group variables in very
|
||||
similar files, this is covered in :ref:`patterns`.
|
||||
|
||||
Prompting For Sensitive Data
|
||||
````````````````````````````
|
||||
|
||||
You may wish to prompt the user for certain input, and can
|
||||
do so with the similarly named 'vars_prompt' section. This has uses
|
||||
beyond security, for instance, you may use the same playbook for all
|
||||
software releases and would prompt for a particular release version
|
||||
in a push-script::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
user: root
|
||||
vars:
|
||||
from: "camelot"
|
||||
vars_prompt:
|
||||
name: "what is your name?"
|
||||
quest: "what is your quest?"
|
||||
favcolor: "what is your favorite color?"
|
||||
|
||||
There are full examples of both of these items in the github examples/playbooks directory.
|
||||
|
||||
An alternative form of vars_prompt allows for hiding input from the user, and may later support
|
||||
some other options, but otherwise works equivalently::
|
||||
|
||||
vars_prompt:
|
||||
- name: "some_password"
|
||||
prompt: "Enter password"
|
||||
private: True
|
||||
- name: "release_version"
|
||||
prompt: "Product release version"
|
||||
private: False
|
||||
|
||||
|
||||
Passing Variables On The Command Line
|
||||
`````````````````````````````````````
|
||||
|
||||
In addition to `vars_prompt` and `vars_files`, it is possible to send variables over
|
||||
the ansible command line. This is particularly useful when writing a generic release playbook
|
||||
where you may want to pass in the version of the application to deploy::
|
||||
|
||||
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
|
||||
|
||||
This is useful, for, among other things, setting the hosts group or the user for the playbook.
|
||||
|
||||
Example::
|
||||
|
||||
-----
|
||||
- user: $user
|
||||
hosts: $hosts
|
||||
tasks:
|
||||
- ...
|
||||
|
||||
ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck"
|
||||
|
||||
Conditional Execution
|
||||
`````````````````````
|
||||
|
||||
Sometimes you will want to skip a particular step on a particular host. This could be something
|
||||
as simple as not installing a certain package if the operating system is a particular version,
|
||||
or it could be something like performing some cleanup steps if a filesystem is getting full.
|
||||
|
||||
This is easy to do in Ansible, with the `only_if` clause, which actually is a Python expression.
|
||||
Don't panic -- it's actually pretty simple::
|
||||
|
||||
vars:
|
||||
favcolor: blue
|
||||
is_favcolor_blue: "'$favcolor' == 'blue'"
|
||||
is_centos: "'$facter_operatingsystem' == 'CentOS'"
|
||||
|
||||
tasks:
|
||||
- name: "shutdown if my favorite color is blue"
|
||||
action: command /sbin/shutdown -t now
|
||||
only_if: '$is_favcolor_blue'
|
||||
|
||||
Variables from tools like `facter` and `ohai` can be used here, if installed, or you can
|
||||
use variables that bubble up from ansible, which many are provided by the :ref:`setup` module. As a reminder,
|
||||
these variables are prefixed, so it's `$facter_operatingsystem`, not `$operatingsystem`. Ansible's
|
||||
built in variables are prefixed with `ansible_`.
|
||||
|
||||
The only_if expression is actually a tiny small bit of Python, so be sure to quote variables and make something
|
||||
that evaluates to `True` or `False`. It is a good idea to use 'vars_files' instead of 'vars' to define
|
||||
all of your conditional expressions in a way that makes them very easy to reuse between plays
|
||||
and playbooks.
|
||||
|
||||
You cannot use live checks here, like 'os.path.exists', so don't try.
|
||||
|
||||
It's also easy to provide your own facts if you want, which is covered in :doc:`moduledev`. To run them, just
|
||||
make a call to your own custom fact gathering module at the top of your list of tasks, and variables returned
|
||||
there will be accessible to future tasks::
|
||||
|
||||
tasks:
|
||||
- name: gather site specific fact data
|
||||
action: site_facts
|
||||
- action: command echo ${my_custom_fact_can_be_used_now}
|
||||
|
||||
One common useful trick with only_if is to key off the changed result of a last command. As an example::
|
||||
|
||||
tasks:
|
||||
- action: template src=/templates/foo.j2 dest=/etc/foo.conf
|
||||
- action: command echo 'the file has changed'
|
||||
only_if: '${last_result.changed}'
|
||||
|
||||
$last_result is a variable automatically set by Ansible, and it is a boolean, so there is no need
|
||||
to test for it against something else with an explicit equals. This assumes Ansible 0.8 and later.
|
||||
|
||||
In Ansible 0.8, a few shortcuts are available for testing whether a variable is defined or not::
|
||||
|
||||
tasks:
|
||||
- action: command echo hi
|
||||
only_if: is_set($some_variable)
|
||||
|
||||
There is a matching 'is_unset' that works the same way. Do not quote the variables inside the function.
|
||||
|
||||
While only_if is a pretty good option for advanced users, it exposes more guts of the engine than we'd like, and
|
||||
we can do better. In 0.9, we will be adding 'when', which will be like a syntactic sugar for only_if and hide
|
||||
this level of complexity -- it will numerous built in operators.
|
||||
|
||||
Conditional Imports
|
||||
```````````````````
|
||||
|
||||
Sometimes you will want to do certain things differently in a playbook based on certain criteria.
|
||||
Having one playbook that works on multiple platforms and OS versions is a good example.
|
||||
|
||||
As an example, the name of the Apache package may be different between CentOS and Debian,
|
||||
but it is easily handled with a minimum of syntax in an Ansible Playbook::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
user: root
|
||||
vars_files:
|
||||
- "vars/common.yml"
|
||||
- [ "vars/$facter_operatingsystem.yml", "vars/os_defaults.yml" ]
|
||||
tasks:
|
||||
- name: make sure apache is running
|
||||
action: service name=$apache state=running
|
||||
|
||||
.. note::
|
||||
The variable (`$facter_operatingsystem`) is being interpolated into
|
||||
the list of filenames being defined for vars_files.
|
||||
|
||||
As a reminder, the various YAML files contain just keys and values::
|
||||
|
||||
---
|
||||
# for vars/CentOS.yml
|
||||
apache: httpd
|
||||
somethingelse: 42
|
||||
|
||||
How does this work? If the operating system was 'CentOS', the first file Ansible would try to import
|
||||
would be 'vars/CentOS.yml', followed up by '/vars/os_defaults.yml' if that file
|
||||
did not exist. If no files in the list were found, an error would be raised.
|
||||
On Debian, it would instead first look towards 'vars/Debian.yml' instead of 'vars/CentOS.yml', before
|
||||
falling back on 'vars/os_defaults.yml'. Pretty simple.
|
||||
|
||||
To use this conditional import feature, you'll need facter or ohai installed prior to running the playbook, but
|
||||
you can of course push this out with Ansible if you like::
|
||||
|
||||
# for facter
|
||||
ansible -m yum -a "pkg=facter ensure=installed"
|
||||
ansible -m yum -a "pkg=ruby-json ensure=installed"
|
||||
|
||||
# for ohai
|
||||
ansible -m yum -a "pkg=ohai ensure=installed"
|
||||
|
||||
Ansible's approach to configuration -- seperating variables from tasks, keeps your playbooks
|
||||
from turning into arbitrary code with ugly nested ifs, conditionals, and so on - and results
|
||||
in more streamlined & auditable configuration rules -- especially because there are a
|
||||
minimum of decision points to track.
|
||||
|
||||
Loops
|
||||
`````
|
||||
|
||||
To save some typing, repeated tasks can be written in short-hand like so::
|
||||
|
||||
- name: add user $item
|
||||
action: user name=$item state=present groups=wheel
|
||||
with_items:
|
||||
- testuser1
|
||||
- testuser2
|
||||
|
||||
If you have defined a YAML list in a variables file, or the 'vars' section, you can also do::
|
||||
|
||||
with_items: $somelist
|
||||
|
||||
The above would be the equivalent of::
|
||||
|
||||
- name: add user testuser1
|
||||
action: user name=testuser1 state=present groups=wheel
|
||||
- name: add user testuser2
|
||||
action: user name=testuser2 state=present groups=wheel
|
||||
|
||||
The yum and apt modules use with_items to execute fewer package manager transactions.
|
||||
|
||||
Note that the types of items you iterate over with 'with_items' do not have to be simple lists of strings.
|
||||
If you have a list of hashes, you can reference subkeys using things like::
|
||||
|
||||
${item.subKeyName}
|
||||
|
||||
More Loops
|
||||
``````````
|
||||
|
||||
.. versionadded: 0.8
|
||||
|
||||
Various 'lookup plugins' allow additional ways to iterate over data. Ansible will have more of these
|
||||
over time. In 0.8, the only lookup plugin that comes stock is 'with_fileglob', but you can also write
|
||||
your own.
|
||||
|
||||
'with_fileglob' matches all files in a single directory, non-recursively, that match a pattern. It can
|
||||
be used like this::
|
||||
|
||||
----
|
||||
- hosts: all
|
||||
|
||||
tasks:
|
||||
|
||||
# first ensure our target directory exists
|
||||
- action: file dest=/etc/fooapp state=directory
|
||||
|
||||
# copy each file over that matches the given pattern
|
||||
- action: copy src=$item dest=/etc/fooapp/ owner=root mode=600
|
||||
with_fileglob: /playbooks/files/fooapp/*
|
||||
|
||||
Getting values from files
|
||||
`````````````````````````
|
||||
|
||||
.. versionadded: 0.8
|
||||
|
||||
Sometimes you'll want to include the content of a file directly into a playbook. You can do so using a macro.
|
||||
This syntax will remain in future versions, though we will also will provide ways to do this via lookup plugins (see "More Loops") as well. What follows
|
||||
is an example using the authorized_key module, which requires the actual text of the SSH key as a parameter::
|
||||
|
||||
tasks:
|
||||
- authorized_key name=$item key=$FILE(/keys/$user1)
|
||||
with_items:
|
||||
- pinky
|
||||
- brain
|
||||
- snowball
|
||||
|
||||
The "$PIPE" macro works just like file, except you would feed it a command string instead. It executes locally, not remotely, as does $FILE.
|
||||
|
||||
Selecting Files And Templates Based On Variables
|
||||
````````````````````````````````````````````````
|
||||
|
||||
Sometimes a configuration file you want to copy, or a template you will use may depend on a variable.
|
||||
The following construct selects the first available file appropriate for the variables of a given host,
|
||||
which is often much cleaner than putting a lot of if conditionals in a template.
|
||||
|
||||
The following example shows how to template out a configuration file that was very different between, say,
|
||||
CentOS and Debian::
|
||||
|
||||
- name: template a file
|
||||
action: template src=$item dest=/etc/myapp/foo.conf
|
||||
first_available_file:
|
||||
- /srv/templates/myapp/${ansible_distribution}.conf
|
||||
- /srv/templates/myapp/default.conf
|
||||
|
||||
|
||||
Asynchronous Actions and Polling
|
||||
````````````````````````````````
|
||||
|
||||
By default tasks in playbooks block, meaning the connections stay open
|
||||
until the task is done on each node. If executing playbooks with
|
||||
a small parallelism value (aka ``--forks``), you may wish that long
|
||||
running operations can go faster. The easiest way to do this is
|
||||
to kick them off all at once and then poll until they are done.
|
||||
|
||||
You will also want to use asynchronous mode on very long running
|
||||
operations that might be subject to timeout.
|
||||
|
||||
To launch a task asynchronously, specify its maximum runtime
|
||||
and how frequently you would like to poll for status. The default
|
||||
poll value is 10 seconds if you do not specify a value for `poll`::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
user: root
|
||||
tasks:
|
||||
- name: simulate long running op (15 sec), wait for up to 45, poll every 5
|
||||
action: command /bin/sleep 15
|
||||
async: 45
|
||||
poll: 5
|
||||
|
||||
.. note::
|
||||
There is no default for the async time limit. If you leave off the
|
||||
'async' keyword, the task runs synchronously, which is Ansible's
|
||||
default.
|
||||
|
||||
Alternatively, if you do not need to wait on the task to complete, you may
|
||||
"fire and forget" by specifying a poll value of 0::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
user: root
|
||||
tasks:
|
||||
- name: simulate long running op, allow to run for 45, fire and forget
|
||||
action: command /bin/sleep 15
|
||||
async: 45
|
||||
poll: 0
|
||||
|
||||
.. note::
|
||||
You shouldn't "fire and forget" with operations that require
|
||||
exclusive locks, such as yum transactions, if you expect to run other
|
||||
commands later in the playbook against those same resources.
|
||||
|
||||
.. note::
|
||||
Using a higher value for ``--forks`` will result in kicking off asynchronous
|
||||
tasks even faster. This also increases the efficiency of polling.
|
||||
|
||||
Local Playbooks
|
||||
```````````````
|
||||
|
||||
It may be useful to use a playbook locally, rather than by connecting over SSH. This can be useful
|
||||
for assuring the configuration of a system by putting a playbook on a crontab. This may also be used
|
||||
to run a playbook inside a OS installer, such as an Anaconda kickstart.
|
||||
|
||||
To run an entire playbook locally, just set the "hosts:" line to "hosts:127.0.0.1" and then run the playbook like so::
|
||||
|
||||
ansible-playbook playbook.yml --connection=local
|
||||
|
||||
Alternatively, a local connection can be used in a single playbook play, even if other plays in the playbook
|
||||
use the default remote connection type::
|
||||
|
||||
hosts: 127.0.0.1
|
||||
connection: local
|
||||
|
||||
Turning Off Facts
|
||||
`````````````````
|
||||
|
||||
If you know you don't need any fact data about your hosts, and know everything about your systems centrally, you
|
||||
can turn off fact gathering. This has advantages in scaling ansible in push mode with very large numbers of
|
||||
systems, mainly, or if you are using Ansible on experimental platforms. In any play, just do this::
|
||||
|
||||
- hosts: whatever
|
||||
gather_facts: False
|
||||
|
||||
Pull-Mode Playbooks
|
||||
```````````````````
|
||||
|
||||
The use of playbooks in local mode (above) is made extremely powerful with the addition of `ansible-pull`.
|
||||
A script for setting up ansible-pull is provided in the examples/playbooks directory of the source
|
||||
checkout.
|
||||
|
||||
The basic idea is to use Ansible to set up a remote copy of ansible on each managed node, each set to run via
|
||||
cron and update playbook source via git. This interverts the default push architecture of ansible into a pull
|
||||
architecture, which has near-limitless scaling potential. The setup playbook can be tuned to change
|
||||
the cron frequency, logging locations, and parameters to ansible-pull.
|
||||
|
||||
This is useful both for extreme scale-out as well as periodic remediation. Usage of the 'fetch' module to retrieve
|
||||
logs from ansible-pull runs would be an excellent way to gather and analyze remote logs from ansible-pull.
|
||||
|
||||
Register Variables
|
||||
``````````````````
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Often in a playbook it may be useful to store the result of a given command in a variable and access
|
||||
it later. Use of the command module in this way can in many ways eliminate the need to write site specific facts, for
|
||||
instance, you could test for the existance of a particular program.
|
||||
|
||||
The 'register' keyword decides what variable to save a result in. The resulting variables can be used in templates, action lines, or only_if statements. It looks like this (in an obviously trivial example)::
|
||||
|
||||
- name: test play
|
||||
hosts: all
|
||||
|
||||
tasks:
|
||||
|
||||
- action: shell cat /etc/motd
|
||||
register: motd_contents
|
||||
|
||||
- action: shell echo "motd contains the word hi"
|
||||
only_if: "'${motd_contents.stdout}'.find('hi') != -1"
|
||||
|
||||
|
||||
Rolling Updates
|
||||
```````````````
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
By default ansible will try to manage all of the machines referenced in a play in parallel. For a rolling updates
|
||||
use case, you can define how many hosts ansible should manage at a single time by using the ''serial'' keyword::
|
||||
|
||||
|
||||
- name: test play
|
||||
hosts: webservers
|
||||
serial: 3
|
||||
|
||||
In the above example, if we had 100 hosts, 3 hosts in the group 'webservers'
|
||||
would complete the play completely before moving on to the next 3 hosts.
|
||||
|
||||
Delegation
|
||||
``````````
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
If you want to perform a task on one host with reference to other hosts, use the 'delegate_to' keyword on a task.
|
||||
This is ideal for placing nodes in a load balanced pool, or removing them. It is also very useful for controlling
|
||||
outage windows. Using this with the 'serial' keyword to control the number of hosts executing at one time is also
|
||||
a good idea::
|
||||
|
||||
---
|
||||
- hosts: webservers
|
||||
serial: 5
|
||||
|
||||
tasks:
|
||||
- name: take out of load balancer pool
|
||||
action: command /usr/bin/take_out_of_pool $inventory_hostname
|
||||
delegate_to: 127.0.0.1
|
||||
|
||||
- name: actual steps would go here
|
||||
action: yum name=acme-web-stack state=latest
|
||||
|
||||
- name: add back to load balancer pool
|
||||
action: command /usr/bin/add_back_to_pool $inventory_hostname
|
||||
delegate_to: 127.0.0.1
|
||||
|
||||
|
||||
Here is the same playbook as above, but using the shorthand syntax,
|
||||
'local_action', for delegating to 127.0.0.1::
|
||||
|
||||
---
|
||||
# ...
|
||||
tasks:
|
||||
- name: take out of load balancer pool
|
||||
local_action: command /usr/bin/take_out_of_pool $inventory_hostname
|
||||
|
||||
# ...
|
||||
|
||||
- name: add back to load balancer pool
|
||||
local_action: command /usr/bin/add_back_to_pool $inventory_hostname
|
||||
|
||||
Fireball Mode
|
||||
`````````````
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Paramiko's core connection types of 'local', 'paramiko', and 'ssh' are augmented in version 0.8 by a new extra-fast
|
||||
connection type called 'fireball'. It can only be used with playbooks and does require some additional setup
|
||||
outside the lines of ansible's normal "no bootstrapping" philosophy. You are not required to use fireball mode
|
||||
to use Ansible, though some users may appreciate it.
|
||||
|
||||
Fireball mode works by launching a temporary 0mq daemon from SSH that by default lives for only 30 minutes before
|
||||
shutting off. Fireball mode once running uses temporary AES keys to encrypt a session, and requires direct
|
||||
communication to given nodes on the configured port. The default is 5099. The fireball daemon runs as any user you
|
||||
set it down as. So it can run as you, root, or so on. If multiple users are running Ansible as the same batch of hosts,
|
||||
take care to use unique ports.
|
||||
|
||||
Fireball mode is roughly 10 times faster than paramiko for communicating with nodes and may be a good option
|
||||
if you have a large number of hosts::
|
||||
|
||||
---
|
||||
|
||||
# set up the fireball transport
|
||||
- hosts: all
|
||||
gather_facts: False
|
||||
connection: ssh
|
||||
sudo: True
|
||||
tasks:
|
||||
- action: fireball
|
||||
|
||||
# these operations will occur over the fireball transport
|
||||
- hosts: all
|
||||
connection: fireball
|
||||
sudo: True
|
||||
tasks:
|
||||
- action: shell echo "Hello ${item}"
|
||||
with_items:
|
||||
- one
|
||||
- two
|
||||
|
||||
In order to use fireball mode, certain dependencies must be installed on both ends. You can use this playbook as a basis for initial bootstrapping on
|
||||
any platform. You will also need gcc and zeromq-devel installed from your package manager, which you can of course also get Ansible to install::
|
||||
|
||||
---
|
||||
- hosts: all
|
||||
sudo: True
|
||||
gather_facts: False
|
||||
connection: ssh
|
||||
tasks:
|
||||
- action: easy_install name=pip
|
||||
- action: pip name=pyzmq state=present
|
||||
- action: pip name=pyasn1
|
||||
- action: pip name=PyCrypto
|
||||
- action: pip name=python-keyczar
|
||||
|
||||
For more information about fireball, see the module documentation section.
|
||||
|
||||
Understanding Variable Precedence
|
||||
`````````````````````````````````
|
||||
|
||||
You have already learned about inventory host and group variables, 'vars', and 'vars_files'.
|
||||
|
||||
If a variable name is defined in more than one place with the same name, priority is as follows
|
||||
to determine which place sets the value of the variable.
|
||||
|
||||
1. Variables loaded from YAML files mentioned in 'vars_files' in a playbook.
|
||||
|
||||
2. 'vars' as defined in the playbook.
|
||||
|
||||
3. facts, whether built in or custom, or variables assigned from the 'register' keyword.
|
||||
|
||||
4. variables passed to parameterized task include statements.
|
||||
|
||||
5. Host variables from inventory.
|
||||
|
||||
6. Group variables from inventory, in order of least specific group to most specific.
|
||||
|
||||
Therefore, if you want to set a default value for something you wish to override somewhere else, the best
|
||||
place to set such a default is in a group variable.
|
||||
|
||||
Style Points
|
||||
````````````
|
||||
|
||||
Ansible playbooks are colorized. If you do not like this, set the ANSIBLE_NOCOLOR=1 environment variable.
|
||||
|
||||
Ansible playbooks also look more impressive with cowsay installed, and we encourage installing this package.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`YAMLSyntax`
|
||||
Learn about YAML syntax
|
||||
:doc:`playbooks`
|
||||
Review the basic playbook features
|
||||
:doc:`bestpractices`
|
||||
Various tips about playbooks in the real world
|
||||
:doc:`modules`
|
||||
Learn about available modules
|
||||
:doc:`moduledev`
|
||||
Learn how to extend Ansible by writing your own modules
|
||||
:doc:`patterns`
|
||||
Learn about how to select hosts
|
||||
`Github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_
|
||||
Complete playbook files from the github project source
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
|
||||
|
||||
24
docsite/rst/who_uses_ansible.rst
Normal file
@ -0,0 +1,24 @@
|
||||
.. _who_uses_ansible:
|
||||
|
||||
Who Uses Ansible
|
||||
================
|
||||
|
||||
The following is an opt-in list of just some of the folks using Ansible -- to get your company details here, `Email Michael <mailto:michael.dehaan@gmail.com>`_.
|
||||
|
||||
* `Alta Language Services <http://www.altalang.com>`_ - language testing, solutions, and translation services
|
||||
* `Basho <http://basho.com>`_ - makers of NoSQL engine Riak
|
||||
* `CatN Hosting <http://catn.com>`_ - scalable Cloud hosting
|
||||
* `Cygate AB <http://cygate.se>`_ - IT solutions from Malmo, Sweeden
|
||||
* `Dag IT Solutions <http://dagit.net>`_ - Enterprise Linux & Beyond
|
||||
* `Duke University Economics <http://duke.edu>`_ - Research & Education
|
||||
* `The Fedora Project <http://fedoraproject.org>`_ - Produces the Popular Linux Distribution
|
||||
* `Four Kitchens <http://fourkitchens.com>`_ - The Drupal Experts
|
||||
* `Lizenfrei.at <http://lizenzfrei.at/>`_ - Open Source consulting and development, Austria
|
||||
* `Schmooze Com <http://www.schmoozecom.com/>`_ - Creators of Industry Leading PBX Platforms
|
||||
* `Scientific Computing Center, Aristotle Univ. of Thessaloniki <http://www.grid.auth.gr/en/>`_ - Grid/Cloud-Based Scientific Computing
|
||||
* `Skylines.io <http://skylines.io>`_ - Real time photo search engine
|
||||
* `Steelhouse <http://steelhouse.com>`_ - Behavioral commerce
|
||||
* `Tomorrow Focus Technologies GmbH <http://www.t-f-t.net/>`_ - Running some of the biggest web sites in Europe
|
||||
* `123i.com.br <http://123i.com.br>`_ - Find real estate in Brazil
|
||||
* `Ginsys <http://ginsys.eu/>`_ - Linux infrastructure consulting from Belgium
|
||||
|
||||
33
examples/DOCUMENTATION.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
# If a key doesn't apply to your module (ex: choices, default, or
|
||||
# aliases) you can use the word 'null', or an empty list, [], where
|
||||
# appropriate.
|
||||
module: modulename
|
||||
short_description: This is a sentence describing the module
|
||||
description:
|
||||
- Longer description of the module
|
||||
- You might include instructions
|
||||
version_added: 0.X
|
||||
author: Your AWESOME name here
|
||||
notes:
|
||||
- Other things consumers of your module should know
|
||||
requirements:
|
||||
- list of required things
|
||||
- like the factor package
|
||||
- or a specic platform
|
||||
options:
|
||||
# One or more of the following
|
||||
option_name:
|
||||
description:
|
||||
- Words go here
|
||||
- that describe
|
||||
- this option
|
||||
required: true or false
|
||||
default: a string or the word null
|
||||
choices: [list, of, choices]
|
||||
aliases: [list, of, aliases]
|
||||
version_added: 0.X
|
||||
examples:
|
||||
# One or more of the following:
|
||||
- code: modulename opt1=arg1 opt2=arg2
|
||||
description: Optional words describing this example
|
||||
@ -21,7 +21,7 @@ module_name = command
|
||||
|
||||
remote_tmp = $HOME/.ansible/tmp
|
||||
|
||||
# the default pattern for ansible-playbooks ("hosts:")
|
||||
# the default pattern for ansible-playbooks ("hosts:")
|
||||
|
||||
pattern = *
|
||||
|
||||
@ -62,10 +62,18 @@ remote_port=22
|
||||
#remote_user=root
|
||||
|
||||
# if set, always use this private key file for authentication, same as if passing
|
||||
# --private-key-file to ansible or ansible-playbook
|
||||
# --private-key to ansible or ansible-playbook
|
||||
|
||||
#private_key_file=/path/to/file
|
||||
|
||||
# format of string $ansible_managed available within Jinja2 templates, replacing
|
||||
# {file}, {host} and {uid} with template filename, host and owner respectively.
|
||||
# The resulting string is passed through strftime(3) so it may contain any
|
||||
# time-formatting specifiers.
|
||||
#
|
||||
# Example: ansible_managed = DONT TOUCH {file}: call {uid} at {host} for changes
|
||||
ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}
|
||||
|
||||
[paramiko_connection]
|
||||
|
||||
# nothing to configure yet
|
||||
|
||||
@ -20,12 +20,15 @@
|
||||
|
||||
vars:
|
||||
|
||||
# schdule is fed directly to cron
|
||||
# schedule is fed directly to cron
|
||||
schedule: '*/15 * * * *'
|
||||
|
||||
# User to run ansible-pull as from cron
|
||||
cron_user: root
|
||||
|
||||
# File that ansible will use for logs
|
||||
logfile: /var/log/ansible-pull.log
|
||||
|
||||
# Directory to where repository will be cloned
|
||||
workdir: /var/lib/ansible/local
|
||||
|
||||
@ -47,5 +50,7 @@
|
||||
owner=root group=root mode=0644
|
||||
|
||||
- name: Create crontab entry to clone/pull git repository
|
||||
action: template src=templates/ansible-pull.j2 dest=/etc/cron.d/ansible-pull owner=root group=root mode=0644
|
||||
action: template src=templates/etc_cron.d_ansible-pull.j2 dest=/etc/cron.d/ansible-pull owner=root group=root mode=0644
|
||||
|
||||
- name: Create logrotate entry for ansible-pull.log
|
||||
action: template src=templates/etc_logrotate.d_ansible-pull.j2 dest=/etc/logrotate.d/ansible-pull owner=root group=root mode=0644
|
||||
|
||||
@ -42,7 +42,9 @@
|
||||
|
||||
# handlers can also be included from files, to promote reuse
|
||||
# and simpler recipes, you may wish to only have one
|
||||
# handler file for all your plays and playbooks
|
||||
# handler file for all your plays and playbooks. This example really
|
||||
# doesn't notify any handlers, it is just showing you how they would
|
||||
# be included (see intro_example for usage).
|
||||
|
||||
- include: handlers/handlers.yml
|
||||
|
||||
|
||||
20
examples/playbooks/loop_plugins.yml
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
|
||||
# in addition to loop_with_items, the loop that works over a variable, ansible can do more sophisticated looping.
|
||||
|
||||
# developer types: these are powered by 'lookup_plugins' should you ever decide to write your own
|
||||
# see lib/ansible/runner/lookup_plugins/fileglob.py -- they can do basically anything!
|
||||
|
||||
- hosts: all
|
||||
gather_facts: False
|
||||
|
||||
tasks:
|
||||
|
||||
# this will copy a bunch of config files over -- dir must be created first
|
||||
|
||||
- file: dest=/etc/fooapp state=directory
|
||||
|
||||
- copy: src=$item dest=/etc/fooapp/ owner=root mode=600
|
||||
with_fileglob: /playbooks/files/fooapp/*
|
||||
|
||||
|
||||
@ -10,20 +10,19 @@
|
||||
tasks:
|
||||
|
||||
# it is possible to save the result of any command in a named register. This variable will be made
|
||||
# available to tasks and templates made further down in the execution flow. Here we save the result
|
||||
# of a simple 'cat' command in a variable called 'motd_contents'
|
||||
# available to tasks and templates made further down in the execution flow.
|
||||
|
||||
- action: shell cat /etc/motd
|
||||
register: motd_contents
|
||||
- action: shell grep hi /etc/motd
|
||||
ignore_errors: True
|
||||
register: motd_result
|
||||
|
||||
# and here we access the register. Note that motd_contents as a variable is structured data because
|
||||
# and here we access the register. Note that variable is structured data because
|
||||
# it is a return from the command module. The shell module makes available variables such as
|
||||
# as 'stdout', 'stderr', and 'rc'. Here's a rather trivial example that runs an arbitrary step
|
||||
# if and only if the motd file contained the word 'hi'. Remember that only_if statements are
|
||||
# Python expressions. This is as complicated as Ansible syntax is going to get, and the only
|
||||
# time python really seeps into ansible's language.
|
||||
|
||||
# as 'stdout', 'stderr', and 'rc'.
|
||||
|
||||
# here we run the next action only if the previous grep returned true
|
||||
|
||||
- action: shell echo "motd contains the word hi"
|
||||
only_if: "'${motd_contents.stdout}'.find('hi') != -1"
|
||||
only_if: "${motd_result.rc} == 0"
|
||||
|
||||
|
||||
|
||||
@ -19,4 +19,3 @@
|
||||
|
||||
- name: made up task just to show variables work here
|
||||
action: command /bin/echo release is $release
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
# Cron job to git clone/pull a repo and then run locally
|
||||
{{ schedule }} {{ cron_user }} ansible-pull -d {{ workdir }} -U {{ repo_url }} >/var/log/ansible-pull.log 2>&1
|
||||
{{ schedule }} {{ cron_user }} ansible-pull -d {{ workdir }} -U {{ repo_url }} >>{{ logfile }} 2>&1
|
||||
@ -0,0 +1,7 @@
|
||||
{{ logfile }} {
|
||||
rotate 7
|
||||
daily
|
||||
compress
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
372
hacking/module_formatter.py
Executable file
@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python
|
||||
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
|
||||
#
|
||||
# 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 os
|
||||
import sys
|
||||
import yaml
|
||||
import codecs
|
||||
import json
|
||||
import ast
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import re
|
||||
import optparse
|
||||
import time
|
||||
import datetime
|
||||
import subprocess
|
||||
|
||||
# modules that are ok that they do not have documentation strings
|
||||
BLACKLIST_MODULES = [
|
||||
'async_wrapper'
|
||||
]
|
||||
|
||||
# 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, 'library'
|
||||
))
|
||||
EXAMPLE_YAML=os.path.abspath(os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yaml'
|
||||
))
|
||||
|
||||
# There is a better way of doing this!
|
||||
# TODO: somebody add U(text, http://foo.bar/) as described by Tim in #991
|
||||
|
||||
_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\(([^)]+)\)")
|
||||
|
||||
def latex_ify(text):
|
||||
|
||||
t = _ITALIC.sub("\\I{" + r"\1" + "}", text)
|
||||
t = _BOLD.sub("\\B{" + r"\1" + "}", t)
|
||||
t = _MODULE.sub("\\M{" + r"\1" + "}", t)
|
||||
t = _URL.sub("\\url{" + r"\1" + "}", t)
|
||||
t = _CONST.sub("\\C{" + r"\1" + "}", t)
|
||||
|
||||
return t
|
||||
|
||||
def html_ify(text):
|
||||
|
||||
t = _ITALIC.sub("<em>" + r"\1" + "</em>", text)
|
||||
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)
|
||||
t = _CONST.sub(r"\1", t)
|
||||
return t
|
||||
|
||||
def json_ify(text):
|
||||
|
||||
t = _ITALIC.sub("<em>" + r"\1" + "</em>", text)
|
||||
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 js_ify(text):
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def man_ify(text):
|
||||
|
||||
t = _ITALIC.sub(r'\\fI' + r"\1" + r"\\fR", text)
|
||||
t = _BOLD.sub(r'\\fB' + r"\1" + r"\\fR", t)
|
||||
t = _MODULE.sub(r'\\fI' + r"\1" + r"\\fR", t)
|
||||
t = _URL.sub(r'\\fI' + r"\1" + r"\\fR", t)
|
||||
t = _CONST.sub(r'\\fC' + r"\1" + r"\\fR", t)
|
||||
|
||||
return t
|
||||
|
||||
def rst_ify(text):
|
||||
|
||||
t = _ITALIC.sub(r'*' + r"\1" + r"*", text)
|
||||
t = _BOLD.sub(r'**' + r"\1" + r"**", t)
|
||||
# mdehaan is disabling because he finds all the Sphinx orange distracting
|
||||
#t = _MODULE.sub(r'``' + r"\1" + r"``", t)
|
||||
t = _MODULE.sub(r"\1", t)
|
||||
t = _URL.sub(r"\1", t)
|
||||
# ditto
|
||||
# t = _CONST.sub(r'``' + r"\1" + r"``", t)
|
||||
t = _CONST.sub(r"\1", t)
|
||||
|
||||
return t
|
||||
|
||||
def markdown_ify(text):
|
||||
|
||||
t = _ITALIC.sub("_" + r"\1" + "_", text)
|
||||
t = _BOLD.sub("**" + r"\1" + "**", t)
|
||||
t = _MODULE.sub("*" + r"\1" + "*", t)
|
||||
t = _URL.sub("[" + r"\1" + "](" + r"\1" + ")", t)
|
||||
t = _CONST.sub("`" + r"\1" + "`", t)
|
||||
|
||||
return t
|
||||
|
||||
# Helper for Jinja2 (format() doesn't work here...)
|
||||
def rst_fmt(text, fmt):
|
||||
return fmt % (text)
|
||||
|
||||
def rst_xline(width, char="="):
|
||||
return char * width
|
||||
|
||||
def load_examples_section(text):
|
||||
return text.split('***BREAK***')
|
||||
|
||||
def get_docstring(filename, verbose=False):
|
||||
"""
|
||||
Search for assignment of the DOCUMENTATION variable in the given file.
|
||||
Parse that from YAML and return the YAML doc or None.
|
||||
"""
|
||||
|
||||
doc = None
|
||||
|
||||
try:
|
||||
# Thank you, Habbie, for this bit of code :-)
|
||||
M = ast.parse(''.join(open(filename)))
|
||||
for child in M.body:
|
||||
if isinstance(child, ast.Assign):
|
||||
if 'DOCUMENTATION' in (t.id for t in child.targets):
|
||||
doc = yaml.load(child.value.s)
|
||||
|
||||
except:
|
||||
if verbose:
|
||||
raise
|
||||
else:
|
||||
print "unable to parse %s" % filename
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def return_data(text, options, outputname, module):
|
||||
if options.output_dir is not None:
|
||||
f = open(os.path.join(options.output_dir, outputname % module), 'w')
|
||||
f.write(text)
|
||||
f.close()
|
||||
else:
|
||||
print text
|
||||
|
||||
def boilerplate():
|
||||
if not os.path.exists(EXAMPLE_YAML):
|
||||
print >>sys.stderr, "Missing example boiler plate: %S" % EXAMPLE_YAML
|
||||
print file(EXAMPLE_YAML).read()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
p = optparse.OptionParser(
|
||||
version='%prog 1.0',
|
||||
usage='usage: %prog [options] arg1 arg2',
|
||||
description='Convert Ansible module DOCUMENTATION strings to other formats',
|
||||
)
|
||||
|
||||
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 modules/ directory")
|
||||
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=['html', 'latex', 'man', 'rst', 'json', 'markdown'],
|
||||
default='latex',
|
||||
help="Output type")
|
||||
p.add_option("-m", "--module",
|
||||
action='append',
|
||||
default=[],
|
||||
dest='module_list',
|
||||
help="Add modules to process in module_dir")
|
||||
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("-G", "--generate",
|
||||
action="store_true",
|
||||
dest="do_boilerplate",
|
||||
default=False,
|
||||
help="generate boilerplate DOCUMENTATION to stdout")
|
||||
p.add_option('-V', action='version', help='Show version number and exit')
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
|
||||
# print "M: %s" % options.module_dir
|
||||
# print "t: %s" % options.type
|
||||
# print "m: %s" % options.module_list
|
||||
# print "v: %s" % options.verbose
|
||||
|
||||
if options.do_boilerplate:
|
||||
boilerplate()
|
||||
sys.exit(0)
|
||||
|
||||
if not options.module_dir:
|
||||
print "Need module_dir"
|
||||
sys.exit(1)
|
||||
if not os.path.exists(options.module_dir):
|
||||
print >>sys.stderr, "Module directory does not exist: %s" % options.module_dir
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if not options.template_dir:
|
||||
print "Need template_dir"
|
||||
sys.exit(1)
|
||||
|
||||
env = Environment(loader=FileSystemLoader(options.template_dir),
|
||||
variable_start_string="@{",
|
||||
variable_end_string="}@",
|
||||
trim_blocks=True,
|
||||
)
|
||||
|
||||
env.globals['xline'] = rst_xline
|
||||
|
||||
if options.type == 'latex':
|
||||
env.filters['jpfunc'] = latex_ify
|
||||
template = env.get_template('latex.j2')
|
||||
outputname = "%s.tex"
|
||||
includecmt = "% generated code\n"
|
||||
includefmt = "\\input %s\n"
|
||||
if options.type == 'html':
|
||||
env.filters['jpfunc'] = html_ify
|
||||
template = env.get_template('html.j2')
|
||||
outputname = "%s.html"
|
||||
includecmt = ""
|
||||
includefmt = ""
|
||||
if options.type == 'man':
|
||||
env.filters['jpfunc'] = man_ify
|
||||
template = env.get_template('man.j2')
|
||||
outputname = "ansible.%s.3"
|
||||
includecmt = ""
|
||||
includefmt = ""
|
||||
if options.type == 'rst':
|
||||
env.filters['jpfunc'] = rst_ify
|
||||
env.filters['html_ify'] = html_ify
|
||||
env.filters['fmt'] = rst_fmt
|
||||
env.filters['xline'] = rst_xline
|
||||
template = env.get_template('rst.j2')
|
||||
outputname = "%s.rst"
|
||||
includecmt = ".. Generated by module_formatter\n"
|
||||
includefmt = ".. include:: modules/%s.rst\n"
|
||||
if options.type == 'json':
|
||||
env.filters['jpfunc'] = json_ify
|
||||
outputname = "%s.json"
|
||||
includecmt = ""
|
||||
includefmt = ""
|
||||
if options.type == 'js':
|
||||
env.filters['jpfunc'] = js_ify
|
||||
template = env.get_template('js.j2')
|
||||
outputname = "%s.js"
|
||||
if options.type == 'markdown':
|
||||
env.filters['jpfunc'] = markdown_ify
|
||||
env.filters['html_ify'] = html_ify
|
||||
template = env.get_template('markdown.j2')
|
||||
outputname = "%s.md"
|
||||
includecmt = ""
|
||||
includefmt = ""
|
||||
|
||||
if options.includes_file is not None and includefmt != "":
|
||||
incfile = open(options.includes_file, "w")
|
||||
incfile.write(includecmt)
|
||||
|
||||
# Temporary variable required to genrate aggregated content in 'js' format.
|
||||
js_data = []
|
||||
for module in sorted(os.listdir(options.module_dir)):
|
||||
if len(options.module_list):
|
||||
if not module in options.module_list:
|
||||
continue
|
||||
|
||||
fname = os.path.join(options.module_dir, module)
|
||||
extra = os.path.join("inc", "%s.tex" % module)
|
||||
|
||||
if fname.endswith(".swp"):
|
||||
continue
|
||||
|
||||
print " processing module source ---> %s" % fname
|
||||
|
||||
if options.type == 'js':
|
||||
if fname.endswith(".json"):
|
||||
f = open(fname)
|
||||
j = json.load(f)
|
||||
f.close()
|
||||
js_data.append(j)
|
||||
continue
|
||||
|
||||
doc = get_docstring(fname, verbose=options.verbose)
|
||||
|
||||
if doc is None and module not in BLACKLIST_MODULES:
|
||||
sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s ***\n" % module)
|
||||
#sys.exit(1)
|
||||
|
||||
if not doc is None:
|
||||
|
||||
doc['filename'] = fname
|
||||
doc['docuri'] = doc['module'].replace('_', '-')
|
||||
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
|
||||
doc['ansible_version'] = options.ansible_version
|
||||
|
||||
if options.includes_file is not None and includefmt != "":
|
||||
incfile.write(includefmt % module)
|
||||
|
||||
if options.verbose:
|
||||
print json.dumps(doc, indent=4)
|
||||
|
||||
|
||||
if options.type == 'latex':
|
||||
if os.path.exists(extra):
|
||||
f = open(extra)
|
||||
extradata = f.read()
|
||||
f.close()
|
||||
doc['extradata'] = extradata
|
||||
|
||||
if options.type == 'json':
|
||||
text = json.dumps(doc, indent=2)
|
||||
else:
|
||||
text = template.render(doc)
|
||||
|
||||
return_data(text, options, outputname, module)
|
||||
|
||||
if options.type == 'js':
|
||||
docs = {}
|
||||
docs['json'] = json.dumps(js_data, indent=2)
|
||||
text = template.render(docs)
|
||||
return_data(text, options, outputname, 'modules')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
7
hacking/templates/html.j2
Normal file
@ -0,0 +1,7 @@
|
||||
<!-- @{ module | upper }@ -->
|
||||
|
||||
<h2>@{module}@</h2>
|
||||
|
||||
{% for desc in description -%}
|
||||
@{ desc | jpfunc }@
|
||||
{% endfor %}
|
||||
5
hacking/templates/js.j2
Normal file
@ -0,0 +1,5 @@
|
||||
function AnsibleModules($scope) {
|
||||
$scope.modules = @{ json }@;
|
||||
|
||||
$scope.orderProp = "module";
|
||||
}
|
||||
65
hacking/templates/man.j2
Normal file
@ -0,0 +1,65 @@
|
||||
.TH ANSIBLE.@{ module | upper }@ 3 "@{ now_date }@" "@{ ansible_version }@" "ANSIBLE MODULES"
|
||||
." generated from @{ filename }@
|
||||
.SH NAME
|
||||
@{ module }@ \- @{ short_description }@
|
||||
." ------ DESCRIPTION
|
||||
.SH DESCRIPTION
|
||||
{% for desc in description %}
|
||||
.PP
|
||||
@{ desc | jpfunc }@
|
||||
{% endfor %}
|
||||
." ------ OPTIONS
|
||||
."
|
||||
."
|
||||
{% if options %}
|
||||
.SH OPTIONS
|
||||
{% for (k,v) in options.iteritems() %}
|
||||
|
||||
.IP @{ k }@
|
||||
{% for desc in v.description %}@{ desc | jpfunc }@{% endfor %}
|
||||
{% if v.get('choices') %}
|
||||
|
||||
.IR Choices :
|
||||
{% for choice in v.get('choices',[]) %}@{ choice }@{% if not loop.last %},{%else%}.{%endif%}{% endfor %}{% endif %}
|
||||
{% if v.get('required') %}(required){% endif %}
|
||||
{% if v.get('default') %} (default: @{ v.get('default') }@){% endif %}
|
||||
{#---------------------------------------------- #}
|
||||
{% if v.get('version_added') %}
|
||||
(Added in Ansible version @{ v.get('version_added') }@.)
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
."
|
||||
."
|
||||
." ------ NOTES
|
||||
{% if notes %}
|
||||
.SH NOTES
|
||||
{% for note in notes %}
|
||||
.PP
|
||||
@{ note | jpfunc }@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
."
|
||||
."
|
||||
." ------ EXAMPLES
|
||||
{% if examples is defined %}
|
||||
.SH EXAMPLES
|
||||
{% for e in examples %}
|
||||
.PP
|
||||
{% if e['description'] %}
|
||||
@{ e['description'] | jpfunc }@
|
||||
{% endif %}
|
||||
|
||||
.nf
|
||||
@{ e['code'] }@
|
||||
.fi
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
." ------- AUTHOR
|
||||
{% if author is defined %}
|
||||
.SH AUTHOR
|
||||
@{ author }@
|
||||
{% endif %}
|
||||
.SH SEE ALSO
|
||||
.IR ansible (1),
|
||||
.I http://ansible.github.com/modules.html#@{docuri}@
|
||||
55
hacking/templates/markdown.j2
Normal file
@ -0,0 +1,55 @@
|
||||
## @{ module }@
|
||||
|
||||
{# ------------------------------------------
|
||||
#
|
||||
# This is Github-flavored Markdown
|
||||
#
|
||||
--------------------------------------------#}
|
||||
|
||||
{% if version_added is defined -%}
|
||||
New in version @{ version_added }@.
|
||||
{% endif %}
|
||||
|
||||
{% for desc in description -%}
|
||||
@{ desc | jpfunc }@
|
||||
{% endfor %}
|
||||
|
||||
{% if options -%}
|
||||
<table>
|
||||
<tr>
|
||||
<th class="head">parameter</th>
|
||||
<th class="head">required</th>
|
||||
<th class="head">default</th>
|
||||
<th class="head">choices</th>
|
||||
<th class="head">comments</th>
|
||||
</tr>
|
||||
{% for (k,v) in options.iteritems() %}
|
||||
<tr>
|
||||
<td>@{ k }@</td>
|
||||
<td>{% if v.get('required', False) %}yes{% else %}no{% endif %}</td>
|
||||
<td>{% if v['default'] %}@{ v['default'] }@{% endif %}</td>
|
||||
<td><ul>{% for choice in v.get('choices',[]) -%}<li>@{ choice }@</li>{% endfor -%}</ul></td>
|
||||
<td>{% for desc in v.description -%}@{ desc | html_ify }@{% endfor -%}{% if v['version_added'] %} (added in Ansible @{v['version_added']}@){% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% for example in examples %}
|
||||
{% if example['description'] %}
|
||||
* @{ example['description'] | jpfunc }@
|
||||
{% endif %}
|
||||
|
||||
```
|
||||
@{ example['code'] }@
|
||||
```
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% if notes %}
|
||||
#### Notes
|
||||
{% for note in notes %}
|
||||
@{ note | jpfunc }@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
64
hacking/templates/rst.j2
Normal file
@ -0,0 +1,64 @@
|
||||
.. _@{ module }@:
|
||||
|
||||
@{ module }@
|
||||
``````````````````````````````
|
||||
|
||||
{# ------------------------------------------
|
||||
#
|
||||
# Please note: this looks like a core dump
|
||||
# but it isn't one.
|
||||
#
|
||||
--------------------------------------------#}
|
||||
|
||||
{% if version_added is defined -%}
|
||||
.. versionadded:: @{ version_added }@
|
||||
{% endif %}
|
||||
|
||||
{% for desc in description -%}
|
||||
@{ desc | jpfunc }@
|
||||
{% endfor %}
|
||||
|
||||
{% if options -%}
|
||||
.. raw:: html
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th class="head">parameter</th>
|
||||
<th class="head">required</th>
|
||||
<th class="head">default</th>
|
||||
<th class="head">choices</th>
|
||||
<th class="head">comments</th>
|
||||
</tr>
|
||||
{% for (k,v) in options.iteritems() %}
|
||||
<tr>
|
||||
<td>@{ k }@</td>
|
||||
<td>{% if v.get('required', False) %}yes{% else %}no{% endif %}</td>
|
||||
<td>{% if v['default'] %}@{ v['default'] }@{% endif %}</td>
|
||||
<td><ul>{% for choice in v.get('choices',[]) -%}<li>@{ choice }@</li>{% endfor -%}</ul></td>
|
||||
<td>{% for desc in v.description -%}@{ desc | html_ify }@{% endfor -%}{% if v['version_added'] %} (added in Ansible @{v['version_added']}@){% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
.. raw:: html
|
||||
|
||||
{% for example in examples %}
|
||||
{% if example['description'] %}<p>@{ example['description'] | html_ify }@</p>{% endif %}
|
||||
<p>
|
||||
<pre>
|
||||
@{ example['code'] | indent(4, True) }@
|
||||
</pre>
|
||||
</p>
|
||||
{% endfor %}
|
||||
<br/>
|
||||
|
||||
{% if notes %}
|
||||
.. raw:: html
|
||||
|
||||
<h4>Notes</h4>
|
||||
{% for note in notes %}
|
||||
<p>@{ note | html_ify }@</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@ -14,5 +14,5 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
__version__ = '0.7'
|
||||
__version__ = '0.8'
|
||||
__author__ = 'Michael DeHaan'
|
||||
|
||||
@ -63,6 +63,12 @@ class CallbackModule(object):
|
||||
def playbook_on_notify(self, host, handler):
|
||||
pass
|
||||
|
||||
def on_no_hosts_matched(self):
|
||||
pass
|
||||
|
||||
def on_no_hosts_remaining(self):
|
||||
pass
|
||||
|
||||
def playbook_on_task_start(self, name, is_conditional):
|
||||
pass
|
||||
|
||||
|
||||
@ -22,19 +22,29 @@ import os
|
||||
import subprocess
|
||||
import os.path
|
||||
from ansible.color import stringc
|
||||
import ansible.constants as C
|
||||
|
||||
dirname = os.path.dirname(__file__)
|
||||
callbacks = utils.import_plugins(os.path.join(dirname, 'callback_plugins'))
|
||||
callbacks = [ c.CallbackModule() for c in callbacks.values() if c.__name__ != '__init__' ]
|
||||
callbacks = [ c.CallbackModule() for c in callbacks.values() ]
|
||||
def load_more_callbacks(dirname):
|
||||
callbacks.extend([c.CallbackModule() for c in utils.import_plugins(dirname).values()])
|
||||
for i in C.DEFAULT_CALLBACK_PLUGIN_PATH.split(os.pathsep):
|
||||
load_more_callbacks(i)
|
||||
|
||||
cowsay = None
|
||||
if os.path.exists("/usr/bin/cowsay"):
|
||||
if os.getenv("ANSIBLE_NOCOWS") is not None:
|
||||
cowsay = None
|
||||
elif os.path.exists("/usr/bin/cowsay"):
|
||||
cowsay = "/usr/bin/cowsay"
|
||||
elif os.path.exists("/usr/games/cowsay"):
|
||||
cowsay = "/usr/games/cowsay"
|
||||
elif os.path.exists("/usr/local/bin/cowsay"):
|
||||
# BSD path for cowsay
|
||||
cowsay = "/usr/local/bin/cowsay"
|
||||
|
||||
def call_callback_module(method_name, *args, **kwargs):
|
||||
|
||||
|
||||
for callback_plugin in callbacks:
|
||||
methods = [
|
||||
getattr(callback_plugin, method_name, None),
|
||||
@ -237,6 +247,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
super(CliRunnerCallbacks, self).on_unreachable(host, res)
|
||||
|
||||
def on_skipped(self, host, item=None):
|
||||
print "%s | skipped" % (host)
|
||||
super(CliRunnerCallbacks, self).on_skipped(host, item)
|
||||
|
||||
def on_error(self, host, err):
|
||||
@ -280,15 +291,16 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
self.stats = stats
|
||||
self._async_notified = {}
|
||||
|
||||
def on_unreachable(self, host, msg):
|
||||
def on_unreachable(self, host, results):
|
||||
item = None
|
||||
if type(msg) == dict:
|
||||
item = msg.get('item', None)
|
||||
if type(results) == dict:
|
||||
item = results.get('item', None)
|
||||
if item:
|
||||
print "fatal: [%s] => (item=%s) => %s" % (host, item, msg)
|
||||
msg = "fatal: [%s] => (item=%s) => %s" % (host, item, results)
|
||||
else:
|
||||
print "fatal: [%s] => %s" % (host, msg)
|
||||
super(PlaybookRunnerCallbacks, self).on_unreachable(host, msg)
|
||||
msg = "fatal: [%s] => %s" % (host, results)
|
||||
print stringc(msg, 'red')
|
||||
super(PlaybookRunnerCallbacks, self).on_unreachable(host, results)
|
||||
|
||||
def on_failed(self, host, results, ignore_errors=False):
|
||||
|
||||
@ -296,13 +308,27 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
results2.pop('invocation', None)
|
||||
|
||||
item = results2.get('item', None)
|
||||
parsed = results2.get('parsed', True)
|
||||
module_msg = ''
|
||||
if not parsed:
|
||||
module_msg = results2.pop('msg', None)
|
||||
stderr = results2.pop('stderr', None)
|
||||
stdout = results2.pop('stdout', None)
|
||||
|
||||
if item:
|
||||
msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results2))
|
||||
else:
|
||||
msg = "failed: [%s] => %s" % (host, utils.jsonify(results2))
|
||||
print stringc(msg, 'red')
|
||||
|
||||
if stderr:
|
||||
print stringc("stderr: %s" % stderr, 'red')
|
||||
if stdout:
|
||||
print stringc("stdout: %s" % stdout, 'red')
|
||||
if not parsed and module_msg:
|
||||
print stringc("invalid output was: %s" % module_msg, 'red')
|
||||
if ignore_errors:
|
||||
print stringc("...ignoring", 'yellow')
|
||||
print stringc("...ignoring", 'cyan')
|
||||
super(PlaybookRunnerCallbacks, self).on_failed(host, results, ignore_errors=ignore_errors)
|
||||
|
||||
def on_ok(self, host, host_result):
|
||||
@ -310,25 +336,29 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
|
||||
host_result2 = host_result.copy()
|
||||
host_result2.pop('invocation', None)
|
||||
changed = host_result.get('changed', False)
|
||||
ok_or_changed = 'ok'
|
||||
if changed:
|
||||
ok_or_changed = 'changed'
|
||||
|
||||
# show verbose output for non-setup module results if --verbose is used
|
||||
msg = ''
|
||||
if not self.verbose or host_result2.get("verbose_override",None) is not None:
|
||||
if item:
|
||||
msg = "ok: [%s] => (item=%s)" % (host,item)
|
||||
msg = "%s: [%s] => (item=%s)" % (ok_or_changed, host, item)
|
||||
else:
|
||||
if 'ansible_job_id' not in host_result or 'finished' in host_result:
|
||||
msg = "ok: [%s]" % (host)
|
||||
msg = "%s: [%s]" % (ok_or_changed, host)
|
||||
else:
|
||||
# verbose ...
|
||||
if item:
|
||||
msg = "ok: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(host_result2))
|
||||
msg = "%s: [%s] => (item=%s) => %s" % (ok_or_changed, host, item, utils.jsonify(host_result2))
|
||||
else:
|
||||
if 'ansible_job_id' not in host_result or 'finished' in host_result2:
|
||||
msg = "ok: [%s] => %s" % (host, utils.jsonify(host_result2))
|
||||
msg = "%s: [%s] => %s" % (ok_or_changed, host, utils.jsonify(host_result2))
|
||||
|
||||
if msg != '':
|
||||
if not 'changed' in host_result2 or not host_result['changed']:
|
||||
if not changed:
|
||||
print stringc(msg, 'green')
|
||||
else:
|
||||
print stringc(msg, 'yellow')
|
||||
@ -353,11 +383,11 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
msg = "skipping: [%s] => (item=%s)" % (host, item)
|
||||
else:
|
||||
msg = "skipping: [%s]" % host
|
||||
print stringc(msg, 'yellow')
|
||||
print stringc(msg, 'cyan')
|
||||
super(PlaybookRunnerCallbacks, self).on_skipped(host, item)
|
||||
|
||||
def on_no_hosts(self):
|
||||
print stringc("no hosts matched or remaining\n", 'red')
|
||||
print stringc("FATAL: no hosts matched or all hosts have already failed -- aborting\n", 'red')
|
||||
super(PlaybookRunnerCallbacks, self).on_no_hosts()
|
||||
|
||||
def on_async_poll(self, host, res, jid, clock):
|
||||
@ -395,6 +425,14 @@ class PlaybookCallbacks(object):
|
||||
def on_notify(self, host, handler):
|
||||
call_callback_module('playbook_on_notify', host, handler)
|
||||
|
||||
def on_no_hosts_matched(self):
|
||||
print stringc("no hosts matched", 'red')
|
||||
call_callback_module('playbook_on_no_hosts_matched')
|
||||
|
||||
def on_no_hosts_remaining(self):
|
||||
print stringc("\nFATAL: all hosts have already failed -- aborting", 'red')
|
||||
call_callback_module('playbook_on_no_hosts_remaining')
|
||||
|
||||
def on_task_start(self, name, is_conditional):
|
||||
msg = "TASK: [%s]" % name
|
||||
if is_conditional:
|
||||
@ -405,7 +443,7 @@ class PlaybookCallbacks(object):
|
||||
def on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None):
|
||||
|
||||
if prompt:
|
||||
msg = prompt
|
||||
msg = "%s: " % prompt
|
||||
else:
|
||||
msg = 'input for %s: ' % varname
|
||||
|
||||
|
||||
@ -36,6 +36,9 @@ else:
|
||||
# curses returns an error (e.g. could not find terminal)
|
||||
ANSIBLE_COLOR=False
|
||||
|
||||
if os.getenv("ANSIBLE_FORCE_COLOR") is not None:
|
||||
ANSIBLE_COLOR=True
|
||||
|
||||
# --- begin "pretty"
|
||||
#
|
||||
# pretty - A miniature library that provides a Python print and stdout
|
||||
|
||||
@ -21,33 +21,35 @@ import ConfigParser
|
||||
import traceback
|
||||
|
||||
def get_config(p, section, key, env_var, default):
|
||||
if env_var is not None:
|
||||
value = os.environ.get(env_var, None)
|
||||
if value is not None:
|
||||
return value
|
||||
if p is not None:
|
||||
try:
|
||||
return p.get(section, key)
|
||||
except:
|
||||
if env_var is not None:
|
||||
return os.environ.get(env_var, default)
|
||||
return default
|
||||
else:
|
||||
if env_var is not None:
|
||||
return os.environ.get(env_var, default)
|
||||
return default
|
||||
return default
|
||||
|
||||
def load_config_file():
|
||||
p = ConfigParser.ConfigParser()
|
||||
path1 = os.path.expanduser(
|
||||
os.environ.get('ANSIBLE_CONFIG', "~/.ansible.cfg"))
|
||||
path2 = "/etc/ansible/ansible.cfg"
|
||||
path1 = os.path.expanduser(os.environ.get('ANSIBLE_CONFIG', "~/.ansible.cfg"))
|
||||
path2 = os.getcwd() + "/ansible.cfg"
|
||||
path3 = "/etc/ansible/ansible.cfg"
|
||||
|
||||
if os.path.exists(path1):
|
||||
p.read(path1)
|
||||
elif os.path.exists(path2):
|
||||
p.read(path2)
|
||||
elif os.path.exists(path3):
|
||||
p.read(path3)
|
||||
else:
|
||||
return None
|
||||
return p
|
||||
|
||||
def shell_expand_path(path):
|
||||
''' shell_expand_path is needed as os.path.expanduser does not work
|
||||
''' shell_expand_path is needed as os.path.expanduser does not work
|
||||
when path is None, which is the default for ANSIBLE_PRIVATE_KEY_FILE '''
|
||||
if path:
|
||||
path = os.path.expanduser(path)
|
||||
@ -57,12 +59,16 @@ p = load_config_file()
|
||||
|
||||
active_user = pwd.getpwuid(os.geteuid())[0]
|
||||
|
||||
# Needed so the RPM can call setup.py and have modules land in the
|
||||
# correct location. See #1277 for discussion
|
||||
DIST_MODULE_PATH = '/usr/share/ansible/'
|
||||
|
||||
# sections in config file
|
||||
DEFAULTS='defaults'
|
||||
|
||||
# configurable things
|
||||
DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'hostfile', 'ANSIBLE_HOSTS', '/etc/ansible/hosts'))
|
||||
DEFAULT_MODULE_PATH = shell_expand_path(get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', '/usr/share/ansible'))
|
||||
DEFAULT_MODULE_PATH = shell_expand_path(get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', DIST_MODULE_PATH))
|
||||
DEFAULT_REMOTE_TMP = shell_expand_path(get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp'))
|
||||
DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command')
|
||||
DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, '*')
|
||||
@ -75,6 +81,12 @@ DEFAULT_PRIVATE_KEY_FILE = shell_expand_path(get_config(p, DEFAULTS, 'private_k
|
||||
DEFAULT_SUDO_USER = get_config(p, DEFAULTS, 'sudo_user', 'ANSIBLE_SUDO_USER', 'root')
|
||||
DEFAULT_REMOTE_PORT = int(get_config(p, DEFAULTS, 'remote_port', 'ANSIBLE_REMOTE_PORT', 22))
|
||||
DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'paramiko')
|
||||
DEFAULT_MANAGED_STR = get_config(p, DEFAULTS, 'ansible_managed', None, 'Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}')
|
||||
|
||||
DEFAULT_ACTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'action_plugins', None, '/usr/share/ansible_plugins/action_plugins'))
|
||||
DEFAULT_CALLBACK_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'callback_plugins', None, '/usr/share/ansible_plugins/callback_plugins'))
|
||||
DEFAULT_CONNECTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'connection_plugins', None, '/usr/share/ansible_plugins/connection_plugins'))
|
||||
DEFAULT_LOOKUP_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'lookup_plugins', None, '/usr/share/ansible_plugins/lookup_plugins'))
|
||||
|
||||
# non-configurable things
|
||||
DEFAULT_REMOTE_PASS = None
|
||||
@ -82,3 +94,4 @@ DEFAULT_SUDO_PASS = None
|
||||
DEFAULT_SUBSET = None
|
||||
|
||||
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
|
||||
ZEROMQ_PORT = int(get_config(p, 'fireball', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099))
|
||||
|
||||
@ -35,7 +35,7 @@ class Inventory(object):
|
||||
"""
|
||||
|
||||
__slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script',
|
||||
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache' ]
|
||||
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list' ]
|
||||
|
||||
def __init__(self, host_list=C.DEFAULT_HOST_LIST):
|
||||
|
||||
@ -49,6 +49,7 @@ class Inventory(object):
|
||||
self._vars_per_host = {}
|
||||
self._vars_per_group = {}
|
||||
self._hosts_cache = {}
|
||||
self._groups_list = {}
|
||||
|
||||
# the inventory object holds a list of groups
|
||||
self.groups = []
|
||||
@ -75,7 +76,7 @@ class Inventory(object):
|
||||
all.add_host(Host(tokens[0], tokens[1]))
|
||||
else:
|
||||
all.add_host(Host(x))
|
||||
elif os.access(host_list, os.X_OK):
|
||||
elif utils.is_executable(host_list):
|
||||
self._is_script = True
|
||||
self.parser = InventoryScript(filename=host_list)
|
||||
self.groups = self.parser.groups.values()
|
||||
@ -96,7 +97,9 @@ class Inventory(object):
|
||||
applied subsets.
|
||||
"""
|
||||
|
||||
# process patterns
|
||||
# process patterns
|
||||
if isinstance(pattern, list):
|
||||
pattern = ';'.join(pattern)
|
||||
patterns = pattern.replace(";",":").split(":")
|
||||
positive_patterns = [ p for p in patterns if not p.startswith("!") ]
|
||||
negative_patterns = [ p for p in patterns if p.startswith("!") ]
|
||||
@ -106,8 +109,8 @@ class Inventory(object):
|
||||
|
||||
# exclude hosts mentioned in a negative pattern
|
||||
if len(negative_patterns):
|
||||
exclude_hosts = self._get_hosts(negative_patterns)
|
||||
hosts = [ h for h in hosts if h not in exclude_hosts ]
|
||||
exclude_hosts = [ h.name for h in self._get_hosts(negative_patterns) ]
|
||||
hosts = [ h for h in hosts if h.name not in exclude_hosts ]
|
||||
|
||||
# exclude hosts not in a subset, if defined
|
||||
if self._subset:
|
||||
@ -213,6 +216,17 @@ class Inventory(object):
|
||||
continue
|
||||
return results
|
||||
|
||||
def groups_list(self):
|
||||
if not self._groups_list:
|
||||
groups = {}
|
||||
for g in self.groups:
|
||||
groups[g.name] = [h.name for h in g.get_hosts()]
|
||||
ancestors = g.get_ancestors()
|
||||
for a in ancestors:
|
||||
groups[a.name] = [h.name for h in a.get_hosts()]
|
||||
self._groups_list = groups
|
||||
return self._groups_list
|
||||
|
||||
def get_groups(self):
|
||||
return self.groups
|
||||
|
||||
@ -252,8 +266,11 @@ class Inventory(object):
|
||||
|
||||
def _get_variables(self, hostname):
|
||||
|
||||
host = self.get_host(hostname)
|
||||
if host is None:
|
||||
raise errors.AnsibleError("host not found: %s" % hostname)
|
||||
|
||||
if self._is_script:
|
||||
host = self.get_host(hostname)
|
||||
cmd = subprocess.Popen(
|
||||
[self.host_list,"--host",hostname],
|
||||
stdout=subprocess.PIPE,
|
||||
@ -269,11 +286,8 @@ class Inventory(object):
|
||||
results['group_names'] = sorted(groups)
|
||||
|
||||
return results
|
||||
|
||||
host = self.get_host(hostname)
|
||||
if host is None:
|
||||
raise Exception("host not found: %s" % hostname)
|
||||
return host.get_variables()
|
||||
else:
|
||||
return host.get_variables()
|
||||
|
||||
def add_group(self, group):
|
||||
self.groups.append(group)
|
||||
|
||||
@ -35,6 +35,7 @@ class InventoryScript(object):
|
||||
self.groups = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
all_hosts = {}
|
||||
|
||||
groups = {}
|
||||
self.raw = utils.parse_json(self.data)
|
||||
@ -45,7 +46,9 @@ class InventoryScript(object):
|
||||
group = groups[group_name] = Group(group_name)
|
||||
host = None
|
||||
for hostname in hosts:
|
||||
host = Host(hostname)
|
||||
if not hostname in all_hosts:
|
||||
all_hosts[hostname] = Host(hostname)
|
||||
host = all_hosts[hostname]
|
||||
group.add_host(host)
|
||||
# FIXME: hack shouldn't be needed
|
||||
all.add_host(host)
|
||||
|
||||
@ -49,15 +49,23 @@ import syslog
|
||||
import types
|
||||
import time
|
||||
import shutil
|
||||
import stat
|
||||
|
||||
try:
|
||||
from hashlib import md5 as _md5
|
||||
except ImportError:
|
||||
from md5 import md5 as _md5
|
||||
|
||||
try:
|
||||
from systemd import journal
|
||||
has_journal = True
|
||||
except ImportError:
|
||||
import syslog
|
||||
has_journal = False
|
||||
|
||||
class AnsibleModule(object):
|
||||
|
||||
def __init__(self, argument_spec, bypass_checks=False, no_log=False,
|
||||
def __init__(self, argument_spec, bypass_checks=False, no_log=False,
|
||||
check_invalid_arguments=True, mutually_exclusive=None, required_together=None,
|
||||
required_one_of=None):
|
||||
|
||||
@ -144,7 +152,7 @@ class AnsibleModule(object):
|
||||
non_zero = [ c for c in counts if c > 0 ]
|
||||
if len(non_zero) > 0:
|
||||
if 0 in counts:
|
||||
self.fail_json(msg="parameters are required together: %s" % check)
|
||||
self.fail_json(msg="parameters are required together: %s" % check)
|
||||
|
||||
def _check_required_arguments(self):
|
||||
''' ensure all required arguments are present '''
|
||||
@ -165,7 +173,7 @@ class AnsibleModule(object):
|
||||
if type(choices) == list:
|
||||
if k in self.params:
|
||||
if self.params[k] not in choices:
|
||||
choices_str=",".join(choices)
|
||||
choices_str=",".join([str(c) for c in choices])
|
||||
msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k])
|
||||
self.fail_json(msg=msg)
|
||||
else:
|
||||
@ -198,11 +206,27 @@ class AnsibleModule(object):
|
||||
|
||||
def _log_invocation(self):
|
||||
''' log that ansible ran the module '''
|
||||
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
||||
# Sanitize possible password argument when logging
|
||||
log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", self.args)
|
||||
log_args = re.sub(r'login_password=.+ (.*)', r"login_password=NOT_LOGGING_PASSWORD \1", log_args)
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args)
|
||||
# TODO: generalize a seperate log function and make log_invocation use it
|
||||
# Sanitize possible password argument when logging.
|
||||
log_args = dict()
|
||||
passwd_keys = ['password', 'login_password']
|
||||
for param in self.params:
|
||||
if param in passwd_keys:
|
||||
log_args[param] = 'NOT_LOGGING_PASSWORD'
|
||||
else:
|
||||
log_args[param] = self.params[param]
|
||||
|
||||
if (has_journal):
|
||||
journal_args = ["MESSAGE=Ansible module invoked", "MODULE=%s" % os.path.basename(__file__)]
|
||||
for arg in log_args:
|
||||
journal_args.append(arg.upper() + "=" + str(log_args[arg]))
|
||||
journal.sendv(*journal_args)
|
||||
else:
|
||||
msg = ''
|
||||
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
||||
for arg in log_args:
|
||||
msg = msg + arg + '=' + str(log_args[arg]) + ' '
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % msg)
|
||||
|
||||
def get_bin_path(self, arg, required=False, opt_dirs=[]):
|
||||
'''
|
||||
@ -217,7 +241,7 @@ class AnsibleModule(object):
|
||||
for d in opt_dirs:
|
||||
if d is not None and os.path.exists(d):
|
||||
paths.append(d)
|
||||
paths += os.environ.get('PATH').split(':')
|
||||
paths += os.environ.get('PATH', '').split(':')
|
||||
bin_path = None
|
||||
# mangle PATH to include /sbin dirs
|
||||
for p in sbin_paths:
|
||||
@ -225,7 +249,7 @@ class AnsibleModule(object):
|
||||
paths.append(p)
|
||||
for d in paths:
|
||||
path = os.path.join(d, arg)
|
||||
if os.path.exists(path) and os.access(path, os.X_OK):
|
||||
if os.path.exists(path) and self.is_executable(path):
|
||||
bin_path = path
|
||||
break
|
||||
if required and bin_path is None:
|
||||
@ -260,6 +284,12 @@ class AnsibleModule(object):
|
||||
print self.jsonify(kwargs)
|
||||
sys.exit(1)
|
||||
|
||||
def is_executable(self, path):
|
||||
'''is the given path executable?'''
|
||||
return (stat.S_IXUSR & os.stat(path)[stat.ST_MODE]
|
||||
or stat.S_IXGRP & os.stat(path)[stat.ST_MODE]
|
||||
or stat.S_IXOTH & os.stat(path)[stat.ST_MODE])
|
||||
|
||||
def md5(self, filename):
|
||||
''' Return MD5 hex digest of local file, or None if file is not present. '''
|
||||
if not os.path.exists(filename):
|
||||
|
||||
@ -20,12 +20,15 @@ import ansible.runner
|
||||
import ansible.constants as C
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
import ansible.callbacks
|
||||
import os
|
||||
import collections
|
||||
from play import Play
|
||||
|
||||
SETUP_CACHE = collections.defaultdict(dict)
|
||||
|
||||
plugins_dir = os.path.join(os.path.dirname(__file__), '..', 'runner')
|
||||
|
||||
class PlayBook(object):
|
||||
'''
|
||||
runs an ansible playbook, given as a datastructure or YAML filename.
|
||||
@ -48,7 +51,7 @@ class PlayBook(object):
|
||||
remote_user = C.DEFAULT_REMOTE_USER,
|
||||
remote_pass = C.DEFAULT_REMOTE_PASS,
|
||||
sudo_pass = C.DEFAULT_SUDO_PASS,
|
||||
remote_port = C.DEFAULT_REMOTE_PORT,
|
||||
remote_port = None,
|
||||
transport = C.DEFAULT_TRANSPORT,
|
||||
private_key_file = C.DEFAULT_PRIVATE_KEY_FILE,
|
||||
callbacks = None,
|
||||
@ -105,15 +108,21 @@ class PlayBook(object):
|
||||
self.private_key_file = private_key_file
|
||||
self.only_tags = only_tags
|
||||
|
||||
self.inventory = ansible.inventory.Inventory(host_list)
|
||||
self.inventory = ansible.inventory.Inventory(host_list)
|
||||
self.inventory.subset(subset)
|
||||
|
||||
self.modules_list = utils.get_available_modules(self.module_path)
|
||||
self.lookup_plugins_list = ansible.runner.lookup_plugin_list
|
||||
|
||||
if not self.inventory._is_script:
|
||||
self.global_vars.update(self.inventory.get_group_variables('all'))
|
||||
|
||||
self.basedir = os.path.dirname(playbook)
|
||||
self.playbook = self._load_playbook_from_file(playbook)
|
||||
(self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook)
|
||||
self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library")
|
||||
ansible.callbacks.load_more_callbacks(os.path.join(self.basedir, "callback_plugins"))
|
||||
|
||||
self.lookup_plugins_list.update(utils.import_plugins(os.path.join(self.basedir, 'lookup_plugins')))
|
||||
|
||||
# *****************************************************
|
||||
|
||||
@ -124,6 +133,7 @@ class PlayBook(object):
|
||||
|
||||
playbook_data = utils.parse_yaml_from_file(path)
|
||||
accumulated_plays = []
|
||||
play_basedirs = []
|
||||
|
||||
if type(playbook_data) != list:
|
||||
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list")
|
||||
@ -134,13 +144,17 @@ class PlayBook(object):
|
||||
if 'include' in play:
|
||||
if len(play.keys()) == 1:
|
||||
included_path = utils.path_dwim(self.basedir, play['include'])
|
||||
accumulated_plays.extend(self._load_playbook_from_file(included_path))
|
||||
(plays, basedirs) = self._load_playbook_from_file(included_path)
|
||||
accumulated_plays.extend(plays)
|
||||
play_basedirs.extend(basedirs)
|
||||
|
||||
else:
|
||||
raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play)
|
||||
else:
|
||||
accumulated_plays.append(play)
|
||||
play_basedirs.append(os.path.dirname(path))
|
||||
|
||||
return accumulated_plays
|
||||
return (accumulated_plays, play_basedirs)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
@ -152,8 +166,8 @@ class PlayBook(object):
|
||||
|
||||
# loop through all patterns and run them
|
||||
self.callbacks.on_start()
|
||||
for play_ds in self.playbook:
|
||||
play = Play(self,play_ds)
|
||||
for (play_ds, play_basedir) in zip(self.playbook, self.play_basedirs):
|
||||
play = Play(self, play_ds, play_basedir)
|
||||
matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
|
||||
matched_tags_all = matched_tags_all | matched_tags
|
||||
unmatched_tags_all = unmatched_tags_all | unmatched_tags
|
||||
@ -166,8 +180,8 @@ class PlayBook(object):
|
||||
# if the playbook is invoked with --tags that don't exist at all in the playbooks
|
||||
# then we need to raise an error so that the user can correct the arguments.
|
||||
unknown_tags = set(self.only_tags) - (matched_tags_all | unmatched_tags_all)
|
||||
unknown_tags.discard('all')
|
||||
|
||||
unknown_tags.discard('all')
|
||||
|
||||
if len(unknown_tags) > 0:
|
||||
unmatched_tags_all.discard('all')
|
||||
msg = 'tag(s) not found in playbook: %s. possible values: %s'
|
||||
@ -176,7 +190,8 @@ class PlayBook(object):
|
||||
raise errors.AnsibleError(msg % (unknown, unmatched))
|
||||
|
||||
for play in plays:
|
||||
self._run_play(play)
|
||||
if not self._run_play(play):
|
||||
break
|
||||
|
||||
# summarize the results
|
||||
results = {}
|
||||
@ -215,10 +230,10 @@ class PlayBook(object):
|
||||
timeout=self.timeout, remote_user=task.play.remote_user,
|
||||
remote_port=task.play.remote_port, module_vars=task.module_vars,
|
||||
private_key_file=self.private_key_file,
|
||||
setup_cache=self.SETUP_CACHE, basedir=self.basedir,
|
||||
setup_cache=self.SETUP_CACHE, basedir=task.play.basedir,
|
||||
conditional=task.only_if, callbacks=self.runner_callbacks,
|
||||
sudo=task.play.sudo, sudo_user=task.play.sudo_user,
|
||||
transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True
|
||||
sudo=task.sudo, sudo_user=task.sudo_user,
|
||||
transport=task.transport, sudo_pass=task.sudo_pass, is_playbook=True
|
||||
)
|
||||
|
||||
if task.async_seconds == 0:
|
||||
@ -230,7 +245,14 @@ class PlayBook(object):
|
||||
# if not polling, playbook requested fire and forget, so don't poll
|
||||
results = self._async_poll(poller, task.async_seconds, task.async_poll_interval)
|
||||
|
||||
contacted = results.get('contacted',{})
|
||||
dark = results.get('dark', {})
|
||||
|
||||
self.inventory.lift_restriction()
|
||||
|
||||
if len(contacted.keys()) == 0 and len(dark.keys()) == 0:
|
||||
return None
|
||||
|
||||
return results
|
||||
|
||||
# *****************************************************
|
||||
@ -242,17 +264,23 @@ class PlayBook(object):
|
||||
|
||||
# load up an appropriate ansible runner to run the task in parallel
|
||||
results = self._run_task_internal(task)
|
||||
# if no hosts are matched, carry on
|
||||
if results is None:
|
||||
results = {}
|
||||
|
||||
# if no hosts are matched, carry on
|
||||
hosts_remaining = True
|
||||
if results is None:
|
||||
hosts_remaining = False
|
||||
results = {}
|
||||
|
||||
contacted = results.get('contacted', {})
|
||||
self.stats.compute(results, ignore_errors=task.ignore_errors)
|
||||
|
||||
# add facts to the global setup cache
|
||||
for host, result in results['contacted'].iteritems():
|
||||
for host, result in contacted.iteritems():
|
||||
facts = result.get('ansible_facts', {})
|
||||
self.SETUP_CACHE[host].update(facts)
|
||||
if task.register:
|
||||
if 'stdout' in result:
|
||||
result['stdout_lines'] = result['stdout'].splitlines()
|
||||
self.SETUP_CACHE[host][task.register] = result
|
||||
|
||||
# flag which notify handlers need to be run
|
||||
@ -260,7 +288,9 @@ class PlayBook(object):
|
||||
for host, results in results.get('contacted',{}).iteritems():
|
||||
if results.get('changed', False):
|
||||
for handler_name in task.notify:
|
||||
self._flag_handler(play.handlers(), utils.template(handler_name, task.module_vars), host)
|
||||
self._flag_handler(play.handlers(), utils.template(play.basedir, handler_name, task.module_vars), host)
|
||||
|
||||
return hosts_remaining
|
||||
|
||||
# *****************************************************
|
||||
|
||||
@ -288,8 +318,12 @@ class PlayBook(object):
|
||||
host_list = [ h for h in self.inventory.list_hosts(play.hosts)
|
||||
if not (h in self.stats.failures or h in self.stats.dark) ]
|
||||
|
||||
if not play.gather_facts:
|
||||
if play.gather_facts is False:
|
||||
return {}
|
||||
elif play.gather_facts is None:
|
||||
host_list = [h for h in host_list if h not in self.SETUP_CACHE or 'module_setup' not in self.SETUP_CACHE[h]]
|
||||
if len(host_list) == 0:
|
||||
return {}
|
||||
|
||||
self.callbacks.on_setup()
|
||||
self.inventory.restrict_to(host_list)
|
||||
@ -310,7 +344,8 @@ class PlayBook(object):
|
||||
# let runner template out future commands
|
||||
setup_ok = setup_results.get('contacted', {})
|
||||
for (host, result) in setup_ok.iteritems():
|
||||
self.SETUP_CACHE[host] = result.get('ansible_facts', {})
|
||||
self.SETUP_CACHE[host].update({'module_setup': True})
|
||||
self.SETUP_CACHE[host].update(result.get('ansible_facts', {}))
|
||||
return setup_results
|
||||
|
||||
# *****************************************************
|
||||
@ -320,6 +355,11 @@ class PlayBook(object):
|
||||
|
||||
self.callbacks.on_play_start(play.name)
|
||||
|
||||
# if no hosts matches this play, drop out
|
||||
if not self.inventory.list_hosts(play.hosts):
|
||||
self.callbacks.on_no_hosts_matched()
|
||||
return True
|
||||
|
||||
# get facts from system
|
||||
self._do_setup_step(play)
|
||||
|
||||
@ -345,6 +385,7 @@ class PlayBook(object):
|
||||
self.inventory.also_restrict_to(on_hosts)
|
||||
|
||||
for task in play.tasks():
|
||||
|
||||
# only run the task if the requested tags match
|
||||
should_run = False
|
||||
for x in self.only_tags:
|
||||
@ -353,7 +394,19 @@ class PlayBook(object):
|
||||
should_run = True
|
||||
break
|
||||
if should_run:
|
||||
self._run_task(play, task, False)
|
||||
if not self._run_task(play, task, False):
|
||||
# whether no hosts matched is fatal or not depends if it was on the initial step.
|
||||
# if we got exactly no hosts on the first step (setup!) then the host group
|
||||
# just didn't match anything and that's ok
|
||||
return False
|
||||
|
||||
host_list = [ h for h in self.inventory.list_hosts(play.hosts)
|
||||
if not (h in self.stats.failures or h in self.stats.dark) ]
|
||||
|
||||
# if no hosts remain, drop out
|
||||
if not host_list:
|
||||
self.callbacks.on_no_hosts_remaining()
|
||||
return False
|
||||
|
||||
# run notify actions
|
||||
for handler in play.handlers():
|
||||
@ -364,3 +417,5 @@ class PlayBook(object):
|
||||
|
||||
self.inventory.lift_also_restriction()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@ -29,7 +29,8 @@ class Play(object):
|
||||
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
|
||||
'handlers', 'remote_user', 'remote_port',
|
||||
'sudo', 'sudo_user', 'transport', 'playbook',
|
||||
'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks'
|
||||
'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks',
|
||||
'basedir'
|
||||
]
|
||||
|
||||
# to catch typos and so forth -- these are userland names
|
||||
@ -42,7 +43,7 @@ class Play(object):
|
||||
|
||||
# *************************************************
|
||||
|
||||
def __init__(self, playbook, ds):
|
||||
def __init__(self, playbook, ds, basedir):
|
||||
''' constructor loads from a play datastructure '''
|
||||
|
||||
for x in ds.keys():
|
||||
@ -56,25 +57,25 @@ class Play(object):
|
||||
raise errors.AnsibleError('hosts declaration is required')
|
||||
elif isinstance(hosts, list):
|
||||
hosts = ';'.join(hosts)
|
||||
hosts = utils.template(hosts, playbook.extra_vars)
|
||||
|
||||
hosts = utils.template(basedir, hosts, playbook.extra_vars)
|
||||
self._ds = ds
|
||||
self.playbook = playbook
|
||||
self.basedir = basedir
|
||||
self.hosts = hosts
|
||||
self.name = ds.get('name', self.hosts)
|
||||
self.vars = ds.get('vars', {})
|
||||
self.vars_files = ds.get('vars_files', [])
|
||||
self.vars_prompt = ds.get('vars_prompt', {})
|
||||
self.vars = self._get_vars(self.playbook.basedir)
|
||||
self.vars = self._get_vars()
|
||||
self._tasks = ds.get('tasks', [])
|
||||
self._handlers = ds.get('handlers', [])
|
||||
self.remote_user = utils.template(ds.get('user', self.playbook.remote_user), playbook.extra_vars)
|
||||
self.remote_user = utils.template(basedir, ds.get('user', self.playbook.remote_user), playbook.extra_vars)
|
||||
self.remote_port = ds.get('port', self.playbook.remote_port)
|
||||
self.sudo = ds.get('sudo', self.playbook.sudo)
|
||||
self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user)
|
||||
self.sudo_user = utils.template(basedir, ds.get('sudo_user', self.playbook.sudo_user), playbook.extra_vars)
|
||||
self.transport = ds.get('connection', self.playbook.transport)
|
||||
self.tags = ds.get('tags', None)
|
||||
self.gather_facts = ds.get('gather_facts', True)
|
||||
self.gather_facts = ds.get('gather_facts', None)
|
||||
self.serial = ds.get('serial', 0)
|
||||
|
||||
self._update_vars_files_for_host(None)
|
||||
@ -100,23 +101,29 @@ class Play(object):
|
||||
tasks = ds.get(keyname, [])
|
||||
results = []
|
||||
for x in tasks:
|
||||
task_vars = self.vars.copy()
|
||||
if 'include' in x:
|
||||
task_vars = self.vars.copy()
|
||||
tokens = shlex.split(x['include'])
|
||||
for t in tokens[1:]:
|
||||
(k,v) = t.split("=", 1)
|
||||
task_vars[k] = utils.template(v, task_vars)
|
||||
include_file = utils.template(tokens[0], task_vars)
|
||||
data = utils.parse_yaml_from_file(utils.path_dwim(self.playbook.basedir, include_file))
|
||||
if 'with_items' in x:
|
||||
items = utils.varReplaceWithItems(self.basedir, x['with_items'], task_vars)
|
||||
else:
|
||||
items = ['']
|
||||
for item in items:
|
||||
mv = task_vars.copy()
|
||||
mv['item'] = item
|
||||
for t in tokens[1:]:
|
||||
(k,v) = t.split("=", 1)
|
||||
mv[k] = utils.varReplaceWithItems(self.basedir, v, mv)
|
||||
include_file = utils.template(self.basedir, tokens[0], mv)
|
||||
data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file))
|
||||
for y in data:
|
||||
results.append(Task(self,y,module_vars=mv.copy()))
|
||||
elif type(x) == dict:
|
||||
data = [x]
|
||||
task_vars = self.vars.copy()
|
||||
results.append(Task(self,x,module_vars=task_vars))
|
||||
else:
|
||||
raise Exception("unexpected task type")
|
||||
|
||||
for y in data:
|
||||
mv = task_vars.copy()
|
||||
results.append(Task(self,y,module_vars=mv))
|
||||
|
||||
for x in results:
|
||||
if self.tags is not None:
|
||||
x.tags.extend(self.tags)
|
||||
@ -135,7 +142,7 @@ class Play(object):
|
||||
|
||||
# *************************************************
|
||||
|
||||
def _get_vars(self, dirname):
|
||||
def _get_vars(self):
|
||||
''' load the vars section from a play, accounting for all sorts of variable features
|
||||
including loading from yaml files, prompting, and conditional includes of the first
|
||||
file found in a list. '''
|
||||
@ -164,7 +171,7 @@ class Play(object):
|
||||
raise errors.AnsibleError("'vars_prompt' item is missing 'name:'")
|
||||
|
||||
vname = var['name']
|
||||
prompt = "%s: " % var.get("prompt", vname)
|
||||
prompt = var.get("prompt", vname)
|
||||
private = var.get("private", True)
|
||||
|
||||
confirm = var.get("confirm", False)
|
||||
@ -260,11 +267,11 @@ class Play(object):
|
||||
found = False
|
||||
sequence = []
|
||||
for real_filename in filename:
|
||||
filename2 = utils.template(real_filename, self.vars)
|
||||
filename2 = utils.template(self.basedir, real_filename, self.vars)
|
||||
filename3 = filename2
|
||||
if host is not None:
|
||||
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
|
||||
filename4 = utils.path_dwim(self.playbook.basedir, filename3)
|
||||
filename3 = utils.template(self.basedir, filename2, self.playbook.SETUP_CACHE[host])
|
||||
filename4 = utils.path_dwim(self.basedir, filename3)
|
||||
sequence.append(filename4)
|
||||
if os.path.exists(filename4):
|
||||
found = True
|
||||
@ -293,13 +300,13 @@ class Play(object):
|
||||
else:
|
||||
# just one filename supplied, load it!
|
||||
|
||||
filename2 = utils.template(filename, self.vars)
|
||||
filename2 = utils.template(self.basedir, filename, self.vars)
|
||||
filename3 = filename2
|
||||
if host is not None:
|
||||
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
|
||||
filename4 = utils.path_dwim(self.playbook.basedir, filename3)
|
||||
filename3 = utils.template(self.basedir, filename2, self.playbook.SETUP_CACHE[host])
|
||||
filename4 = utils.path_dwim(self.basedir, filename3)
|
||||
if self._has_vars_in(filename4):
|
||||
return
|
||||
continue
|
||||
new_vars = utils.parse_yaml_from_file(filename4)
|
||||
if new_vars:
|
||||
if type(new_vars) != dict:
|
||||
|
||||
@ -26,21 +26,39 @@ class Task(object):
|
||||
'notify', 'module_name', 'module_args', 'module_vars',
|
||||
'play', 'notified_by', 'tags', 'register', 'with_items',
|
||||
'delegate_to', 'first_available_file', 'ignore_errors',
|
||||
'local_action'
|
||||
'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass',
|
||||
'items_lookup_plugin', 'items_lookup_terms'
|
||||
]
|
||||
|
||||
# to prevent typos and such
|
||||
VALID_KEYS = [
|
||||
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items',
|
||||
'first_available_file', 'include', 'tags', 'register', 'ignore_errors',
|
||||
'delegate_to', 'local_action'
|
||||
'delegate_to', 'local_action', 'transport', 'sudo', 'sudo_user',
|
||||
'sudo_pass'
|
||||
]
|
||||
|
||||
def __init__(self, play, ds, module_vars=None):
|
||||
''' constructor loads from a task or handler datastructure '''
|
||||
|
||||
for x in ds.keys():
|
||||
if not x in Task.VALID_KEYS:
|
||||
|
||||
# code to allow for saying "modulename: args" versus "action: modulename args"
|
||||
if x in play.playbook.modules_list:
|
||||
ds['action'] = x + " " + ds[x]
|
||||
ds.pop(x)
|
||||
|
||||
# code to allow "with_glob" and to reference a lookup plugin named glob
|
||||
elif x.startswith("with_") and x != 'with_items':
|
||||
plugin_name = x.replace("with_","")
|
||||
if plugin_name in play.playbook.lookup_plugins_list:
|
||||
ds['items_lookup_plugin'] = plugin_name
|
||||
ds['items_lookup_terms'] = ds[x]
|
||||
ds.pop(x)
|
||||
else:
|
||||
raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name))
|
||||
|
||||
elif not x in Task.VALID_KEYS:
|
||||
raise errors.AnsibleError("%s is not a legal parameter in an Ansible task or handler" % x)
|
||||
|
||||
self.module_vars = module_vars
|
||||
@ -50,7 +68,14 @@ class Task(object):
|
||||
self.name = ds.get('name', None)
|
||||
self.tags = [ 'all' ]
|
||||
self.register = ds.get('register', None)
|
||||
|
||||
self.sudo = ds.get('sudo', play.sudo)
|
||||
if self.sudo is True:
|
||||
self.sudo_user = ds.get('sudo_user', play.sudo_user)
|
||||
self.sudo_pass = ds.get('sudo_pass', play.playbook.sudo_pass)
|
||||
else:
|
||||
self.sudo_user = None
|
||||
self.sudo_pass = None
|
||||
|
||||
# Both are defined
|
||||
if ('action' in ds) and ('local_action' in ds):
|
||||
raise errors.AnsibleError("the 'action' and 'local_action' attributes can not be used together")
|
||||
@ -64,6 +89,14 @@ class Task(object):
|
||||
else:
|
||||
self.action = ds.get('action', '')
|
||||
self.delegate_to = ds.get('delegate_to', None)
|
||||
self.transport = ds.get('transport', play.transport)
|
||||
|
||||
# delegate_to can use variables
|
||||
if not (self.delegate_to is None):
|
||||
self.delegate_to = utils.template(None, self.delegate_to, self.module_vars)
|
||||
# delegate_to: localhost should use local transport
|
||||
if self.delegate_to in ['127.0.0.1', 'localhost']:
|
||||
self.transport = 'local'
|
||||
|
||||
# notified by is used by Playbook code to flag which hosts
|
||||
# need to run a notifier
|
||||
@ -81,6 +114,10 @@ class Task(object):
|
||||
self.first_available_file = ds.get('first_available_file', None)
|
||||
self.with_items = ds.get('with_items', None)
|
||||
|
||||
self.items_lookup_plugin = ds.get('items_lookup_plugin', None)
|
||||
self.items_lookup_terms = ds.get('items_lookup_terms', None)
|
||||
|
||||
|
||||
self.ignore_errors = ds.get('ignore_errors', False)
|
||||
|
||||
# notify can be a string or a list, store as a list
|
||||
@ -101,12 +138,13 @@ class Task(object):
|
||||
# allow the user to list comma delimited tags
|
||||
import_tags = import_tags.split(",")
|
||||
|
||||
self.name = utils.template(self.name, self.module_vars)
|
||||
self.action = utils.template(self.action, self.module_vars)
|
||||
self.name = utils.template(None, self.name, self.module_vars)
|
||||
self.action = utils.template(None, self.action, self.module_vars)
|
||||
|
||||
# handle mutually incompatible options
|
||||
if self.with_items is not None and self.first_available_file is not None:
|
||||
raise errors.AnsibleError("with_items and first_available_file are mutually incompatible in a single task")
|
||||
incompatibles = [ x for x in [ self.with_items, self.first_available_file, self.items_lookup_plugin ] if x is not None ]
|
||||
if len(incompatibles) > 1:
|
||||
raise errors.AnsibleError("with_items, with_(plugin), and first_available_file are mutually incompatible in a single task")
|
||||
|
||||
# make first_available_file accessable to Runner code
|
||||
if self.first_available_file:
|
||||
@ -117,6 +155,10 @@ class Task(object):
|
||||
self.with_items = [ ]
|
||||
self.module_vars['items'] = self.with_items
|
||||
|
||||
if self.items_lookup_plugin is not None:
|
||||
self.module_vars['items_lookup_plugin'] = self.items_lookup_plugin
|
||||
self.module_vars['items_lookup_terms'] = self.items_lookup_terms
|
||||
|
||||
# allow runner to see delegate_to option
|
||||
self.module_vars['delegate_to'] = self.delegate_to
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ import tempfile
|
||||
import time
|
||||
import collections
|
||||
import socket
|
||||
import base64
|
||||
import sys
|
||||
|
||||
import ansible.constants as C
|
||||
import ansible.inventory
|
||||
@ -43,6 +45,15 @@ try:
|
||||
except ImportError:
|
||||
HAS_ATFORK=False
|
||||
|
||||
dirname = os.path.dirname(__file__)
|
||||
action_plugin_list = utils.import_plugins(os.path.join(dirname, 'action_plugins'))
|
||||
for i in reversed(C.DEFAULT_ACTION_PLUGIN_PATH.split(os.pathsep)):
|
||||
action_plugin_list.update(utils.import_plugins(i))
|
||||
lookup_plugin_list = utils.import_plugins(os.path.join(dirname, 'lookup_plugins'))
|
||||
for i in reversed(C.DEFAULT_LOOKUP_PLUGIN_PATH.split(os.pathsep)):
|
||||
lookup_plugin_list.update(utils.import_plugins(i))
|
||||
|
||||
|
||||
################################################
|
||||
|
||||
def _executor_hook(job_queue, result_queue):
|
||||
@ -64,6 +75,23 @@ def _executor_hook(job_queue, result_queue):
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
class HostVars(dict):
|
||||
''' A special view of setup_cache that adds values from the inventory when needed. '''
|
||||
|
||||
def __init__(self, setup_cache, inventory):
|
||||
self.setup_cache = setup_cache
|
||||
self.inventory = inventory
|
||||
self.lookup = {}
|
||||
|
||||
self.update(setup_cache)
|
||||
|
||||
def __getitem__(self, host):
|
||||
if not host in self.lookup:
|
||||
result = self.inventory.get_variables(host)
|
||||
result.update(self.setup_cache.get(host, {}))
|
||||
self.lookup[host] = result
|
||||
return self.lookup[host]
|
||||
|
||||
class Runner(object):
|
||||
''' core API interface to ansible '''
|
||||
|
||||
@ -79,7 +107,7 @@ class Runner(object):
|
||||
pattern=C.DEFAULT_PATTERN, # which hosts? ex: 'all', 'acme.example.org'
|
||||
remote_user=C.DEFAULT_REMOTE_USER, # ex: 'username'
|
||||
remote_pass=C.DEFAULT_REMOTE_PASS, # ex: 'password123' or None if using key
|
||||
remote_port=C.DEFAULT_REMOTE_PORT, # if SSH on different ports
|
||||
remote_port=None, # if SSH on different ports
|
||||
private_key_file=C.DEFAULT_PRIVATE_KEY_FILE, # if not using keys/passwords
|
||||
sudo_pass=C.DEFAULT_SUDO_PASS, # ex: 'password123' or None
|
||||
background=0, # async poll every X seconds, else 0 for non-async
|
||||
@ -136,6 +164,19 @@ class Runner(object):
|
||||
# ensure we are using unique tmp paths
|
||||
random.seed()
|
||||
|
||||
# instantiate plugin classes
|
||||
self.action_plugins = {}
|
||||
self.lookup_plugins = {}
|
||||
for (k,v) in action_plugin_list.iteritems():
|
||||
self.action_plugins[k] = v.ActionModule(self)
|
||||
for (k,v) in lookup_plugin_list.iteritems():
|
||||
self.lookup_plugins[k] = v.LookupModule(self)
|
||||
|
||||
for (k,v) in utils.import_plugins(os.path.join(self.basedir, 'action_plugins')).iteritems():
|
||||
self.action_plugins[k] = v.ActionModule(self)
|
||||
for (k,v) in utils.import_plugins(os.path.join(self.basedir, 'lookup_plugins')).iteritems():
|
||||
self.lookup_plugins[k] = v.LookupModule(self)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _delete_remote_files(self, conn, files):
|
||||
@ -145,11 +186,11 @@ class Runner(object):
|
||||
# ability to turn off temp file deletion for debug purposes
|
||||
return
|
||||
|
||||
if type(files) == str:
|
||||
if type(files) in [ str, unicode ]:
|
||||
files = [ files ]
|
||||
for filename in files:
|
||||
if filename.find('/tmp/') == -1:
|
||||
raise Exception("not going to happen")
|
||||
raise Exception("safeguard deletion, removal of %s is not going to happen" % filename)
|
||||
self._low_level_exec_command(conn, "rm -rf %s" % filename, None)
|
||||
|
||||
# *****************************************************
|
||||
@ -162,7 +203,10 @@ class Runner(object):
|
||||
|
||||
afd, afile = tempfile.mkstemp()
|
||||
afo = os.fdopen(afd, 'w')
|
||||
afo.write(data.encode('utf8'))
|
||||
try:
|
||||
afo.write(data.encode('utf8'))
|
||||
except:
|
||||
raise errors.AnsibleError("failure encoding into utf-8")
|
||||
afo.flush()
|
||||
afo.close()
|
||||
|
||||
@ -180,10 +224,13 @@ class Runner(object):
|
||||
|
||||
''' runs a module that has already been transferred '''
|
||||
|
||||
if type(args) == dict:
|
||||
args = utils.jsonify(args,format=True)
|
||||
# hack to support fireball mode
|
||||
if module_name == 'fireball':
|
||||
args = "%s password=%s" % (args, base64.b64encode(str(utils.key_for_hostname(conn.host))))
|
||||
if 'port' not in args:
|
||||
args += " port=%s" % C.ZEROMQ_PORT
|
||||
|
||||
(remote_module_path, is_new_style) = self._copy_module(conn, tmp, module_name, inject)
|
||||
(remote_module_path, is_new_style) = self._copy_module(conn, tmp, module_name, args, inject)
|
||||
cmd = "chmod u+x %s" % remote_module_path
|
||||
if self.sudo and self.sudo_user != 'root':
|
||||
# deal with possible umask issues once sudo'ed to other user
|
||||
@ -192,7 +239,7 @@ class Runner(object):
|
||||
|
||||
cmd = ""
|
||||
if not is_new_style:
|
||||
args = utils.template(args, inject)
|
||||
args = utils.template(self.basedir, args, inject)
|
||||
argsfile = self._transfer_str(conn, tmp, 'arguments', args)
|
||||
if async_jid is None:
|
||||
cmd = "%s %s" % (remote_module_path, argsfile)
|
||||
@ -209,217 +256,6 @@ class Runner(object):
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_raw(self, conn, tmp, inject=None):
|
||||
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
|
||||
return ReturnData(conn=conn, result=dict(
|
||||
stdout=self._low_level_exec_command(conn, self.module_args.encode('utf-8'), tmp, sudoable = True)
|
||||
))
|
||||
|
||||
# ***************************************************
|
||||
|
||||
def _execute_normal_module(self, conn, tmp, module_name, inject=None):
|
||||
''' transfer & execute a module that is not 'copy' or 'template' '''
|
||||
|
||||
# shell and command are the same module
|
||||
if module_name == 'shell':
|
||||
module_name = 'command'
|
||||
self.module_args += " #USE_SHELL"
|
||||
|
||||
vv("REMOTE_MODULE %s %s" % (module_name, self.module_args), host=conn.host)
|
||||
exec_rc = self._execute_module(conn, tmp, module_name, self.module_args, inject=inject)
|
||||
return exec_rc
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_async_module(self, conn, tmp, module_name, inject=None):
|
||||
''' transfer the given module name, plus the async module, then run it '''
|
||||
|
||||
# shell and command module are the same
|
||||
module_args = self.module_args
|
||||
if module_name == 'shell':
|
||||
module_name = 'command'
|
||||
module_args += " #USE_SHELL"
|
||||
|
||||
(module_path, is_new_style) = self._copy_module(conn, tmp, module_name, inject)
|
||||
self._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp)
|
||||
|
||||
return self._execute_module(conn, tmp, 'async_wrapper', module_args,
|
||||
async_module=module_path,
|
||||
async_jid=self.generated_jid,
|
||||
async_limit=self.background,
|
||||
inject=inject
|
||||
)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_copy(self, conn, tmp, inject=None):
|
||||
''' handler for file transfer operations '''
|
||||
|
||||
# load up options
|
||||
options = utils.parse_kv(self.module_args)
|
||||
source = options.get('src', None)
|
||||
dest = options.get('dest', None)
|
||||
if (source is None and not 'first_available_file' in inject) or dest is None:
|
||||
result=dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# if we have first_available_file in our vars
|
||||
# look up the files and use the first one we find as src
|
||||
if 'first_available_file' in inject:
|
||||
found = False
|
||||
for fn in inject.get('first_available_file'):
|
||||
fn = utils.template(fn, inject)
|
||||
if os.path.exists(fn):
|
||||
source = fn
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
results=dict(failed=True, msg="could not find src in first_available_file list")
|
||||
return ReturnData(conn=conn, results=results)
|
||||
|
||||
source = utils.template(source, inject)
|
||||
source = utils.path_dwim(self.basedir, source)
|
||||
|
||||
local_md5 = utils.md5(source)
|
||||
if local_md5 is None:
|
||||
result=dict(failed=True, msg="could not find src=%s" % source)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
remote_md5 = self._remote_md5(conn, tmp, dest)
|
||||
|
||||
exec_rc = None
|
||||
if local_md5 != remote_md5:
|
||||
# transfer the file to a remote tmp location
|
||||
tmp_src = tmp + os.path.basename(source)
|
||||
conn.put_file(source, tmp_src)
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.sudo and self.sudo_user != 'root':
|
||||
self._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp)
|
||||
|
||||
# run the copy module
|
||||
self.module_args = "%s src=%s" % (self.module_args, tmp_src)
|
||||
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
|
||||
|
||||
else:
|
||||
# no need to transfer the file, already correct md5
|
||||
result = dict(changed=False, md5sum=remote_md5, transferred=False)
|
||||
return ReturnData(conn=conn, result=result).daisychain('file')
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_fetch(self, conn, tmp, inject=None):
|
||||
''' handler for fetch operations '''
|
||||
|
||||
# load up options
|
||||
options = utils.parse_kv(self.module_args)
|
||||
source = options.get('src', None)
|
||||
dest = options.get('dest', None)
|
||||
if source is None or dest is None:
|
||||
results = dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(conn=conn, result=results)
|
||||
|
||||
# apply templating to source argument
|
||||
source = utils.template(source, inject)
|
||||
# apply templating to dest argument
|
||||
dest = utils.template(dest, inject)
|
||||
|
||||
# files are saved in dest dir, with a subdir for each host, then the filename
|
||||
dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), conn.host, source)
|
||||
dest = dest.replace("//","/")
|
||||
|
||||
# calculate md5 sum for the remote file
|
||||
remote_md5 = self._remote_md5(conn, tmp, source)
|
||||
|
||||
# these don't fail because you may want to transfer a log file that possibly MAY exist
|
||||
# but keep going to fetch other log files
|
||||
if remote_md5 == '0':
|
||||
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
if remote_md5 == '1':
|
||||
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
if remote_md5 == '2':
|
||||
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# calculate md5 sum for the local file
|
||||
local_md5 = utils.md5(dest)
|
||||
|
||||
if remote_md5 != local_md5:
|
||||
# create the containing directories, if needed
|
||||
if not os.path.isdir(os.path.dirname(dest)):
|
||||
os.makedirs(os.path.dirname(dest))
|
||||
|
||||
# fetch the file and check for changes
|
||||
conn.fetch_file(source, dest)
|
||||
new_md5 = utils.md5(dest)
|
||||
if new_md5 != remote_md5:
|
||||
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
result = dict(changed=True, md5sum=new_md5)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
else:
|
||||
result = dict(changed=False, md5sum=local_md5, file=source)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_template(self, conn, tmp, inject=None):
|
||||
''' handler for template operations '''
|
||||
|
||||
if not self.is_playbook:
|
||||
raise errors.AnsibleError("in current versions of ansible, templates are only usable in playbooks")
|
||||
|
||||
# load up options
|
||||
options = utils.parse_kv(self.module_args)
|
||||
source = options.get('src', None)
|
||||
dest = options.get('dest', None)
|
||||
if (source is None and 'first_available_file' not in inject) or dest is None:
|
||||
result = dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
# if we have first_available_file in our vars
|
||||
# look up the files and use the first one we find as src
|
||||
if 'first_available_file' in inject:
|
||||
found = False
|
||||
for fn in self.module_vars.get('first_available_file'):
|
||||
fn = utils.template(fn, inject)
|
||||
if os.path.exists(fn):
|
||||
source = fn
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
result = dict(failed=True, msg="could not find src in first_available_file list")
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
source = utils.template(source, inject)
|
||||
|
||||
# template the source data locally & transfer
|
||||
try:
|
||||
resultant = utils.template_from_file(self.basedir, source, inject)
|
||||
except Exception, e:
|
||||
result = dict(failed=True, msg=str(e))
|
||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||
|
||||
xfered = self._transfer_str(conn, tmp, 'source', resultant)
|
||||
|
||||
# run the copy module, queue the file module
|
||||
self.module_args = "%s src=%s dest=%s" % (self.module_args, xfered, dest)
|
||||
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _execute_assemble(self, conn, tmp, inject=None):
|
||||
''' handler for assemble operations '''
|
||||
|
||||
# FIXME: once assemble is ported over to the use the new common logic, this method
|
||||
# will be unneccessary as it can decide to daisychain via it's own module returns.
|
||||
# and this function can be deleted.
|
||||
|
||||
return self._execute_module(conn, tmp, 'assemble', self.module_args, inject=inject).daisychain('file')
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _executor(self, host):
|
||||
''' handler for multiprocessing library '''
|
||||
|
||||
@ -446,24 +282,36 @@ class Runner(object):
|
||||
''' executes any module one or more times '''
|
||||
|
||||
host_variables = self.inventory.get_variables(host)
|
||||
port = host_variables.get('ansible_ssh_port', self.remote_port)
|
||||
if self.transport in [ 'paramiko', 'ssh' ]:
|
||||
port = host_variables.get('ansible_ssh_port', self.remote_port)
|
||||
if port is None:
|
||||
port = C.DEFAULT_REMOTE_PORT
|
||||
else:
|
||||
# fireball, local, etc
|
||||
port = self.remote_port
|
||||
|
||||
inject = {}
|
||||
inject.update(host_variables)
|
||||
inject.update(self.module_vars)
|
||||
inject.update(self.setup_cache[host])
|
||||
inject['hostvars'] = self.setup_cache
|
||||
inject['hostvars'] = HostVars(self.setup_cache, self.inventory)
|
||||
inject['group_names'] = host_variables.get('group_names', [])
|
||||
inject['groups'] = self.inventory.groups_list()
|
||||
|
||||
# allow with_items to work in playbooks...
|
||||
# apt and yum are converted into a single call, others run in a loop
|
||||
|
||||
items = self.module_vars.get('items', [])
|
||||
if isinstance(items, basestring) and items.startswith("$"):
|
||||
items = items.replace("$","")
|
||||
if items in inject:
|
||||
items = inject[items]
|
||||
else:
|
||||
raise errors.AnsibleError("unbound variable in with_items: %s" % items)
|
||||
items = utils.varReplaceWithItems(self.basedir, items, inject)
|
||||
|
||||
# if we instead said 'with_foo' and there is a lookup module named foo...
|
||||
items_plugin = self.module_vars.get('items_lookup_plugin', None)
|
||||
if items_plugin is not None:
|
||||
items_terms = self.module_vars.get('items_lookup_terms', '')
|
||||
if items_plugin in self.lookup_plugins:
|
||||
items_terms = utils.template(self.basedir, items_terms, inject)
|
||||
items = self.lookup_plugins[items_plugin].run(items_terms)
|
||||
|
||||
if type(items) != list:
|
||||
raise errors.AnsibleError("with_items only takes a list: %s" % items)
|
||||
|
||||
@ -475,7 +323,7 @@ class Runner(object):
|
||||
# logic to decide how to run things depends on whether with_items is used
|
||||
|
||||
if len(items) == 0:
|
||||
return self._executor_internal_inner(host, inject, port)
|
||||
return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port)
|
||||
else:
|
||||
# executing using with_items, so make multiple calls
|
||||
# TODO: refactor
|
||||
@ -484,17 +332,13 @@ class Runner(object):
|
||||
all_changed = False
|
||||
all_failed = False
|
||||
results = []
|
||||
# Save module name and args since daisy-chaining can overwrite them
|
||||
module_name = self.module_name
|
||||
module_args = self.module_args
|
||||
for x in items:
|
||||
self.module_name = module_name
|
||||
self.module_args = module_args
|
||||
inject['item'] = x
|
||||
result = self._executor_internal_inner(host, inject, port)
|
||||
result = self._executor_internal_inner(host, self.module_name, self.module_args, inject, port)
|
||||
results.append(result.result)
|
||||
if result.comm_ok == False:
|
||||
all_comm_ok = False
|
||||
all_failed = True
|
||||
break
|
||||
for x in results:
|
||||
if x.get('changed') == True:
|
||||
@ -502,7 +346,7 @@ class Runner(object):
|
||||
if (x.get('failed') == True) or (('rc' in x) and (x['rc'] != 0)):
|
||||
all_failed = True
|
||||
break
|
||||
msg = 'All items succeeded'
|
||||
msg = 'All items completed'
|
||||
if all_failed:
|
||||
msg = "One or more items failed."
|
||||
rd_result = dict(failed=all_failed, changed=all_changed, results=results, msg=msg)
|
||||
@ -512,76 +356,84 @@ class Runner(object):
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _executor_internal_inner(self, host, inject, port, is_chained=False):
|
||||
def _executor_internal_inner(self, host, module_name, module_args, inject, port, is_chained=False):
|
||||
''' decides how to invoke a module '''
|
||||
|
||||
# FIXME: temporary, need to refactor to pass as parameters versus reassigning
|
||||
prev_module_name = self.module_name
|
||||
prev_module_args = self.module_args
|
||||
|
||||
# special non-user/non-fact variables:
|
||||
# 'groups' variable is a list of host name in each group
|
||||
# 'hostvars' variable contains variables for each host name
|
||||
# ... and is set elsewhere
|
||||
# 'inventory_hostname' is also set elsewhere
|
||||
group_hosts = {}
|
||||
for g in self.inventory.groups:
|
||||
group_hosts[g.name] = [ h.name for h in g.hosts ]
|
||||
inject['groups'] = group_hosts
|
||||
inject['groups'] = self.inventory.groups_list()
|
||||
|
||||
# allow module args to work as a dictionary
|
||||
# though it is usually a string
|
||||
new_args = ""
|
||||
if type(self.module_args) == dict:
|
||||
for (k,v) in self.module_args.iteritems():
|
||||
if type(module_args) == dict:
|
||||
for (k,v) in module_args.iteritems():
|
||||
new_args = new_args + "%s='%s' " % (k,v)
|
||||
self.module_args = new_args
|
||||
self.module_args = utils.template(self.module_args, inject)
|
||||
module_args = new_args
|
||||
|
||||
conditional = utils.template(self.conditional, inject)
|
||||
if not eval(conditional):
|
||||
conditional = utils.template(self.basedir, self.conditional, inject)
|
||||
if not utils.check_conditional(conditional):
|
||||
result = utils.jsonify(dict(skipped=True))
|
||||
self.callbacks.on_skipped(host, inject.get('item',None))
|
||||
return ReturnData(host=host, result=result)
|
||||
|
||||
conn = None
|
||||
actual_host = host
|
||||
actual_host = inject.get('ansible_ssh_host', host)
|
||||
actual_port = port
|
||||
if self.transport in ['paramiko', 'ssh' ]:
|
||||
actual_port = inject.get('ansible_ssh_port', port)
|
||||
|
||||
# the delegated host may have different SSH port configured, etc
|
||||
# and we need to transfer those, and only those, variables
|
||||
delegate_to = inject.get('delegate_to', None)
|
||||
if delegate_to is not None:
|
||||
delegate_to = utils.template(self.basedir, delegate_to, inject)
|
||||
try:
|
||||
delegate_info = inject['hostvars'][delegate_to]
|
||||
actual_host = delegate_info.get('ansible_ssh_host', delegate_to)
|
||||
actual_port = delegate_info.get('ansible_ssh_port', port)
|
||||
except errors.AnsibleError:
|
||||
actual_host = delegate_to
|
||||
|
||||
try:
|
||||
delegate_to = inject.get('delegate_to', None)
|
||||
if delegate_to is not None:
|
||||
actual_host = delegate_to
|
||||
conn = self.connector.connect(actual_host, port)
|
||||
if delegate_to is not None:
|
||||
conn._delegate_for = host
|
||||
transport = None # use Runner setting
|
||||
if delegate_to and actual_host in [ '127.0.0.1', 'localhost' ]:
|
||||
transport = 'local'
|
||||
if actual_port is not None:
|
||||
actual_port = int(actual_port)
|
||||
conn = self.connector.connect(actual_host, actual_port, transport=transport)
|
||||
if delegate_to:
|
||||
conn.delegate = host
|
||||
|
||||
|
||||
except errors.AnsibleConnectionFailed, e:
|
||||
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
||||
return ReturnData(host=host, comm_ok=False, result=result)
|
||||
|
||||
module_name = utils.template(self.module_name, inject)
|
||||
module_name = utils.template(self.basedir, module_name, inject)
|
||||
module_args = utils.template(self.basedir, module_args, inject, expand_lists=True)
|
||||
|
||||
tmp = ''
|
||||
if self.module_name != 'raw':
|
||||
tmp = self._make_tmp_path(conn)
|
||||
result = None
|
||||
|
||||
handler = getattr(self, "_execute_%s" % self.module_name, None)
|
||||
handler = self.action_plugins.get(module_name, None)
|
||||
if handler:
|
||||
result = handler(conn, tmp, inject=inject)
|
||||
if self.background != 0:
|
||||
raise errors.AnsibleError("async mode is not supported with the %s module" % module_name)
|
||||
result = handler.run(conn, tmp, module_name, module_args, inject)
|
||||
else:
|
||||
if self.background == 0:
|
||||
result = self._execute_normal_module(conn, tmp, module_name, inject=inject)
|
||||
result = self.action_plugins['normal'].run(conn, tmp, module_name, module_args, inject)
|
||||
else:
|
||||
result = self._execute_async_module(conn, tmp, module_name, inject=inject)
|
||||
result = self.action_plugins['async'].run(conn, tmp, module_name, module_args, inject)
|
||||
|
||||
if result.is_successful() and 'daisychain' in result.result:
|
||||
self.module_name = result.result['daisychain']
|
||||
if 'daisychain_args' in result.result:
|
||||
self.module_args = result.result['daisychain_args']
|
||||
result2 = self._executor_internal_inner(host, inject, port, is_chained=True)
|
||||
|
||||
# FIXME: remove this hack
|
||||
self.module_name = prev_module_name
|
||||
self.module_args = prev_module_args
|
||||
result2 = self._executor_internal_inner(host, result.result['daisychain'], result.result.get('daisychain_args', {}), inject, port, is_chained=True)
|
||||
|
||||
changed = False
|
||||
if result.result.get('changed',False) or result2.result.get('changed',False):
|
||||
@ -604,8 +456,8 @@ class Runner(object):
|
||||
result.result['item'] = inject['item']
|
||||
|
||||
result.result['invocation'] = dict(
|
||||
module_args=self.module_args,
|
||||
module_name=self.module_name
|
||||
module_args=module_args,
|
||||
module_name=module_name
|
||||
)
|
||||
|
||||
if is_chained:
|
||||
@ -628,12 +480,12 @@ class Runner(object):
|
||||
sudo_user = self.sudo_user
|
||||
stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudo_user, sudoable=sudoable)
|
||||
|
||||
if type(stdout) != str:
|
||||
if type(stdout) not in [ str, unicode ]:
|
||||
out = "\n".join(stdout.readlines())
|
||||
else:
|
||||
out = stdout
|
||||
|
||||
if type(stderr) != str:
|
||||
if type(stderr) not in [ str, unicode ]:
|
||||
err = "\n".join(stderr.readlines())
|
||||
else:
|
||||
err = stderr
|
||||
@ -655,8 +507,17 @@ class Runner(object):
|
||||
cmd = " || ".join(md5s)
|
||||
cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path)
|
||||
data = self._low_level_exec_command(conn, cmd, tmp, sudoable=False)
|
||||
data = utils.last_non_blank_line(data)
|
||||
return data.split()[0]
|
||||
data2 = utils.last_non_blank_line(data)
|
||||
try:
|
||||
return data2.split()[0]
|
||||
except IndexError:
|
||||
sys.stderr.write("warning: md5sum command failed unusually, please report this to the list so it can be fixed\n")
|
||||
sys.stderr.write("command: %s\n" % md5s)
|
||||
sys.stderr.write("----\n")
|
||||
sys.stderr.write("output: %s\n" % data)
|
||||
sys.stderr.write("----\n")
|
||||
# this will signal that it changed and allow things to keep going
|
||||
return "INVALIDMD5SUM"
|
||||
|
||||
# *****************************************************
|
||||
|
||||
@ -676,25 +537,27 @@ class Runner(object):
|
||||
cmd += ' && echo %s' % basetmp
|
||||
|
||||
result = self._low_level_exec_command(conn, cmd, None, sudoable=False)
|
||||
return utils.last_non_blank_line(result).strip() + '/'
|
||||
rc = utils.last_non_blank_line(result).strip() + '/'
|
||||
return rc
|
||||
|
||||
|
||||
# *****************************************************
|
||||
|
||||
def _copy_module(self, conn, tmp, module, inject):
|
||||
def _copy_module(self, conn, tmp, module_name, module_args, inject):
|
||||
''' transfer a module over SFTP, does not run it '''
|
||||
|
||||
if module.startswith("/"):
|
||||
raise errors.AnsibleFileNotFound("%s is not a module" % module)
|
||||
if module_name.startswith("/"):
|
||||
raise errors.AnsibleFileNotFound("%s is not a module" % module_name)
|
||||
|
||||
# Search module path(s) for named module.
|
||||
for module_path in self.module_path.split(os.pathsep):
|
||||
in_path = os.path.expanduser(os.path.join(module_path, module))
|
||||
in_path = os.path.expanduser(os.path.join(module_path, module_name))
|
||||
if os.path.exists(in_path):
|
||||
break
|
||||
else:
|
||||
raise errors.AnsibleFileNotFound("module %s not found in %s" % (module, self.module_path))
|
||||
raise errors.AnsibleFileNotFound("module %s not found in %s" % (module_name, self.module_path))
|
||||
|
||||
out_path = os.path.join(tmp, module)
|
||||
out_path = os.path.join(tmp, module_name)
|
||||
|
||||
module_data = ""
|
||||
is_new_style=False
|
||||
@ -704,7 +567,7 @@ class Runner(object):
|
||||
if module_common.REPLACER in module_data:
|
||||
is_new_style=True
|
||||
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
|
||||
encoded_args = "\"\"\"%s\"\"\"" % utils.template(self.module_args, inject).replace("\"","\\\"")
|
||||
encoded_args = "\"\"\"%s\"\"\"" % module_args.replace("\"","\\\"")
|
||||
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
|
||||
|
||||
# use the correct python interpreter for the host
|
||||
@ -715,7 +578,7 @@ class Runner(object):
|
||||
module_lines[0] = "#!%s" % interpreter
|
||||
module_data = "\n".join(module_lines)
|
||||
|
||||
self._transfer_str(conn, tmp, module, module_data)
|
||||
self._transfer_str(conn, tmp, module_name, module_data)
|
||||
return (out_path, is_new_style)
|
||||
|
||||
# *****************************************************
|
||||
@ -723,9 +586,10 @@ class Runner(object):
|
||||
def _parallel_exec(self, hosts):
|
||||
''' handles mulitprocessing when more than 1 fork is required '''
|
||||
|
||||
job_queue = multiprocessing.Manager().Queue()
|
||||
manager = multiprocessing.Manager()
|
||||
job_queue = manager.Queue()
|
||||
[job_queue.put(i) for i in hosts]
|
||||
result_queue = multiprocessing.Manager().Queue()
|
||||
result_queue = manager.Queue()
|
||||
|
||||
workers = []
|
||||
for i in range(self.forks):
|
||||
@ -787,7 +651,26 @@ class Runner(object):
|
||||
|
||||
hosts = [ (self,x) for x in hosts ]
|
||||
results = None
|
||||
if self.forks > 1:
|
||||
|
||||
# Check if this is an action plugin. Some of them are designed
|
||||
# to be ran once per group of hosts. Example module: pause,
|
||||
# run once per hostgroup, rather than pausing once per each
|
||||
# host.
|
||||
p = self.action_plugins.get(self.module_name, None)
|
||||
if p and getattr(p, 'BYPASS_HOST_LOOP', None):
|
||||
# Expose the current hostgroup to the bypassing plugins
|
||||
self.host_set = hosts
|
||||
# We aren't iterating over all the hosts in this
|
||||
# group. So, just pick the first host in our group to
|
||||
# construct the conn object with.
|
||||
result_data = self._executor(hosts[0][1]).result
|
||||
# Create a ResultData item for each host in this group
|
||||
# using the returned result. If we didn't do this we would
|
||||
# get false reports of dark hosts.
|
||||
results = [ ReturnData(host=h[1], result=result_data, comm_ok=True) \
|
||||
for h in hosts ]
|
||||
del self.host_set
|
||||
elif self.forks > 1:
|
||||
results = self._parallel_exec(hosts)
|
||||
else:
|
||||
results = [ self._executor(h[1]) for h in hosts ]
|
||||
|
||||
0
lib/ansible/runner/action_plugins/__init__.py
Normal file
52
lib/ansible/runner/action_plugins/async.py
Normal file
@ -0,0 +1,52 @@
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# 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 os
|
||||
import pwd
|
||||
import random
|
||||
import traceback
|
||||
import tempfile
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible import module_common
|
||||
from ansible.runner.return_data import ReturnData
|
||||
|
||||
class ActionModule(object):
|
||||
|
||||
def __init__(self, runner):
|
||||
self.runner = runner
|
||||
|
||||
def run(self, conn, tmp, module_name, module_args, inject):
|
||||
''' transfer the given module name, plus the async module, then run it '''
|
||||
|
||||
# shell and command module are the same
|
||||
if module_name == 'shell':
|
||||
module_name = 'command'
|
||||
module_args += " #USE_SHELL"
|
||||
|
||||
(module_path, is_new_style) = self.runner._copy_module(conn, tmp, module_name, module_args, inject)
|
||||
self.runner._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp)
|
||||
|
||||
return self.runner._execute_module(conn, tmp, 'async_wrapper', module_args,
|
||||
async_module=module_path,
|
||||
async_jid=self.runner.generated_jid,
|
||||
async_limit=self.runner.background,
|
||||
inject=inject
|
||||
)
|
||||
|
||||
96
lib/ansible/runner/action_plugins/copy.py
Normal file
@ -0,0 +1,96 @@
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# 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 os
|
||||
import pwd
|
||||
import random
|
||||
import traceback
|
||||
import tempfile
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible import module_common
|
||||
from ansible.runner.return_data import ReturnData
|
||||
|
||||
class ActionModule(object):
|
||||
|
||||
def __init__(self, runner):
|
||||
self.runner = runner
|
||||
|
||||
def run(self, conn, tmp, module_name, module_args, inject):
|
||||
''' handler for file transfer operations '''
|
||||
|
||||
# load up options
|
||||
options = utils.parse_kv(module_args)
|
||||
source = options.get('src', None)
|
||||
dest = options.get('dest', None)
|
||||
|
||||
if dest.endswith("/"):
|
||||
base = os.path.basename(source)
|
||||
dest = os.path.join(dest, base)
|
||||
|
||||
if (source is None and not 'first_available_file' in inject) or dest is None:
|
||||
result=dict(failed=True, msg="src and dest are required")
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# if we have first_available_file in our vars
|
||||
# look up the files and use the first one we find as src
|
||||
if 'first_available_file' in inject:
|
||||
found = False
|
||||
for fn in inject.get('first_available_file'):
|
||||
fn = utils.template(self.runner.basedir, fn, inject)
|
||||
if os.path.exists(fn):
|
||||
source = fn
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
results=dict(failed=True, msg="could not find src in first_available_file list")
|
||||
return ReturnData(conn=conn, results=results)
|
||||
|
||||
source = utils.template(self.runner.basedir, source, inject)
|
||||
source = utils.path_dwim(self.runner.basedir, source)
|
||||
|
||||
local_md5 = utils.md5(source)
|
||||
if local_md5 is None:
|
||||
result=dict(failed=True, msg="could not find src=%s" % source)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
remote_md5 = self.runner._remote_md5(conn, tmp, dest)
|
||||
|
||||
exec_rc = None
|
||||
if local_md5 != remote_md5:
|
||||
# transfer the file to a remote tmp location
|
||||
tmp_src = tmp + os.path.basename(source)
|
||||
conn.put_file(source, tmp_src)
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root':
|
||||
self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp)
|
||||
|
||||
# run the copy module
|
||||
module_args = "%s src=%s" % (module_args, tmp_src)
|
||||
return self.runner._execute_module(conn, tmp, 'copy', module_args, inject=inject).daisychain('file', module_args)
|
||||
|
||||
else:
|
||||
# no need to transfer the file, already correct md5, but still need to set src so the file module
|
||||
# does not freak out. It's just the basename of the file.
|
||||
|
||||
tmp_src = tmp + os.path.basename(source)
|
||||
module_args = "%s src=%s" % (module_args, tmp_src)
|
||||
result = dict(changed=False, md5sum=remote_md5, transferred=False)
|
||||
return ReturnData(conn=conn, result=result).daisychain('file', module_args)
|
||||
|
||||