mirror of
https://github.com/ansible/awx.git
synced 2026-03-31 07:45:08 -02:30
Compare commits
1437 Commits
19.3.0
...
revert-124
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba8dd4d98 | ||
|
|
33e445f4f6 | ||
|
|
9bcb60d9e0 | ||
|
|
40109d58c7 | ||
|
|
2ef3f5f9e8 | ||
|
|
389c4a3180 | ||
|
|
cbb019ed09 | ||
|
|
bf5dfdaba7 | ||
|
|
0f7f8af9b8 | ||
|
|
0237402390 | ||
|
|
84d7fa882d | ||
|
|
cd2fae3471 | ||
|
|
8be64145f9 | ||
|
|
23d28fb4c8 | ||
|
|
aeffd6f393 | ||
|
|
ab6b4bad03 | ||
|
|
769c253ac2 | ||
|
|
8031b3d402 | ||
|
|
bd93ac7edd | ||
|
|
37ff9913d3 | ||
|
|
9cb44a7e52 | ||
|
|
6279295541 | ||
|
|
de17cff39c | ||
|
|
22ca49e673 | ||
|
|
008a4b4d30 | ||
|
|
8d4089c7f3 | ||
|
|
e296d0adad | ||
|
|
401b30b3ed | ||
|
|
20cc54694c | ||
|
|
e6ec0952fb | ||
|
|
db1dec3a98 | ||
|
|
1853d3850e | ||
|
|
1e57c84383 | ||
|
|
3cf120c6a7 | ||
|
|
fd671ecc9d | ||
|
|
a0d5f1fb03 | ||
|
|
ff882a322b | ||
|
|
b70231f7d0 | ||
|
|
93d1aa0a9d | ||
|
|
c586f8bbc6 | ||
|
|
26912a06d1 | ||
|
|
218a3d333b | ||
|
|
d2013bd416 | ||
|
|
6a3f9690b0 | ||
|
|
d59b6f834c | ||
|
|
cbea36745e | ||
|
|
ae7be525e1 | ||
|
|
5062ce1e61 | ||
|
|
566665ee8c | ||
|
|
96423af160 | ||
|
|
a01bef8d2c | ||
|
|
0522233892 | ||
|
|
63ea6bb5b3 | ||
|
|
c2715d7c29 | ||
|
|
783b744bdb | ||
|
|
f7982a0d64 | ||
|
|
2147ac226e | ||
|
|
6cc22786bc | ||
|
|
861a9f581e | ||
|
|
e57a8183ba | ||
|
|
8a7163ffad | ||
|
|
439b351c95 | ||
|
|
14afab918e | ||
|
|
ef8d4e73ae | ||
|
|
61f483ae32 | ||
|
|
21bed7473d | ||
|
|
31d8ddcf84 | ||
|
|
9419270897 | ||
|
|
f755d93a58 | ||
|
|
05df2ebad2 | ||
|
|
b44442c460 | ||
|
|
989b389ba4 | ||
|
|
5bd4aade0e | ||
|
|
470910b612 | ||
|
|
dbb81551c8 | ||
|
|
f7c5cb2979 | ||
|
|
babd6f0975 | ||
|
|
7bcceb7e98 | ||
|
|
c92619a2dc | ||
|
|
923cc671db | ||
|
|
db105c21e4 | ||
|
|
372aa36207 | ||
|
|
173318764b | ||
|
|
1dd535a859 | ||
|
|
e7d37b26f3 | ||
|
|
f4ef7d6927 | ||
|
|
7cbe112e4e | ||
|
|
c441db2aab | ||
|
|
fb292d9706 | ||
|
|
35a5f93182 | ||
|
|
116dc0c480 | ||
|
|
b87ba1c53d | ||
|
|
59691b71bb | ||
|
|
cc0bb3e401 | ||
|
|
7ef90bd9f4 | ||
|
|
f820c49b82 | ||
|
|
ac62d86f2a | ||
|
|
b9e67e7972 | ||
|
|
48a2ebd48c | ||
|
|
ee13ddd87d | ||
|
|
3fcf7429a3 | ||
|
|
51a8790d56 | ||
|
|
c231e4d05e | ||
|
|
987e5a084d | ||
|
|
70ac7b2920 | ||
|
|
bda335cb19 | ||
|
|
30c060cb27 | ||
|
|
9b0a2b0b76 | ||
|
|
2f82b75748 | ||
|
|
84fcd2ff00 | ||
|
|
3bc0c53e37 | ||
|
|
bc2dbcfce8 | ||
|
|
876edf54a3 | ||
|
|
b31bf8fab1 | ||
|
|
e8b2998578 | ||
|
|
8a92a01652 | ||
|
|
705f86f8cf | ||
|
|
9ab6a6d57e | ||
|
|
791eb4c1e1 | ||
|
|
870ca29388 | ||
|
|
816518cfab | ||
|
|
9e981583a6 | ||
|
|
d6fb8d6cd7 | ||
|
|
7dbf5f7138 | ||
|
|
aaec9487e6 | ||
|
|
96fa881df1 | ||
|
|
b7057fdc3e | ||
|
|
2679c99cad | ||
|
|
ea3a8d4912 | ||
|
|
63d9cd7b57 | ||
|
|
b692bbaa12 | ||
|
|
186af73e5d | ||
|
|
fddf292d47 | ||
|
|
1180634ba7 | ||
|
|
9abdafe101 | ||
|
|
48ebcd5918 | ||
|
|
fe6d0ce9cc | ||
|
|
62dabcae63 | ||
|
|
0b63af8d4d | ||
|
|
b05ebe9623 | ||
|
|
c836fafb61 | ||
|
|
96330f608d | ||
|
|
23aaf5b3ad | ||
|
|
a3e86dcd73 | ||
|
|
81b8028ea2 | ||
|
|
a4bfb032ff | ||
|
|
2704b202bf | ||
|
|
550d9d5e42 | ||
|
|
ab2d05a07d | ||
|
|
4543f6935f | ||
|
|
78d3d6dc94 | ||
|
|
02e7424f51 | ||
|
|
2d6ca4cbb1 | ||
|
|
e244644a1d | ||
|
|
d216457c09 | ||
|
|
20a1da61c0 | ||
|
|
bf7ab1ede7 | ||
|
|
3b6b449545 | ||
|
|
781cf531e6 | ||
|
|
9b7475247c | ||
|
|
44dc7f8d1d | ||
|
|
60eaf9e235 | ||
|
|
f5102ed24d | ||
|
|
309178e4e2 | ||
|
|
76ffdbb993 | ||
|
|
d8037618c8 | ||
|
|
e94e15977c | ||
|
|
f37951249f | ||
|
|
9191079dda | ||
|
|
fdd560747d | ||
|
|
faa5df19ca | ||
|
|
5f9326b131 | ||
|
|
8e389d40b4 | ||
|
|
e62c77e783 | ||
|
|
48b3a43ec2 | ||
|
|
5f783fd5ee | ||
|
|
e112cf93c2 | ||
|
|
d9f26a411e | ||
|
|
ea84e7a491 | ||
|
|
7fab619fed | ||
|
|
699a35b88a | ||
|
|
8095adb945 | ||
|
|
8d36712860 | ||
|
|
0db34d0498 | ||
|
|
7ab254e5e3 | ||
|
|
dd7ab459e2 | ||
|
|
33df2e8aa4 | ||
|
|
39b8fd433b | ||
|
|
c31d74100d | ||
|
|
3af89c1e2b | ||
|
|
1d35bba8c3 | ||
|
|
c3c3e24875 | ||
|
|
ab9c97b158 | ||
|
|
5e700c992d | ||
|
|
b548ad21a9 | ||
|
|
127016d36b | ||
|
|
3d0391173b | ||
|
|
ce560bcd5f | ||
|
|
d553c37d7d | ||
|
|
8a5e89e24b | ||
|
|
8c3e289170 | ||
|
|
9364c8e562 | ||
|
|
5831949ebf | ||
|
|
7fe98a670f | ||
|
|
6f68f3cba6 | ||
|
|
4dc956c76f | ||
|
|
11a56117eb | ||
|
|
10eed6286a | ||
|
|
d36befd9ce | ||
|
|
0c4ddc7f6f | ||
|
|
3ef9679de3 | ||
|
|
d36441489a | ||
|
|
d26c12dd7c | ||
|
|
7fa7ed3658 | ||
|
|
2c68e7a3d2 | ||
|
|
0c9b1c3c79 | ||
|
|
e10b0e513e | ||
|
|
68c66edada | ||
|
|
6eb17e7af7 | ||
|
|
9a24da3098 | ||
|
|
8ed0543b8b | ||
|
|
73a84444d1 | ||
|
|
451767c179 | ||
|
|
8366386126 | ||
|
|
997686a2ea | ||
|
|
f02212b1fe | ||
|
|
2ba68ef5d0 | ||
|
|
2041665880 | ||
|
|
1e6ca01686 | ||
|
|
e15a76e7aa | ||
|
|
64db44acef | ||
|
|
9972389a8d | ||
|
|
e0b1274eee | ||
|
|
973facebba | ||
|
|
df649e2c56 | ||
|
|
a778017efb | ||
|
|
6a9305818e | ||
|
|
2669904c72 | ||
|
|
35529b5eeb | ||
|
|
d55ed8713c | ||
|
|
7973f28bed | ||
|
|
8189964cce | ||
|
|
ee4c901dc7 | ||
|
|
78220cad82 | ||
|
|
40279bc6c0 | ||
|
|
f6fb46d99e | ||
|
|
954b32941e | ||
|
|
48b016802c | ||
|
|
35aa5dd79f | ||
|
|
237402068c | ||
|
|
31dda6e9d6 | ||
|
|
bca6e00e37 | ||
|
|
1c9b4af61d | ||
|
|
eba4a3f1c2 | ||
|
|
0ae9fe3624 | ||
|
|
1b662fcca5 | ||
|
|
cfdba959dd | ||
|
|
78660ad0a2 | ||
|
|
70697869d7 | ||
|
|
10e55108ef | ||
|
|
d4223b8877 | ||
|
|
9537d148d7 | ||
|
|
a133a14b70 | ||
|
|
4ca9e9577b | ||
|
|
44986fad36 | ||
|
|
eb2fca86b6 | ||
|
|
458a1fc035 | ||
|
|
6e87b29e92 | ||
|
|
be1d0c525c | ||
|
|
0787cb4fc2 | ||
|
|
19063a2d90 | ||
|
|
e8e2f820d2 | ||
|
|
aaad634483 | ||
|
|
dfa4127bae | ||
|
|
f3725c714a | ||
|
|
cef3ed01ac | ||
|
|
fc1a3f46f9 | ||
|
|
bfa5feb51b | ||
|
|
4c0813bd69 | ||
|
|
9b0b0f2a5f | ||
|
|
e87c121f8f | ||
|
|
65dfc424bc | ||
|
|
dfea9cc526 | ||
|
|
0d97a0364a | ||
|
|
1da57a4a12 | ||
|
|
b73078e9db | ||
|
|
b17f22cd38 | ||
|
|
7b225057ce | ||
|
|
8242078c06 | ||
|
|
a86740c3c9 | ||
|
|
cbde56549d | ||
|
|
385a94866c | ||
|
|
21972c91dd | ||
|
|
36d3f9afdb | ||
|
|
df2d303ab0 | ||
|
|
05eba350b7 | ||
|
|
1e12e12578 | ||
|
|
bbdab82433 | ||
|
|
f7be6b6423 | ||
|
|
ba358eaa4f | ||
|
|
162e09972f | ||
|
|
2cfccdbe16 | ||
|
|
434fa7b7be | ||
|
|
2f8bdf1eab | ||
|
|
e1705738a1 | ||
|
|
4cfb8fe482 | ||
|
|
d52d2af4b4 | ||
|
|
97fd3832d4 | ||
|
|
3cedd0e0bd | ||
|
|
507b1898ce | ||
|
|
e3fe9010b7 | ||
|
|
2c350b8b90 | ||
|
|
d74e258079 | ||
|
|
b03cabd314 | ||
|
|
6a63af83c0 | ||
|
|
452744b67e | ||
|
|
703a68d4fe | ||
|
|
557893e4b0 | ||
|
|
d7051fb6ce | ||
|
|
867c50da19 | ||
|
|
e8d76ec272 | ||
|
|
c102c61532 | ||
|
|
adb2b0da89 | ||
|
|
3610008699 | ||
|
|
3b44838dde | ||
|
|
0205d7deab | ||
|
|
dd47829bdb | ||
|
|
e7e72d13a9 | ||
|
|
4bbdf1ec8a | ||
|
|
4596df449e | ||
|
|
ecbb636ba1 | ||
|
|
e3aed9dad4 | ||
|
|
213983a322 | ||
|
|
2977084787 | ||
|
|
b6362a63cc | ||
|
|
7517ba820b | ||
|
|
29d60844a8 | ||
|
|
41b0607d7e | ||
|
|
13f7166a30 | ||
|
|
0cc9b84ead | ||
|
|
68ee4311bf | ||
|
|
6e6c3f676e | ||
|
|
c67f50831b | ||
|
|
50ef234bd6 | ||
|
|
2bef5ce09b | ||
|
|
a49c4796f4 | ||
|
|
9eab9586e5 | ||
|
|
cd35787a86 | ||
|
|
cbe84ff4f3 | ||
|
|
410f38eccf | ||
|
|
b885fc2d86 | ||
|
|
4c93f5794a | ||
|
|
456bb75dcb | ||
|
|
02fd8b0d20 | ||
|
|
fbe6c80f86 | ||
|
|
3d5f302d10 | ||
|
|
856a2c1734 | ||
|
|
4277b73438 | ||
|
|
2888f9f8d0 | ||
|
|
68221cdcbe | ||
|
|
f50501cc2a | ||
|
|
c84fac65e0 | ||
|
|
d64c457b3d | ||
|
|
1bd5a880dc | ||
|
|
47d5a89f40 | ||
|
|
6060e7e29f | ||
|
|
677187a43e | ||
|
|
972cb82d16 | ||
|
|
3102df0bf6 | ||
|
|
cb63d92bbf | ||
|
|
c43424ed09 | ||
|
|
a0ccc8c925 | ||
|
|
47160f0118 | ||
|
|
44f0609314 | ||
|
|
689a216726 | ||
|
|
4b45148614 | ||
|
|
c84e603ac5 | ||
|
|
c7049e1a0e | ||
|
|
0b4c3e3046 | ||
|
|
8a5fd11506 | ||
|
|
b565038fdf | ||
|
|
526b1e692a | ||
|
|
c93155132a | ||
|
|
ae7960e9d7 | ||
|
|
3a1268de1e | ||
|
|
10042df309 | ||
|
|
2530ada9d7 | ||
|
|
11890f0eee | ||
|
|
5cb3f31df0 | ||
|
|
ac0624236e | ||
|
|
13eb174c9f | ||
|
|
a3e29317c5 | ||
|
|
75d7cb5bca | ||
|
|
9059dce8af | ||
|
|
1676c02611 | ||
|
|
86a888f0d0 | ||
|
|
816652a8e2 | ||
|
|
c1817ab19e | ||
|
|
b2dcc0d7e9 | ||
|
|
ba5361b25e | ||
|
|
ae826ed19d | ||
|
|
e24fc43a45 | ||
|
|
b719e5771c | ||
|
|
778862fe51 | ||
|
|
30d185a67f | ||
|
|
89c2a4c6ed | ||
|
|
868e811b3f | ||
|
|
f6496c28fe | ||
|
|
81cda0ba74 | ||
|
|
2e9974133a | ||
|
|
49051c4aaf | ||
|
|
e2a89ad8a2 | ||
|
|
f4b0bd68bd | ||
|
|
5a304db840 | ||
|
|
e3044298bf | ||
|
|
bbb9770a97 | ||
|
|
4328b4cb67 | ||
|
|
a324753180 | ||
|
|
1462af61b0 | ||
|
|
8478a0f70b | ||
|
|
8288655b30 | ||
|
|
ac8204427e | ||
|
|
f6b8ce18d0 | ||
|
|
dc42946ff3 | ||
|
|
44cc934c2b | ||
|
|
933956eccb | ||
|
|
27dc8caabd | ||
|
|
4b98df237e | ||
|
|
0fa3ca8dc0 | ||
|
|
0712affa9b | ||
|
|
b646aa03f8 | ||
|
|
4beea35d9e | ||
|
|
e8948a9d6e | ||
|
|
28f25d5aba | ||
|
|
7cbb783b2c | ||
|
|
1793f94f27 | ||
|
|
0b7c9cd8ad | ||
|
|
51b5b78084 | ||
|
|
bea924ddc6 | ||
|
|
b5fcc6e541 | ||
|
|
ffb46fec52 | ||
|
|
4190cf126c | ||
|
|
58721098d5 | ||
|
|
cfd6df7a3b | ||
|
|
9f6fa4cf97 | ||
|
|
7822da03fb | ||
|
|
58cb3d5bdc | ||
|
|
a3c97a51be | ||
|
|
202dc00f4c | ||
|
|
309e58b6d7 | ||
|
|
34b20e26fa | ||
|
|
1de2487e8f | ||
|
|
8d95b72527 | ||
|
|
a920c9cc20 | ||
|
|
427f6d1687 | ||
|
|
dc64168ed4 | ||
|
|
4b913a0ae8 | ||
|
|
dac26e5e91 | ||
|
|
6c56f2b35b | ||
|
|
3b1a626fa9 | ||
|
|
35907fdf51 | ||
|
|
3513956cd6 | ||
|
|
3ed65ce39e | ||
|
|
73e02e745a | ||
|
|
91df8ab0f7 | ||
|
|
ef0f6ca248 | ||
|
|
4ecce81c51 | ||
|
|
823e4cb11a | ||
|
|
28fa90e9e5 | ||
|
|
3d22c8ae91 | ||
|
|
e2135b8d68 | ||
|
|
fe5736dc7f | ||
|
|
b36af5dfb3 | ||
|
|
4446434e5b | ||
|
|
39905b33cd | ||
|
|
dbdc529d4a | ||
|
|
0cbc802cf4 | ||
|
|
b04747676c | ||
|
|
5297a87ad4 | ||
|
|
be6657239d | ||
|
|
0caf263508 | ||
|
|
c77667788a | ||
|
|
bd907425a9 | ||
|
|
229ed53c0e | ||
|
|
f17ceca7a0 | ||
|
|
deac08ba8a | ||
|
|
e0082f4c76 | ||
|
|
bed0443b18 | ||
|
|
24152555c5 | ||
|
|
56f51eebce | ||
|
|
6c1adade25 | ||
|
|
ec5e677635 | ||
|
|
327cae056e | ||
|
|
7a11470817 | ||
|
|
efb01f3c36 | ||
|
|
85ec83c3fd | ||
|
|
1679102204 | ||
|
|
7601531d82 | ||
|
|
56ab1c2f0a | ||
|
|
f309054637 | ||
|
|
8efed4ef62 | ||
|
|
e4c85a5790 | ||
|
|
8af2214979 | ||
|
|
01ce3440eb | ||
|
|
b6573ec2e2 | ||
|
|
d54838cd94 | ||
|
|
e3f3ab224a | ||
|
|
c06ced93f7 | ||
|
|
ea59e895af | ||
|
|
fb7a8dfd16 | ||
|
|
18b1440d7c | ||
|
|
593eebf062 | ||
|
|
fcdff8bdfb | ||
|
|
b9cdd6f2c8 | ||
|
|
270497eda1 | ||
|
|
dbcdb825b0 | ||
|
|
6c28f4b204 | ||
|
|
7b2b979c1b | ||
|
|
e87c9d8811 | ||
|
|
7d04737a65 | ||
|
|
99056e3697 | ||
|
|
c110101cb1 | ||
|
|
aa6129fda0 | ||
|
|
1d181757e8 | ||
|
|
4fa8b6ded8 | ||
|
|
79afdfd1a6 | ||
|
|
7702abb368 | ||
|
|
3d6a49ce7c | ||
|
|
f823049f55 | ||
|
|
b608b73110 | ||
|
|
12c36d279e | ||
|
|
59bd73bff8 | ||
|
|
afbd9f04d7 | ||
|
|
26fb5a0bd7 | ||
|
|
acebff7be1 | ||
|
|
45bd143c07 | ||
|
|
aa46a7fe06 | ||
|
|
07d0eedb0a | ||
|
|
9aae2a11f2 | ||
|
|
51021f380b | ||
|
|
7818a479ee | ||
|
|
799bac4066 | ||
|
|
b562d5cc88 | ||
|
|
41b3ad1b83 | ||
|
|
c912dd4e76 | ||
|
|
3ec9bacb30 | ||
|
|
13db49aab7 | ||
|
|
ac6a82eee4 | ||
|
|
546fc24a0a | ||
|
|
f99820a391 | ||
|
|
873875af84 | ||
|
|
fcb1c4823e | ||
|
|
a1203e6fec | ||
|
|
2321f06c8a | ||
|
|
ce8b9750c9 | ||
|
|
574e3ed6ef | ||
|
|
584514766d | ||
|
|
eb8a1fec49 | ||
|
|
38ccea0f1f | ||
|
|
5d000c37d6 | ||
|
|
1803c5bdb4 | ||
|
|
23e700a1ef | ||
|
|
7114b9fa11 | ||
|
|
2e4d866f69 | ||
|
|
f1cc808429 | ||
|
|
30a39e1d1b | ||
|
|
22ad7244fa | ||
|
|
5be901c044 | ||
|
|
2c073ae488 | ||
|
|
d4a4ba7fdb | ||
|
|
3b8a0fcd95 | ||
|
|
d02cef9d92 | ||
|
|
bc783b8f94 | ||
|
|
4de27117e8 | ||
|
|
49bcf2e211 | ||
|
|
f52ef6e967 | ||
|
|
99bbc347ec | ||
|
|
53185a4ea5 | ||
|
|
23f6fae27a | ||
|
|
4b7e3620ca | ||
|
|
e4f0153a7d | ||
|
|
80947e2b32 | ||
|
|
8bf9dd038e | ||
|
|
4080007ced | ||
|
|
7a6fd2623e | ||
|
|
079eed2b9e | ||
|
|
4c9d028a35 | ||
|
|
bd5c304a50 | ||
|
|
123a3a22c9 | ||
|
|
82d91f8dbd | ||
|
|
cd86310562 | ||
|
|
f04d7733bb | ||
|
|
b2fe1c46ee | ||
|
|
676b8f6d8f | ||
|
|
028f09002f | ||
|
|
0500512c3c | ||
|
|
1e625ed58b | ||
|
|
e620bef2a5 | ||
|
|
05142a779d | ||
|
|
65d17fb316 | ||
|
|
faa12880a9 | ||
|
|
9b6fa55433 | ||
|
|
b852baaa39 | ||
|
|
a3a216f91f | ||
|
|
efff85bc1f | ||
|
|
df61d1a59c | ||
|
|
4450b11e61 | ||
|
|
9f021b780c | ||
|
|
7df66eff5e | ||
|
|
6e5cde0b05 | ||
|
|
a65948de69 | ||
|
|
0d0a8fdc9a | ||
|
|
a5b888c193 | ||
|
|
32cc8e1a63 | ||
|
|
69ea456cf6 | ||
|
|
e02e91adaa | ||
|
|
264c508c80 | ||
|
|
c6209df1e0 | ||
|
|
a155f5561f | ||
|
|
0eac63b844 | ||
|
|
d07c2973e0 | ||
|
|
f1efc578cb | ||
|
|
0b486762fa | ||
|
|
17756f0e72 | ||
|
|
128400bfb5 | ||
|
|
de1df8bf28 | ||
|
|
fe01f13edb | ||
|
|
4040e09cb8 | ||
|
|
3b6cd18283 | ||
|
|
4f505486e3 | ||
|
|
f6e18bbf06 | ||
|
|
bbf6484e89 | ||
|
|
a988ad0c4e | ||
|
|
a815e94209 | ||
|
|
650bee1dea | ||
|
|
80c188586c | ||
|
|
b5cf8f9326 | ||
|
|
1aefd39782 | ||
|
|
8c21a2aa9e | ||
|
|
2df3ca547b | ||
|
|
8645147292 | ||
|
|
169da866f3 | ||
|
|
5e8107621e | ||
|
|
eb52095670 | ||
|
|
cb57752903 | ||
|
|
b870659fd9 | ||
|
|
895c05a84a | ||
|
|
4d47f24dd4 | ||
|
|
4bd6c2a804 | ||
|
|
48fa947692 | ||
|
|
88f66d5c51 | ||
|
|
fd135caed5 | ||
|
|
7fbab6760e | ||
|
|
e9a8175fd7 | ||
|
|
0d75a25bf0 | ||
|
|
6af294e9a4 | ||
|
|
38f50f014b | ||
|
|
a394f11d07 | ||
|
|
3ab73ddf84 | ||
|
|
c7a1fb67d0 | ||
|
|
afb8be4f0b | ||
|
|
dc2a392f4c | ||
|
|
61323c7f85 | ||
|
|
7ebf6b77e5 | ||
|
|
fa47e48a15 | ||
|
|
fee47fe347 | ||
|
|
039c038cd7 | ||
|
|
dd99a25db0 | ||
|
|
85791f730c | ||
|
|
eb859b9812 | ||
|
|
7cf0523561 | ||
|
|
aae2e3f835 | ||
|
|
e0ce4c49f3 | ||
|
|
a60a65cd2a | ||
|
|
b7d0ec53e8 | ||
|
|
ef5cd66494 | ||
|
|
0d1898e72d | ||
|
|
f20cd8c203 | ||
|
|
8993dc706a | ||
|
|
af18453691 | ||
|
|
c102bf05af | ||
|
|
b1570302bc | ||
|
|
69a42b1a89 | ||
|
|
272e012626 | ||
|
|
d785f30c5f | ||
|
|
9854f8a6ab | ||
|
|
4235bf67f8 | ||
|
|
a6bc0d4222 | ||
|
|
b859c3360d | ||
|
|
391907c41e | ||
|
|
04a550cc67 | ||
|
|
cf459dc4e8 | ||
|
|
afebcc574d | ||
|
|
f3474f0811 | ||
|
|
7378952a8b | ||
|
|
3cfab418d1 | ||
|
|
8090cd3032 | ||
|
|
cd54d560b3 | ||
|
|
9fc92ccc52 | ||
|
|
b8674a3f8c | ||
|
|
07ccce9845 | ||
|
|
0c8c69f04a | ||
|
|
5856f805fc | ||
|
|
73a5802c11 | ||
|
|
826a069be0 | ||
|
|
1246b14e7e | ||
|
|
1ed0b70601 | ||
|
|
c3621f1e89 | ||
|
|
7de86fc4b4 | ||
|
|
963948b5c8 | ||
|
|
d9749e8975 | ||
|
|
f6e4e53728 | ||
|
|
98adb196ea | ||
|
|
6b60edbe5d | ||
|
|
9d6de42f48 | ||
|
|
a94a602ccd | ||
|
|
301818003d | ||
|
|
50d52c31e2 | ||
|
|
799968460d | ||
|
|
170d95aa3c | ||
|
|
fe7a2fe229 | ||
|
|
3f08e26881 | ||
|
|
921b2bfb28 | ||
|
|
9af2c92795 | ||
|
|
dabae456d9 | ||
|
|
c40785b6eb | ||
|
|
e2e80313ac | ||
|
|
14a99a7b9e | ||
|
|
50e8c299c6 | ||
|
|
326d12382f | ||
|
|
1de9dddd21 | ||
|
|
87b1f0d0de | ||
|
|
dd6cf19c39 | ||
|
|
f085afd92f | ||
|
|
604cbc1737 | ||
|
|
60b6faff19 | ||
|
|
e70059ed6b | ||
|
|
b26c1c16b9 | ||
|
|
c2bf9d94be | ||
|
|
ea09adbbf3 | ||
|
|
9d0de57fae | ||
|
|
da733538c4 | ||
|
|
6db7cea148 | ||
|
|
3993aa9524 | ||
|
|
6f9d4d89cd | ||
|
|
443bdc1234 | ||
|
|
9cd43d044e | ||
|
|
f8e680867b | ||
|
|
96a5540083 | ||
|
|
750e1bd80a | ||
|
|
a12f161be5 | ||
|
|
04568ea830 | ||
|
|
3be0b527d6 | ||
|
|
afc0732a32 | ||
|
|
9703fb06fc | ||
|
|
54cbf13219 | ||
|
|
6774a12c67 | ||
|
|
94e53d988b | ||
|
|
22d47ea8c4 | ||
|
|
73bba00cc6 | ||
|
|
6ed429ada2 | ||
|
|
d2c2d459c4 | ||
|
|
c8b906ffb7 | ||
|
|
264f1d6638 | ||
|
|
16c7908adc | ||
|
|
c9d05d7d4a | ||
|
|
ec7e4488dc | ||
|
|
72f440acf5 | ||
|
|
21bf698c81 | ||
|
|
489ee30e54 | ||
|
|
2abab0772f | ||
|
|
0bca0fabaa | ||
|
|
93ac3fea43 | ||
|
|
c72b71a43a | ||
|
|
9e8c40598c | ||
|
|
4ded4afb7d | ||
|
|
801c45da6d | ||
|
|
278b356a18 | ||
|
|
a718e01dbf | ||
|
|
8e6cdde861 | ||
|
|
62b0c2b647 | ||
|
|
1cd30ceb31 | ||
|
|
15c7a3f85b | ||
|
|
d977aff8cf | ||
|
|
e3b44c3950 | ||
|
|
ba035efc91 | ||
|
|
76cfd7784a | ||
|
|
3e6875ce1d | ||
|
|
1ab7aa0fc4 | ||
|
|
5950e0bfcb | ||
|
|
ac540d3d3f | ||
|
|
848ddc5f3e | ||
|
|
30d1d63813 | ||
|
|
9781a9094f | ||
|
|
ab3de5898d | ||
|
|
7ff8a3764b | ||
|
|
32d6d746b3 | ||
|
|
ecf9a0827d | ||
|
|
a9a7fac308 | ||
|
|
54b5884943 | ||
|
|
1fb38137dc | ||
|
|
2d6192db75 | ||
|
|
9ecceb4a1e | ||
|
|
6b25fcaa80 | ||
|
|
c5c83a4240 | ||
|
|
5e0eb5ab97 | ||
|
|
2de5ffc8d9 | ||
|
|
3b2fe39a0a | ||
|
|
285ff080d0 | ||
|
|
627bde9e9e | ||
|
|
ef7d5e6004 | ||
|
|
598c8a1c4d | ||
|
|
b3c20ee0ae | ||
|
|
cd8d382038 | ||
|
|
b678d61318 | ||
|
|
43c8231f7d | ||
|
|
db401e0daa | ||
|
|
675d4c5f2b | ||
|
|
fdbf3ed279 | ||
|
|
5660f9ac59 | ||
|
|
546e63aa4c | ||
|
|
ddbd143793 | ||
|
|
35ba321546 | ||
|
|
2fe7fe30f8 | ||
|
|
8d4d1d594b | ||
|
|
c86fafbd7e | ||
|
|
709c439afc | ||
|
|
4cdc88e4bb | ||
|
|
7c550a76a5 | ||
|
|
cfabbcaaf6 | ||
|
|
7ae6286152 | ||
|
|
fd9c28c960 | ||
|
|
fa9ee96f7f | ||
|
|
334c33ca07 | ||
|
|
85cc67fb4e | ||
|
|
af9eb7c374 | ||
|
|
44968cc01e | ||
|
|
af69b25eaa | ||
|
|
eb33b95083 | ||
|
|
aa9124e072 | ||
|
|
c086fad945 | ||
|
|
0fef88c358 | ||
|
|
56f8f8d3f4 | ||
|
|
5bced09fc5 | ||
|
|
b4e9ff7ce0 | ||
|
|
208cbabb31 | ||
|
|
2fb5cfd55d | ||
|
|
582036ba45 | ||
|
|
e06f9f5438 | ||
|
|
461876da93 | ||
|
|
4f1c662691 | ||
|
|
9abd4e05d0 | ||
|
|
faba64890e | ||
|
|
add54bfd0b | ||
|
|
16d39bb72b | ||
|
|
e63ce9ed08 | ||
|
|
60831cae88 | ||
|
|
97cf46eaa9 | ||
|
|
381e75b913 | ||
|
|
7bd516a16c | ||
|
|
3dd01cde89 | ||
|
|
495394084d | ||
|
|
2609ee5ed0 | ||
|
|
da930ce276 | ||
|
|
987924cbda | ||
|
|
8fac1c18c8 | ||
|
|
eb64fde885 | ||
|
|
b1e9537499 | ||
|
|
9d636cad29 | ||
|
|
696c0b0055 | ||
|
|
6e030fd62f | ||
|
|
bb14a95076 | ||
|
|
9664aed1f2 | ||
|
|
6dda5f477e | ||
|
|
72cd73ca71 | ||
|
|
02e18cf919 | ||
|
|
82671680e3 | ||
|
|
bff49f2a5f | ||
|
|
59d582ce83 | ||
|
|
a4a3ba65d7 | ||
|
|
11f4b64229 | ||
|
|
b76029fac3 | ||
|
|
3d45f31536 | ||
|
|
ade00c70e5 | ||
|
|
82dca5336d | ||
|
|
8c33d0ecbd | ||
|
|
dea5fd1a9d | ||
|
|
6a131f70f0 | ||
|
|
d33a0d5dde | ||
|
|
11cc7e37e1 | ||
|
|
7e6cb7ecc9 | ||
|
|
807c58dc36 | ||
|
|
1517f2d910 | ||
|
|
b0c59ee330 | ||
|
|
1ff52bab56 | ||
|
|
7a9fca7f77 | ||
|
|
dea53a0dba | ||
|
|
db999b82ed | ||
|
|
c92468062d | ||
|
|
4de0f09c85 | ||
|
|
9c9c1b4d3b | ||
|
|
5ffe91f069 | ||
|
|
63867518ee | ||
|
|
53ff99e391 | ||
|
|
c035c12c0a | ||
|
|
6e39a02e99 | ||
|
|
956638e564 | ||
|
|
37907ad348 | ||
|
|
386aa898ec | ||
|
|
f1c5da7026 | ||
|
|
fc2a5224ef | ||
|
|
ce5aefd3d8 | ||
|
|
b2124dffb5 | ||
|
|
25eaace4be | ||
|
|
bb8efbcc82 | ||
|
|
e0bd5ad041 | ||
|
|
69ec49d0e9 | ||
|
|
8126f734e3 | ||
|
|
f2aaa6778c | ||
|
|
4fd5b01a83 | ||
|
|
1747a844fc | ||
|
|
afc210a70d | ||
|
|
f63003f982 | ||
|
|
e89037dd77 | ||
|
|
ab6e650e9c | ||
|
|
2ed246cb61 | ||
|
|
4449555abe | ||
|
|
f340f491dc | ||
|
|
c8f1e714e1 | ||
|
|
ddc428532f | ||
|
|
3414cae677 | ||
|
|
9d6972c6ce | ||
|
|
0566a0f1d6 | ||
|
|
de0561dcc2 | ||
|
|
a9f4f53f92 | ||
|
|
5fdfd4114a | ||
|
|
b195f9da44 | ||
|
|
1205d71f4b | ||
|
|
3f762a6476 | ||
|
|
4aa403c122 | ||
|
|
a13070a8da | ||
|
|
b63b171653 | ||
|
|
7219f8fed8 | ||
|
|
b6a5f834d6 | ||
|
|
99b9d53bbb | ||
|
|
edca19a697 | ||
|
|
c13d721062 | ||
|
|
d2f316c484 | ||
|
|
70e832d4db | ||
|
|
21895bd09b | ||
|
|
411ef5f9e8 | ||
|
|
f6282b9a09 | ||
|
|
e10030b73d | ||
|
|
cdf14158b4 | ||
|
|
f310e672b0 | ||
|
|
675d0d28d2 | ||
|
|
4c2fd056ef | ||
|
|
a259e48377 | ||
|
|
095c586172 | ||
|
|
c9c198b54b | ||
|
|
2a11bb4f3b | ||
|
|
35bac50962 | ||
|
|
366d2c1d97 | ||
|
|
9a930cbd95 | ||
|
|
03277513a9 | ||
|
|
1b0fca8026 | ||
|
|
c9cf5b78c5 | ||
|
|
d6679a1e9b | ||
|
|
b721a4b361 | ||
|
|
dfde30798e | ||
|
|
88bbd43314 | ||
|
|
fb1c97cdc1 | ||
|
|
f5ae8a0a4c | ||
|
|
1994eaa406 | ||
|
|
510b40a776 | ||
|
|
f37b070965 | ||
|
|
41385261f3 | ||
|
|
19b4849345 | ||
|
|
76283bd299 | ||
|
|
2e4cda74c8 | ||
|
|
5512b71e16 | ||
|
|
97b60c43b7 | ||
|
|
35b62f8526 | ||
|
|
a15a3f005c | ||
|
|
776c4a988a | ||
|
|
c419969253 | ||
|
|
ba324c73ce | ||
|
|
4a5dc78331 | ||
|
|
55dc9dfb54 | ||
|
|
23a8191bb5 | ||
|
|
c665caaf35 | ||
|
|
099efb883d | ||
|
|
44237426df | ||
|
|
eeefd19ad3 | ||
|
|
47ae6e7a5a | ||
|
|
03ed6e9755 | ||
|
|
8d4e7f0a82 | ||
|
|
7fdf491c05 | ||
|
|
ef1563283e | ||
|
|
a206d79851 | ||
|
|
42c9c0a06b | ||
|
|
f0ede01017 | ||
|
|
d67007f777 | ||
|
|
83d81e3788 | ||
|
|
e789e16289 | ||
|
|
61c9683aa6 | ||
|
|
ee9d1356b2 | ||
|
|
f92a49fda9 | ||
|
|
3dc6a055ac | ||
|
|
229f0d97f9 | ||
|
|
7cc530f950 | ||
|
|
2ef840ce12 | ||
|
|
a372d8d1d5 | ||
|
|
aad150cf1d | ||
|
|
be13a11dd5 | ||
|
|
59c6f35b0b | ||
|
|
37e45c5e7c | ||
|
|
39370f1eab | ||
|
|
aec7ac6ebd | ||
|
|
f6e63d0917 | ||
|
|
0ae67edaba | ||
|
|
481f6435ee | ||
|
|
d0c5c3d3cf | ||
|
|
9f8250bd47 | ||
|
|
3a3fffb2dd | ||
|
|
4cfa4eaf8e | ||
|
|
abb1125a2c | ||
|
|
a2acbe9fe6 | ||
|
|
cab8c690d2 | ||
|
|
0d1f8a06ce | ||
|
|
d42fe921db | ||
|
|
db7fb81855 | ||
|
|
d3c695b853 | ||
|
|
010c3ab0b8 | ||
|
|
58cdbca5cf | ||
|
|
8275082896 | ||
|
|
d79da1ef9f | ||
|
|
a9636426b8 | ||
|
|
329caad681 | ||
|
|
ecb84e090c | ||
|
|
8e9fc14b0e | ||
|
|
0f77ca605d | ||
|
|
231fcc8178 | ||
|
|
2839091b22 | ||
|
|
47e67481b3 | ||
|
|
55059b015f | ||
|
|
eb6c58682d | ||
|
|
26055de772 | ||
|
|
ebb4581595 | ||
|
|
d1fecc11c9 | ||
|
|
056247a34a | ||
|
|
7010015e8a | ||
|
|
62d50d27be | ||
|
|
1e5231d68b | ||
|
|
e04efad3c0 | ||
|
|
e54db3ce50 | ||
|
|
77076dbd67 | ||
|
|
6f20a798ab | ||
|
|
0d3a22bbc3 | ||
|
|
f34c96ecf5 | ||
|
|
206c85778e | ||
|
|
d6b4b9f973 | ||
|
|
3065e29deb | ||
|
|
481047bed8 | ||
|
|
f72292cce2 | ||
|
|
7b35902d33 | ||
|
|
1660900914 | ||
|
|
a7be25ce8b | ||
|
|
54b5ba08b8 | ||
|
|
0fb8d48074 | ||
|
|
b5fac4157d | ||
|
|
9e61949f9f | ||
|
|
6c5640798f | ||
|
|
03222197a3 | ||
|
|
12f417d0a3 | ||
|
|
c77aaece1d | ||
|
|
25140c9072 | ||
|
|
3a636c29ab | ||
|
|
a11d5ccd37 | ||
|
|
f6e7937f74 | ||
|
|
e447b667e5 | ||
|
|
24c635e9bc | ||
|
|
2ad4dcd741 | ||
|
|
f5cd9e0799 | ||
|
|
e7064868b4 | ||
|
|
65cbbf15c9 | ||
|
|
a325509e1e | ||
|
|
69ae731898 | ||
|
|
3452dee1b0 | ||
|
|
64b337e3c6 | ||
|
|
5df9655fe3 | ||
|
|
f3669f3be6 | ||
|
|
61eb99c46d | ||
|
|
f74a14e34f | ||
|
|
517f1d7991 | ||
|
|
25e69885d0 | ||
|
|
60a357eda1 | ||
|
|
d74679a5f9 | ||
|
|
73a865073d | ||
|
|
4ff8c28fe4 | ||
|
|
4ab2539c8a | ||
|
|
459eb3903e | ||
|
|
611a537b55 | ||
|
|
3a74cc5a74 | ||
|
|
f1520e1a70 | ||
|
|
727b4668c2 | ||
|
|
1287e001d8 | ||
|
|
c9b53cf975 | ||
|
|
64811d0b6b | ||
|
|
74af187568 | ||
|
|
a28c023cf1 | ||
|
|
cdf7fd64b2 | ||
|
|
84ffa4a5b7 | ||
|
|
326a43de11 | ||
|
|
07f193d8d6 | ||
|
|
f79a57c3e2 | ||
|
|
f8319fcd02 | ||
|
|
815ef4c9c9 | ||
|
|
d1800aa6d0 | ||
|
|
dda940344e | ||
|
|
1fffeb430c | ||
|
|
7d0bbd0a4c | ||
|
|
15fd22681d | ||
|
|
6a2826b91c | ||
|
|
112111c7f9 | ||
|
|
ed8498f43f | ||
|
|
77a5bb9069 | ||
|
|
37f86803f7 | ||
|
|
160858b051 | ||
|
|
68f44c01ea | ||
|
|
bef8d7426f | ||
|
|
c758f079cd | ||
|
|
7e404b7c19 | ||
|
|
4b7faea552 | ||
|
|
4ddd391033 | ||
|
|
e52416fd47 | ||
|
|
f67a2d2f46 | ||
|
|
fcdda8d7a7 | ||
|
|
1f0b936e82 | ||
|
|
b70793db5c | ||
|
|
0f044f6c21 | ||
|
|
4c205dfde9 | ||
|
|
d58d460119 | ||
|
|
24a6edef9e | ||
|
|
a5485096ac | ||
|
|
60a5ccf70b | ||
|
|
d93a7c2997 | ||
|
|
af5f8e8a4a | ||
|
|
1596c855ff | ||
|
|
f45dd7a748 | ||
|
|
a036363e85 | ||
|
|
4aceea41fd | ||
|
|
7bbfcbaefd | ||
|
|
18eaa9bb92 | ||
|
|
6826d5444b | ||
|
|
622ec69216 | ||
|
|
d38c109d49 | ||
|
|
a31b2d0259 | ||
|
|
b13c076881 | ||
|
|
c429a55382 | ||
|
|
20c4b21c39 | ||
|
|
d3289dc688 | ||
|
|
685c0b844e | ||
|
|
57c9b14198 | ||
|
|
0736f4d166 | ||
|
|
fed94b531d | ||
|
|
43a77e8667 | ||
|
|
637dc3844d | ||
|
|
815a45cf2f | ||
|
|
0b66b61dd6 | ||
|
|
7c011a1796 | ||
|
|
bf8859f401 | ||
|
|
c14d5ec59e | ||
|
|
6d850e031a | ||
|
|
38af9e2d42 | ||
|
|
a3d7901d5f | ||
|
|
54b3e2f285 | ||
|
|
d0a13cb12a | ||
|
|
003bf29dce | ||
|
|
71c72f74a1 | ||
|
|
adaa24a562 | ||
|
|
ad24fe7017 | ||
|
|
e5578a8ef3 | ||
|
|
3a40d5e243 | ||
|
|
8e34898b4e | ||
|
|
7eefa897b3 | ||
|
|
4c7c89b410 | ||
|
|
caafa55c35 | ||
|
|
7776d426ac | ||
|
|
2d87ccface | ||
|
|
b9131b9e8b | ||
|
|
7c9626b0e7 | ||
|
|
1338aef2bd | ||
|
|
b9ecf389c2 | ||
|
|
75a873079d | ||
|
|
4824153cd9 | ||
|
|
5b28e7b397 | ||
|
|
f3f781917a | ||
|
|
4398c7c777 | ||
|
|
b6179c6073 | ||
|
|
dd4943310d | ||
|
|
7df6f8d88c | ||
|
|
c026790f55 | ||
|
|
0b0d049071 | ||
|
|
87105a654c | ||
|
|
32651db4e9 | ||
|
|
270f6c4abd | ||
|
|
3664cc3369 | ||
|
|
2204e03123 | ||
|
|
7b6befa3d2 | ||
|
|
84bc91defd | ||
|
|
2dca92c788 | ||
|
|
76dc22cd06 | ||
|
|
6d4b4cac37 | ||
|
|
3fc63489f1 | ||
|
|
e8cd8c249c | ||
|
|
471f47cd9e | ||
|
|
e5dbb592fa | ||
|
|
44466a3e76 | ||
|
|
d6ef84e9e2 | ||
|
|
c4d8485c81 | ||
|
|
dbb1a0c733 | ||
|
|
b5dee61e57 | ||
|
|
2c7d9320e2 | ||
|
|
fd3a82d430 | ||
|
|
3a776ccbff | ||
|
|
f96ed11a87 | ||
|
|
86f8ced486 | ||
|
|
940f055412 | ||
|
|
d7f1f0c7e6 | ||
|
|
045785c36f | ||
|
|
45600d034d | ||
|
|
33c7f0b5fc | ||
|
|
62e9e7ea80 | ||
|
|
a75c10f447 | ||
|
|
ee4b47595a | ||
|
|
9be8fba63d | ||
|
|
15f41a0f16 | ||
|
|
f06eb5e2f1 | ||
|
|
a9f4011a45 | ||
|
|
55f2125a51 | ||
|
|
b41f90e7d4 | ||
|
|
7c707ede2b | ||
|
|
4df9f9eca0 | ||
|
|
6af27fffbc | ||
|
|
a7ed9c5ff6 | ||
|
|
51b45c4fac | ||
|
|
313de35e60 | ||
|
|
0ac3a377fd | ||
|
|
1319fadc60 | ||
|
|
181bda51ce | ||
|
|
e914c23c42 | ||
|
|
c1587b25b8 | ||
|
|
9e74ac24fa | ||
|
|
48eb06f320 | ||
|
|
65ba87e71f | ||
|
|
f92924d57e | ||
|
|
eeb0feabc0 | ||
|
|
ac8b49b39d | ||
|
|
1b50db26b6 | ||
|
|
cbe612baa5 | ||
|
|
1f34d4c134 | ||
|
|
f864335463 | ||
|
|
47970d3455 | ||
|
|
6cdaacdda3 | ||
|
|
9b66bda8b9 | ||
|
|
ef354ca1e6 | ||
|
|
515c3450c2 | ||
|
|
5607c350cd | ||
|
|
b9758f5c1a | ||
|
|
aad432aaa3 | ||
|
|
d4971eb7b7 | ||
|
|
7860eb529f | ||
|
|
49c2a38437 | ||
|
|
d4bf238173 | ||
|
|
c085397bcb | ||
|
|
58fab2530f | ||
|
|
287b32870e | ||
|
|
46ac9506e6 | ||
|
|
19ccfcff9a | ||
|
|
f8a08c8a5e | ||
|
|
6f7fe8f9f9 | ||
|
|
86b41a4887 | ||
|
|
3786693078 | ||
|
|
6a17e5b65b | ||
|
|
169c0f6642 | ||
|
|
054569da70 | ||
|
|
4a6ab622df | ||
|
|
07cc75f6d4 | ||
|
|
7fc8775654 | ||
|
|
41a6473782 | ||
|
|
f39834ad82 | ||
|
|
bdb13343bb | ||
|
|
262cd3c695 | ||
|
|
f02099e8b7 | ||
|
|
7bf3ee69ef | ||
|
|
41e837d1e2 | ||
|
|
2090e46ac2 | ||
|
|
f09ee33e6c | ||
|
|
22782f8c5f | ||
|
|
e61e7df54e | ||
|
|
baf37e94eb | ||
|
|
bba2a264ea | ||
|
|
324ca7fe72 | ||
|
|
fb5394e31c | ||
|
|
53baea4c6c | ||
|
|
35a51b393a | ||
|
|
9ee9de76b5 | ||
|
|
ae15dcaf0b | ||
|
|
eb0528c157 | ||
|
|
764089e493 | ||
|
|
77e704cef1 | ||
|
|
59ce1bba16 | ||
|
|
1d3a36d821 | ||
|
|
dc0d74ca2c | ||
|
|
ef36d7c87f | ||
|
|
81fe39f060 | ||
|
|
5a6e9a06e2 | ||
|
|
9083425c24 | ||
|
|
010f5031a7 | ||
|
|
40e5b70495 | ||
|
|
9588ff3b4f | ||
|
|
30cf483357 | ||
|
|
4d1fa4d262 | ||
|
|
ac40449d6e | ||
|
|
dc4b014d12 | ||
|
|
d129928e42 | ||
|
|
573b2bc44f | ||
|
|
c095f0fc19 | ||
|
|
ae06e9cb14 | ||
|
|
73af95f55e | ||
|
|
64d9a7983b | ||
|
|
f6d14564a2 | ||
|
|
6c266b47e6 | ||
|
|
a2b984a1a5 | ||
|
|
0a7945a911 | ||
|
|
9c3e78443b | ||
|
|
68f79a1f3a | ||
|
|
b00e5876d4 | ||
|
|
7481d20261 | ||
|
|
637d6173bc | ||
|
|
e23e634974 | ||
|
|
1c65fbaae3 | ||
|
|
dc0cc0f910 | ||
|
|
424dbe8208 | ||
|
|
db34423af8 | ||
|
|
ca76f4db0c | ||
|
|
711e5e09ba | ||
|
|
6001bd5446 | ||
|
|
02f60467d7 | ||
|
|
cdce745c55 | ||
|
|
467a37f8fe | ||
|
|
88a6412b54 | ||
|
|
502eaf9fb9 | ||
|
|
de8eab0434 | ||
|
|
f317fca9e4 | ||
|
|
561fc289fb | ||
|
|
77933e97c0 | ||
|
|
ee4792dbf8 | ||
|
|
cde0df937f | ||
|
|
daf4310176 | ||
|
|
fb0e55fd1b | ||
|
|
2e5ef22585 | ||
|
|
8e043b139a | ||
|
|
e7dbe90cb5 | ||
|
|
42484cf98d | ||
|
|
274e487a96 | ||
|
|
940c189c12 | ||
|
|
c3ad479fc6 | ||
|
|
928c35ede5 | ||
|
|
1a9fcdccc2 | ||
|
|
3b1e40d227 | ||
|
|
4e84c7c4c4 | ||
|
|
f47eb126e2 | ||
|
|
5d4ab13386 | ||
|
|
b53d3bc81d | ||
|
|
46ccc58749 | ||
|
|
289beb85d2 | ||
|
|
460c7c3379 | ||
|
|
9881bb72b8 | ||
|
|
264c560a8a | ||
|
|
2fc581c249 | ||
|
|
a79d7444e5 | ||
|
|
f8d074db01 | ||
|
|
c3843004aa | ||
|
|
f597205fa7 | ||
|
|
e7be86867d | ||
|
|
13300bdbd4 | ||
|
|
b09da48835 | ||
|
|
39e23db523 | ||
|
|
b10a8b0fa9 | ||
|
|
05cb876df5 | ||
|
|
4a271d6897 | ||
|
|
41342883d4 | ||
|
|
cc7488bc15 | ||
|
|
367e0a5e87 | ||
|
|
4a2917b6a0 | ||
|
|
c6a63d01db | ||
|
|
0694cb9a7d | ||
|
|
da2bf4c510 | ||
|
|
48a044cc68 | ||
|
|
b7c0f02cb1 | ||
|
|
a76194c493 | ||
|
|
86390152bc | ||
|
|
899d36b2c9 | ||
|
|
530977d6b3 | ||
|
|
aa682fa2c9 | ||
|
|
28ad404baa | ||
|
|
1ff8ebab94 | ||
|
|
c616678beb | ||
|
|
500d407099 | ||
|
|
b99129c6b2 | ||
|
|
60f1919791 | ||
|
|
262a2b70e2 | ||
|
|
977164b920 | ||
|
|
a0df379225 | ||
|
|
b5bc9bb3f4 | ||
|
|
b5708a8cc4 | ||
|
|
c8604c73a9 | ||
|
|
949c2b92af | ||
|
|
5473e54219 | ||
|
|
aefc28a0ed | ||
|
|
f102b0ccf9 | ||
|
|
55e37f6229 | ||
|
|
ad0dc028f2 | ||
|
|
e3893b1887 | ||
|
|
c89296e76d | ||
|
|
c58fef949d | ||
|
|
26ab6dd264 | ||
|
|
abf870e604 | ||
|
|
a83aa7c0ae | ||
|
|
82fe099060 | ||
|
|
304ec80d80 | ||
|
|
f6104dd438 | ||
|
|
7fadc00fb3 | ||
|
|
26e5830b80 | ||
|
|
efcac6d55a |
@@ -1,2 +1,3 @@
|
|||||||
awx/ui/node_modules
|
awx/ui/node_modules
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
.git
|
||||||
|
|||||||
17
.github/BOTMETA.yml
vendored
17
.github/BOTMETA.yml
vendored
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
files:
|
|
||||||
awx/ui/:
|
|
||||||
labels: component:ui
|
|
||||||
maintainers: $team_ui
|
|
||||||
awx/api/:
|
|
||||||
labels: component:api
|
|
||||||
maintainers: $team_api
|
|
||||||
awx/main/:
|
|
||||||
labels: component:api
|
|
||||||
maintainers: $team_api
|
|
||||||
installer/:
|
|
||||||
labels: component:installer
|
|
||||||
|
|
||||||
macros:
|
|
||||||
team_api: wwitzel3 matburt chrismeyersfsu cchurch AlanCoding ryanpetrello rooftopcellist
|
|
||||||
team_ui: jlmitch5 jaredevantabor mabashian marshmalien benthomasson jakemcdermott
|
|
||||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
|||||||
workflows/e2e_test.yml @tiagodread @shanemcd @jakemcdermott
|
|
||||||
28
.github/ISSUE_TEMPLATE.md
vendored
28
.github/ISSUE_TEMPLATE.md
vendored
@@ -6,17 +6,37 @@ practices regarding responsible disclosure, see
|
|||||||
https://www.ansible.com/security
|
https://www.ansible.com/security
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
PLEASE DO NOT USE A BLANK TEMPLATE IN THE AWX REPO.
|
||||||
|
This is a legacy template used for internal testing ONLY.
|
||||||
|
|
||||||
|
Any issues opened will this template will be automatically closed.
|
||||||
|
|
||||||
|
Instead use the bug or feature request.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### ISSUE TYPE
|
##### ISSUE TYPE
|
||||||
<!--- Pick one below and delete the rest: -->
|
<!--- Pick one below and delete the rest: -->
|
||||||
- Bug Report
|
- Breaking Change
|
||||||
- Feature Idea
|
- New or Enhanced Feature
|
||||||
- Documentation
|
- Bug or Docs Fix
|
||||||
|
|
||||||
|
|
||||||
##### COMPONENT NAME
|
##### COMPONENT NAME
|
||||||
<!-- Pick the area of AWX for this issue, you can have multiple, delete the rest: -->
|
<!-- Pick the area of AWX for this issue, you can have multiple, delete the rest: -->
|
||||||
- API
|
- API
|
||||||
- UI
|
- UI
|
||||||
- Installer
|
- Collection
|
||||||
|
- Docs
|
||||||
|
- CLI
|
||||||
|
- Other
|
||||||
|
|
||||||
|
|
||||||
##### SUMMARY
|
##### SUMMARY
|
||||||
<!-- Briefly describe the problem. -->
|
<!-- Briefly describe the problem. -->
|
||||||
|
|||||||
32
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
32
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,32 +1,29 @@
|
|||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: Create a report to help us improve
|
description: "🐞 Create a report to help us improve"
|
||||||
labels:
|
|
||||||
- bug
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Issues are for **concrete, actionable bugs and feature requests** only. For debugging help or technical support, please use:
|
Bug Report issues are for **concrete, actionable bugs** only.
|
||||||
- The #ansible-awx channel on irc.libera.chat
|
For debugging help or technical support, please see the [Get Involved section of our README](https://github.com/ansible/awx#get-involved)
|
||||||
- https://groups.google.com/forum/#!forum/awx-project
|
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: terms
|
id: terms
|
||||||
attributes:
|
attributes:
|
||||||
label: Please confirm the following
|
label: Please confirm the following
|
||||||
options:
|
options:
|
||||||
- label: I agree to follow this project's [code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
|
- label: I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
|
||||||
required: true
|
required: true
|
||||||
- label: I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
|
- label: I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
|
||||||
required: true
|
required: true
|
||||||
- label: I understand that AWX is open source software provided for free and that I am not entitled to status updates or other assurances.
|
- label: I understand that AWX is open source software provided for free and that I might not receive a timely response.
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: summary
|
id: summary
|
||||||
attributes:
|
attributes:
|
||||||
label: Summary
|
label: Bug Summary
|
||||||
description: Briefly describe the problem.
|
description: Briefly describe the problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
@@ -39,6 +36,18 @@ body:
|
|||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: components
|
||||||
|
attributes:
|
||||||
|
label: Select the relevant components
|
||||||
|
options:
|
||||||
|
- label: UI
|
||||||
|
- label: API
|
||||||
|
- label: Docs
|
||||||
|
- label: Collection
|
||||||
|
- label: CLI
|
||||||
|
- label: Other
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: awx-install-method
|
id: awx-install-method
|
||||||
attributes:
|
attributes:
|
||||||
@@ -50,9 +59,8 @@ body:
|
|||||||
- minikube
|
- minikube
|
||||||
- openshift
|
- openshift
|
||||||
- minishift
|
- minishift
|
||||||
- docker on linux
|
- docker development environment
|
||||||
- docker for mac
|
- N/A
|
||||||
- boot2docker
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: For debugging help or technical support
|
||||||
|
url: https://github.com/ansible/awx#get-involved
|
||||||
|
about: For general debugging or technical support please see the Get Involved section of our readme.
|
||||||
|
- name: 📝 Ansible Code of Conduct
|
||||||
|
url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser
|
||||||
|
about: AWX uses the Ansible Code of Conduct; ❤ Be nice to other members of the community. ☮ Behave.
|
||||||
|
- name: 💼 For Enterprise
|
||||||
|
url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser
|
||||||
|
about: Red Hat offers support for the Ansible Automation Platform
|
||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: "✨ Feature request"
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
|
|
||||||
---
|
|
||||||
<!-- Issues are for **concrete, actionable bugs and feature requests** only - if you're just asking for debugging help or technical support, please use:
|
|
||||||
|
|
||||||
- http://web.libera.chat/?channels=#ansible-awx
|
|
||||||
- https://groups.google.com/forum/#!forum/awx-project
|
|
||||||
|
|
||||||
We have to limit this because of limited volunteer time to respond to issues! -->
|
|
||||||
|
|
||||||
##### ISSUE TYPE
|
|
||||||
- Feature Idea
|
|
||||||
|
|
||||||
##### SUMMARY
|
|
||||||
<!-- Briefly describe the problem or desired enhancement. -->
|
|
||||||
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
name: ✨ Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Feature Request issues are for **feature requests** only.
|
||||||
|
For debugging help or technical support, please see the [Get Involved section of our README](https://github.com/ansible/awx#get-involved)
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Please confirm the following
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
|
||||||
|
required: true
|
||||||
|
- label: I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
|
||||||
|
required: true
|
||||||
|
- label: I understand that AWX is open source software provided for free and that I might not receive a timely response.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Feature Summary
|
||||||
|
description: Briefly describe the desired enhancement.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: components
|
||||||
|
attributes:
|
||||||
|
label: Select the relevant components
|
||||||
|
options:
|
||||||
|
- label: UI
|
||||||
|
- label: API
|
||||||
|
- label: Docs
|
||||||
|
- label: Collection
|
||||||
|
- label: CLI
|
||||||
|
- label: Other
|
||||||
|
|
||||||
9
.github/LABEL_MAP.md
vendored
9
.github/LABEL_MAP.md
vendored
@@ -1,9 +0,0 @@
|
|||||||
Bug Report: type:bug
|
|
||||||
Bugfix Pull Request: type:bug
|
|
||||||
Feature Request: type:enhancement
|
|
||||||
Feature Pull Request: type:enhancement
|
|
||||||
UI: component:ui
|
|
||||||
API: component:api
|
|
||||||
Installer: component:installer
|
|
||||||
Docs Pull Request: component:docs
|
|
||||||
Documentation: component:docs
|
|
||||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,14 +9,18 @@ the change does.
|
|||||||
|
|
||||||
##### ISSUE TYPE
|
##### ISSUE TYPE
|
||||||
<!--- Pick one below and delete the rest: -->
|
<!--- Pick one below and delete the rest: -->
|
||||||
- Feature Pull Request
|
- Breaking Change
|
||||||
- Bugfix Pull Request
|
- New or Enhanced Feature
|
||||||
- Docs Pull Request
|
- Bug or Docs Fix
|
||||||
|
|
||||||
##### COMPONENT NAME
|
##### COMPONENT NAME
|
||||||
<!--- Name of the module/plugin/module/task -->
|
<!--- Name of the module/plugin/module/task -->
|
||||||
- API
|
- API
|
||||||
- UI
|
- UI
|
||||||
|
- Collection
|
||||||
|
- CLI
|
||||||
|
- Docs
|
||||||
|
- Other
|
||||||
|
|
||||||
##### AWX VERSION
|
##### AWX VERSION
|
||||||
<!--- Paste verbatim output from `make VERSION` between quotes below -->
|
<!--- Paste verbatim output from `make VERSION` between quotes below -->
|
||||||
|
|||||||
19
.github/dependabot.yml
vendored
Normal file
19
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/awx/ui"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
allow:
|
||||||
|
- dependency-type: "production"
|
||||||
|
reviewers:
|
||||||
|
- "AlexSCorey"
|
||||||
|
- "keithjgrant"
|
||||||
|
- "kialam"
|
||||||
|
- "mabashian"
|
||||||
|
- "marshmalien"
|
||||||
|
labels:
|
||||||
|
- "component:ui"
|
||||||
|
- "dependencies"
|
||||||
|
target-branch: "devel"
|
||||||
16
.github/issue_labeler.yml
vendored
Normal file
16
.github/issue_labeler.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
needs_triage:
|
||||||
|
- '.*'
|
||||||
|
"type:bug":
|
||||||
|
- "Bug Summary"
|
||||||
|
"type:enhancement":
|
||||||
|
- "Feature Summary"
|
||||||
|
"component:ui":
|
||||||
|
- "\\[X\\] UI"
|
||||||
|
"component:api":
|
||||||
|
- "\\[X\\] API"
|
||||||
|
"component:docs":
|
||||||
|
- "\\[X\\] Docs"
|
||||||
|
"component:awx_collection":
|
||||||
|
- "\\[X\\] Collection"
|
||||||
|
"component:cli":
|
||||||
|
- "\\[X\\] awxkit"
|
||||||
19
.github/pr_labeler.yml
vendored
Normal file
19
.github/pr_labeler.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"component:api":
|
||||||
|
- any: ["awx/**/*", "!awx/ui/**"]
|
||||||
|
|
||||||
|
"component:ui":
|
||||||
|
- any: ["awx/ui/**/*"]
|
||||||
|
|
||||||
|
"component:docs":
|
||||||
|
- any: ["docs/**/*"]
|
||||||
|
|
||||||
|
"component:cli":
|
||||||
|
- any: ["awxkit/**/*"]
|
||||||
|
|
||||||
|
"component:awx_collection":
|
||||||
|
- any: ["awx_collection/**/*"]
|
||||||
|
|
||||||
|
"dependencies":
|
||||||
|
- any: ["awx/ui/package.json"]
|
||||||
|
- any: ["awx/requirements/*.txt"]
|
||||||
|
- any: ["awx/requirements/requirements.in"]
|
||||||
111
.github/triage_replies.md
vendored
Normal file
111
.github/triage_replies.md
vendored
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
## General
|
||||||
|
- For the roundup of all the different mailing lists available from AWX, Ansible, and beyond visit: https://docs.ansible.com/ansible/latest/community/communication.html
|
||||||
|
- Hello, we think your question is answered in our FAQ. Does this: https://www.ansible.com/products/awx-project/faq cover your question?
|
||||||
|
- You can find the latest documentation here: https://docs.ansible.com/automation-controller/latest/html/userguide/index.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## PRs/Issues
|
||||||
|
|
||||||
|
### Visit our mailing list
|
||||||
|
- Hello, this appears to be less of a bug report or feature request and more of a question. Could you please ask this on our mailing list? See https://github.com/ansible/awx/#get-involved for information for ways to connect with us.
|
||||||
|
|
||||||
|
### Denied Submission
|
||||||
|
|
||||||
|
- Hi! \
|
||||||
|
\
|
||||||
|
Thanks very much for your submission to AWX. It means a lot to us that you have taken time to contribute. \
|
||||||
|
\
|
||||||
|
At this time we do not want to merge this PR. Our reasons for this are: \
|
||||||
|
\
|
||||||
|
(A) INSERT ITEM HERE \
|
||||||
|
\
|
||||||
|
Please know that we are always up for discussion but this project is very active. Because of this, we're unlikely to see comments made on closed PRs, and we lock them after some time. If you or anyone else has any further questions, please let us know by using any of the communication methods listed in the page below: \
|
||||||
|
\
|
||||||
|
https://github.com/ansible/awx/#get-involved \
|
||||||
|
\
|
||||||
|
In the future, sometimes starting a discussion on the development list prior to implementing a feature can make getting things included a little easier, but it is not always necessary. \
|
||||||
|
\
|
||||||
|
Thank you once again for this and your interest in AWX!
|
||||||
|
|
||||||
|
|
||||||
|
### No Progress Issue
|
||||||
|
- Hi! \
|
||||||
|
\
|
||||||
|
Thank you very much for for this issue. It means a lot to us that you have taken time to contribute by opening this report. \
|
||||||
|
\
|
||||||
|
On this issue, there were comments added but it has been some time since then without response. At this time we are closing this issue. If you get time to address the comments we can reopen the issue if you can contact us by using any of the communication methods listed in the page below: \
|
||||||
|
\
|
||||||
|
https://github.com/ansible/awx/#get-involved \
|
||||||
|
\
|
||||||
|
Thank you once again for this and your interest in AWX!
|
||||||
|
|
||||||
|
|
||||||
|
### No Progress PR
|
||||||
|
- Hi! \
|
||||||
|
\
|
||||||
|
Thank you very much for your submission to AWX. It means a lot to us that you have taken time to contribute. \
|
||||||
|
\
|
||||||
|
On this PR, changes were requested but it has been some time since then. We think this PR has merit but without the requested changes we are unable to merge it. At this time we are closing your PR. If you get time to address the changes you are welcome to open another PR or we can reopen this PR upon request if you contact us by using any of the communication methods listed in the page below: \
|
||||||
|
\
|
||||||
|
https://github.com/ansible/awx/#get-involved \
|
||||||
|
\
|
||||||
|
Thank you once again for this and your interest in AWX!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Common
|
||||||
|
|
||||||
|
### Give us more info
|
||||||
|
- Hello, we'd love to help, but we need a little more information about the problem you're having. Screenshots, log outputs, or any reproducers would be very helpful.
|
||||||
|
|
||||||
|
### Code of Conduct
|
||||||
|
- Hello. Please keep in mind that Ansible adheres to a Code of Conduct in its community spaces. The spirit of the code of conduct is to be kind, and this is your friendly reminder to be so. Please see the full code of conduct here if you have questions: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html
|
||||||
|
|
||||||
|
### EE Contents / Community General
|
||||||
|
- Hello. The awx-ee contains the collections and dependencies needed for supported AWX features to function. Anything beyond that (like the community.general package) will require you to build your own EE. For information on how to do that, see https://ansible-builder.readthedocs.io/en/stable/ \
|
||||||
|
\
|
||||||
|
The Ansible Community is looking at building an EE that corresponds to all of the collections inside the ansible package. That may help you if and when it happens; see https://github.com/ansible-community/community-topics/issues/31 for details.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Mailing List Triage
|
||||||
|
|
||||||
|
### Create an issue
|
||||||
|
- Hello, thanks for reaching out on list. We think this merits an issue on our Github, https://github.com/ansible/awx/issues. If you could open an issue up on Github it will get tagged and integrated into our planning and workflow. All future work will be tracked there. Issues should include as much information as possible, including screenshots, log outputs, or any reproducers.
|
||||||
|
|
||||||
|
### Create a Pull Request
|
||||||
|
- Hello, we think your idea is good! Please consider contributing a PR for this following our contributing guidelines: https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md
|
||||||
|
|
||||||
|
### Receptor
|
||||||
|
- You can find the receptor docs here: https://receptor.readthedocs.io/en/latest/
|
||||||
|
- Hello, your issue seems related to receptor. Could you please open an issue in the receptor repository? https://github.com/ansible/receptor. Thanks!
|
||||||
|
|
||||||
|
### Ansible Engine not AWX
|
||||||
|
- Hello, your question seems to be about Ansible development, not about AWX. Try asking on the Ansible-devel specific mailing list: https://groups.google.com/g/ansible-devel
|
||||||
|
- Hello, your question seems to be about using Ansible, not about AWX. https://groups.google.com/g/ansible-project is the best place to visit for user questions about Ansible. Thanks!
|
||||||
|
|
||||||
|
### Ansible Galaxy not AWX
|
||||||
|
- Hey there. That sounds like an FAQ question. Did this: https://www.ansible.com/products/awx-project/faq cover your question?
|
||||||
|
|
||||||
|
### Contributing Guidelines
|
||||||
|
- AWX: https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md
|
||||||
|
- AWX-Operator: https://github.com/ansible/awx-operator/blob/devel/CONTRIBUTING.md
|
||||||
|
|
||||||
|
### AWX Release
|
||||||
|
Subject: Announcing AWX Xa.Ya.za and AWX-Operator Xb.Yb.zb
|
||||||
|
|
||||||
|
- Hi all, \
|
||||||
|
\
|
||||||
|
We're happy to announce that the next release of AWX, version <b>`Xa.Ya.za`</b> is now available! \
|
||||||
|
In addition AWX Operator version <b>`Xb.Yb.zb`</b> has also been released! \
|
||||||
|
\
|
||||||
|
Please see the releases pages for more details: \
|
||||||
|
AWX: https://github.com/ansible/awx/releases/tag/Xa.Ya.za \
|
||||||
|
Operator: https://github.com/ansible/awx-operator/releases/tag/Xb.Yb.zb \
|
||||||
|
\
|
||||||
|
The AWX team.
|
||||||
|
|
||||||
|
## Try latest version
|
||||||
|
- Hello, this issue pertains to an older version of AWX. Try upgrading to the latest version and let us know if that resolves your issue.
|
||||||
214
.github/workflows/ci.yml
vendored
214
.github/workflows/ci.yml
vendored
@@ -1,173 +1,137 @@
|
|||||||
---
|
---
|
||||||
name: CI
|
name: CI
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.base_ref || 'devel' }}
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
jobs:
|
jobs:
|
||||||
api-test:
|
common-tests:
|
||||||
|
name: ${{ matrix.tests.name }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
tests:
|
||||||
|
- name: api-test
|
||||||
|
command: /start_tests.sh
|
||||||
|
label: Run API Tests
|
||||||
|
- name: api-lint
|
||||||
|
command: /var/lib/awx/venv/awx/bin/tox -e linters
|
||||||
|
label: Run API Linters
|
||||||
|
- name: api-swagger
|
||||||
|
command: /start_tests.sh swagger
|
||||||
|
label: Generate API Reference
|
||||||
|
- name: awx-collection
|
||||||
|
command: /start_tests.sh test_collection_all
|
||||||
|
label: Run Collection Tests
|
||||||
|
- name: api-schema
|
||||||
|
label: Check API Schema
|
||||||
|
command: /start_tests.sh detect-schema-change SCHEMA_DIFF_BASE_BRANCH=${{ github.event.pull_request.base.ref }}
|
||||||
|
- name: ui-lint
|
||||||
|
label: Run UI Linters
|
||||||
|
command: make ui-lint
|
||||||
|
- name: ui-test-screens
|
||||||
|
label: Run UI Screens Tests
|
||||||
|
command: make ui-test-screens
|
||||||
|
- name: ui-test-general
|
||||||
|
label: Run UI General Tests
|
||||||
|
command: make ui-test-general
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
- name: Log in to registry
|
- name: Log in to registry
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
- name: Pre-pull image to warm build cache
|
||||||
run: |
|
run: |
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} || :
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ env.BRANCH }} make docker-compose-build
|
||||||
|
|
||||||
- name: Run API Tests
|
- name: ${{ matrix.texts.label }}
|
||||||
run: |
|
run: |
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} /start_tests.sh
|
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} ${{ matrix.tests.command }}
|
||||||
api-lint:
|
dev-env:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
- name: Log in to registry
|
- name: Log in to registry
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
- name: Pre-pull image to warm build cache
|
||||||
run: |
|
run: |
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} || :
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ env.BRANCH }} make docker-compose-build
|
||||||
|
|
||||||
- name: Run API Linters
|
- name: Run smoke test
|
||||||
run: |
|
run: |
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
export DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }}
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} /var/lib/awx/venv/awx/bin/tox -e linters
|
export COMPOSE_TAG=${{ env.BRANCH }}
|
||||||
api-swagger:
|
ansible-playbook tools/docker-compose/ansible/smoke-test.yml -e repo_dir=$(pwd) -v
|
||||||
|
|
||||||
|
awx-operator:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout awx
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: awx
|
||||||
|
|
||||||
- name: Log in to registry
|
- name: Checkout awx-operator
|
||||||
run: |
|
uses: actions/checkout@v2
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
with:
|
||||||
|
repository: ansible/awx-operator
|
||||||
|
path: awx-operator
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
- name: Install playbook dependencies
|
||||||
run: |
|
run: |
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} || :
|
python3 -m pip install docker setuptools_scm
|
||||||
|
|
||||||
- name: Build image
|
- name: Build AWX image
|
||||||
|
working-directory: awx
|
||||||
run: |
|
run: |
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
ansible-playbook -v tools/ansible/build.yml \
|
||||||
|
-e headless=yes \
|
||||||
|
-e awx_image=awx \
|
||||||
|
-e awx_image_tag=ci \
|
||||||
|
-e ansible_python_interpreter=$(which python3)
|
||||||
|
|
||||||
- name: Generate API Reference
|
- name: Run test deployment with awx-operator
|
||||||
|
working-directory: awx-operator
|
||||||
run: |
|
run: |
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
python3 -m pip install -r molecule/requirements.txt
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} /start_tests.sh swagger
|
ansible-galaxy collection install -r molecule/requirements.yml
|
||||||
awx-collection:
|
sudo rm -f $(which kustomize)
|
||||||
runs-on: ubuntu-latest
|
make kustomize
|
||||||
permissions:
|
KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule -v test -s kind
|
||||||
packages: write
|
env:
|
||||||
contents: read
|
AWX_TEST_IMAGE: awx
|
||||||
steps:
|
AWX_TEST_VERSION: ci
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Log in to registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
|
||||||
run: |
|
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
run: |
|
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
|
||||||
|
|
||||||
- name: Run Collection Tests
|
|
||||||
run: |
|
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} /start_tests.sh test_collection_all
|
|
||||||
api-schema:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Log in to registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
|
||||||
run: |
|
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
run: |
|
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
|
||||||
|
|
||||||
- name: Check API Schema
|
|
||||||
run: |
|
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} /start_tests.sh detect-schema-change
|
|
||||||
ui-lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Log in to registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
|
||||||
run: |
|
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
run: |
|
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
|
||||||
|
|
||||||
- name: Run UI Linters
|
|
||||||
run: |
|
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} make ui-lint
|
|
||||||
ui-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Log in to registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
|
||||||
run: |
|
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
run: |
|
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose-build
|
|
||||||
|
|
||||||
- name: Run UI Tests
|
|
||||||
run: |
|
|
||||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
|
||||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }} make ui-test
|
|
||||||
|
|||||||
30
.github/workflows/devel_image.yml
vendored
30
.github/workflows/devel_image.yml
vendored
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
name: Push Development Image
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- devel
|
|
||||||
jobs:
|
|
||||||
push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Log in to registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
|
||||||
run: |
|
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/}
|
|
||||||
|
|
||||||
- name: Build image
|
|
||||||
run: |
|
|
||||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${GITHUB_REF##*/} make docker-compose-build
|
|
||||||
|
|
||||||
- name: Push image
|
|
||||||
run: |
|
|
||||||
docker push ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/}
|
|
||||||
43
.github/workflows/devel_images.yml
vendored
Normal file
43
.github/workflows/devel_images.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
name: Build/Push Development Images
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- devel
|
||||||
|
- release_*
|
||||||
|
jobs:
|
||||||
|
push:
|
||||||
|
if: endsWith(github.repository, '/awx') || startsWith(github.ref, 'refs/heads/release_')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
|
- name: Pre-pull image to warm build cache
|
||||||
|
run: |
|
||||||
|
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
||||||
|
docker pull ghcr.io/${{ github.repository_owner }}/awx_kube_devel:${GITHUB_REF##*/} || :
|
||||||
|
|
||||||
|
- name: Build images
|
||||||
|
run: |
|
||||||
|
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${GITHUB_REF##*/} make docker-compose-build
|
||||||
|
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-dev-build
|
||||||
|
|
||||||
|
- name: Push image
|
||||||
|
run: |
|
||||||
|
docker push ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/}
|
||||||
|
docker push ghcr.io/${{ github.repository_owner }}/awx_kube_devel:${GITHUB_REF##*/}
|
||||||
10
.github/workflows/e2e_test.yml
vendored
10
.github/workflows/e2e_test.yml
vendored
@@ -18,6 +18,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
- name: Install system deps
|
- name: Install system deps
|
||||||
run: sudo apt-get install -y gettext
|
run: sudo apt-get install -y gettext
|
||||||
|
|
||||||
@@ -85,7 +93,7 @@ jobs:
|
|||||||
-e CYPRESS_baseUrl="https://$AWX_IP:8043" \
|
-e CYPRESS_baseUrl="https://$AWX_IP:8043" \
|
||||||
-e CYPRESS_AWX_E2E_USERNAME=admin \
|
-e CYPRESS_AWX_E2E_USERNAME=admin \
|
||||||
-e CYPRESS_AWX_E2E_PASSWORD='password' \
|
-e CYPRESS_AWX_E2E_PASSWORD='password' \
|
||||||
-e COMMAND="npm run cypress-gha" \
|
-e COMMAND="npm run cypress-concurrently-gha" \
|
||||||
-v /dev/shm:/dev/shm \
|
-v /dev/shm:/dev/shm \
|
||||||
-v $PWD:/e2e \
|
-v $PWD:/e2e \
|
||||||
-w /e2e \
|
-w /e2e \
|
||||||
|
|||||||
21
.github/workflows/label_issue.yml
vendored
Normal file
21
.github/workflows/label_issue.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Label Issue
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Label Issue
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Label Issue
|
||||||
|
uses: github/issue-labeler@v2.4.1
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
not-before: 2021-12-07T07:00:00Z
|
||||||
|
configuration-path: .github/issue_labeler.yml
|
||||||
|
enable-versioned-regex: 0
|
||||||
20
.github/workflows/label_pr.yml
vendored
Normal file
20
.github/workflows/label_pr.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: Label PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Label PR
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Label PR
|
||||||
|
uses: actions/labeler@v3
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
configuration-path: .github/pr_labeler.yml
|
||||||
73
.github/workflows/promote.yml
vendored
Normal file
73
.github/workflows/promote.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: Promote Release
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
promote:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout awx
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python${{ env.py_version }} -m pip install wheel twine setuptools-scm
|
||||||
|
|
||||||
|
- name: Set official collection namespace
|
||||||
|
run: echo collection_namespace=awx >> $GITHUB_ENV
|
||||||
|
if: ${{ github.repository_owner == 'ansible' }}
|
||||||
|
|
||||||
|
- name: Set unofficial collection namespace
|
||||||
|
run: echo collection_namespace=${{ github.repository_owner }} >> $GITHUB_ENV
|
||||||
|
if: ${{ github.repository_owner != 'ansible' }}
|
||||||
|
|
||||||
|
- name: Build collection and publish to galaxy
|
||||||
|
run: |
|
||||||
|
COLLECTION_TEMPLATE_VERSION=true COLLECTION_NAMESPACE=${{ env.collection_namespace }} make build_collection
|
||||||
|
ansible-galaxy collection publish \
|
||||||
|
--token=${{ secrets.GALAXY_TOKEN }} \
|
||||||
|
awx_collection_build/${{ env.collection_namespace }}-awx-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
|
- name: Set official pypi info
|
||||||
|
run: echo pypi_repo=pypi >> $GITHUB_ENV
|
||||||
|
if: ${{ github.repository_owner == 'ansible' }}
|
||||||
|
|
||||||
|
- name: Set unofficial pypi info
|
||||||
|
run: echo pypi_repo=testpypi >> $GITHUB_ENV
|
||||||
|
if: ${{ github.repository_owner != 'ansible' }}
|
||||||
|
|
||||||
|
- name: Build awxkit and upload to pypi
|
||||||
|
run: |
|
||||||
|
cd awxkit && python3 setup.py bdist_wheel
|
||||||
|
twine upload \
|
||||||
|
-r ${{ env.pypi_repo }} \
|
||||||
|
-u ${{ secrets.PYPI_USERNAME }} \
|
||||||
|
-p ${{ secrets.PYPI_PASSWORD }} \
|
||||||
|
dist/*
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
run: |
|
||||||
|
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
|
- name: Log in to Quay
|
||||||
|
run: |
|
||||||
|
echo ${{ secrets.QUAY_TOKEN }} | docker login quay.io -u ${{ secrets.QUAY_USER }} --password-stdin
|
||||||
|
|
||||||
|
- name: Re-tag and promote awx image
|
||||||
|
run: |
|
||||||
|
docker pull ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}
|
||||||
|
docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
|
||||||
|
docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:latest
|
||||||
|
docker push quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
|
||||||
|
docker push quay.io/${{ github.repository }}:latest
|
||||||
|
|
||||||
119
.github/workflows/stage.yml
vendored
Normal file
119
.github/workflows/stage.yml
vendored
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
name: Stage Release
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'AWX version.'
|
||||||
|
required: true
|
||||||
|
default: ''
|
||||||
|
operator_version:
|
||||||
|
description: 'Operator version. Leave blank to skip staging awx-operator.'
|
||||||
|
default: ''
|
||||||
|
confirm:
|
||||||
|
description: 'Are you sure? Set this to yes.'
|
||||||
|
required: true
|
||||||
|
default: 'no'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Verify inputs
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ ${{ github.event.inputs.confirm }} != "yes" ]]; then
|
||||||
|
>&2 echo "Confirm must be 'yes'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${{ github.event.inputs.version }} == "" ]]; then
|
||||||
|
>&2 echo "Set version to continue."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
- name: Checkout awx
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: awx
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
|
- name: Checkout awx-logos
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ansible/awx-logos
|
||||||
|
path: awx-logos
|
||||||
|
|
||||||
|
- name: Checkout awx-operator
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ${{ github.repository_owner }}/awx-operator
|
||||||
|
path: awx-operator
|
||||||
|
|
||||||
|
- name: Install playbook dependencies
|
||||||
|
run: |
|
||||||
|
python3 -m pip install docker setuptools_scm
|
||||||
|
|
||||||
|
- name: Build and stage AWX
|
||||||
|
working-directory: awx
|
||||||
|
run: |
|
||||||
|
ansible-playbook -v tools/ansible/build.yml \
|
||||||
|
-e registry=ghcr.io \
|
||||||
|
-e registry_username=${{ github.actor }} \
|
||||||
|
-e registry_password=${{ secrets.GITHUB_TOKEN }} \
|
||||||
|
-e awx_image=${{ github.repository }} \
|
||||||
|
-e awx_version=${{ github.event.inputs.version }} \
|
||||||
|
-e ansible_python_interpreter=$(which python3) \
|
||||||
|
-e push=yes \
|
||||||
|
-e awx_official=yes
|
||||||
|
|
||||||
|
- name: Build and stage awx-operator
|
||||||
|
working-directory: awx-operator
|
||||||
|
run: |
|
||||||
|
BUILD_ARGS="--build-arg DEFAULT_AWX_VERSION=${{ github.event.inputs.version }} \
|
||||||
|
--build-arg OPERATOR_VERSION=${{ github.event.inputs.operator_version }}" \
|
||||||
|
IMAGE_TAG_BASE=ghcr.io/${{ github.repository_owner }}/awx-operator \
|
||||||
|
VERSION=${{ github.event.inputs.operator_version }} make docker-build docker-push
|
||||||
|
|
||||||
|
- name: Run test deployment with awx-operator
|
||||||
|
working-directory: awx-operator
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r molecule/requirements.txt
|
||||||
|
ansible-galaxy collection install -r molecule/requirements.yml
|
||||||
|
sudo rm -f $(which kustomize)
|
||||||
|
make kustomize
|
||||||
|
KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule test -s kind
|
||||||
|
env:
|
||||||
|
AWX_TEST_IMAGE: ${{ github.repository }}
|
||||||
|
AWX_TEST_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
|
- name: Create draft release for AWX
|
||||||
|
working-directory: awx
|
||||||
|
run: |
|
||||||
|
ansible-playbook -v tools/ansible/stage.yml \
|
||||||
|
-e repo=${{ github.repository }} \
|
||||||
|
-e awx_image=ghcr.io/${{ github.repository }} \
|
||||||
|
-e version=${{ github.event.inputs.version }} \
|
||||||
|
-e github_token=${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create draft release for awx-operator
|
||||||
|
if: ${{ github.event.inputs.operator_version != '' }}
|
||||||
|
working-directory: awx
|
||||||
|
run: |
|
||||||
|
ansible-playbook tools/ansible/stage.yml \
|
||||||
|
-e version=${{ github.event.inputs.operator_version }} \
|
||||||
|
-e repo=${{ github.repository_owner }}/awx-operator \
|
||||||
|
-e github_token=${{ secrets.AWX_OPERATOR_RELEASE_TOKEN }}
|
||||||
13
.github/workflows/upload_schema.yml
vendored
13
.github/workflows/upload_schema.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- devel
|
- devel
|
||||||
|
- release_**
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -13,13 +14,21 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get python version from Makefile
|
||||||
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install python ${{ env.py_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.py_version }}
|
||||||
|
|
||||||
- name: Log in to registry
|
- name: Log in to registry
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
- name: Pre-pull image to warm build cache
|
||||||
run: |
|
run: |
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/}
|
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
@@ -38,6 +47,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ansible localhost -c local, -m command -a "{{ ansible_python_interpreter + ' -m pip install boto3'}}"
|
ansible localhost -c local, -m command -a "{{ ansible_python_interpreter + ' -m pip install boto3'}}"
|
||||||
ansible localhost -c local -m aws_s3 \
|
ansible localhost -c local -m aws_s3 \
|
||||||
-a 'src=${{ github.workspace }}/schema.json bucket=awx-public-ci-files object=schema.json mode=put permission=public-read'
|
-a "src=${{ github.workspace }}/schema.json bucket=awx-public-ci-files object=${GITHUB_REF##*/}/schema.json mode=put permission=public-read"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -7,6 +7,9 @@ reference-schema.json
|
|||||||
.tags
|
.tags
|
||||||
.tags1
|
.tags1
|
||||||
|
|
||||||
|
# User level pre-commit hooks
|
||||||
|
pre-commit-user
|
||||||
|
|
||||||
# Tower
|
# Tower
|
||||||
awx-dev
|
awx-dev
|
||||||
awx/settings/local_*.py*
|
awx/settings/local_*.py*
|
||||||
@@ -35,13 +38,13 @@ awx/ui/build
|
|||||||
awx/ui/.env.local
|
awx/ui/.env.local
|
||||||
awx/ui/instrumented
|
awx/ui/instrumented
|
||||||
rsyslog.pid
|
rsyslog.pid
|
||||||
tools/prometheus/data
|
|
||||||
tools/docker-compose/ansible/awx_dump.sql
|
tools/docker-compose/ansible/awx_dump.sql
|
||||||
tools/docker-compose/Dockerfile
|
tools/docker-compose/Dockerfile
|
||||||
tools/docker-compose/_build
|
tools/docker-compose/_build
|
||||||
tools/docker-compose/_sources
|
tools/docker-compose/_sources
|
||||||
tools/docker-compose/overrides/
|
tools/docker-compose/overrides/
|
||||||
tools/docker-compose-minikube/_sources
|
tools/docker-compose-minikube/_sources
|
||||||
|
tools/docker-compose/keycloak.awx.realm.json
|
||||||
|
|
||||||
# Tower setup playbook testing
|
# Tower setup playbook testing
|
||||||
setup/test/roles/postgresql
|
setup/test/roles/postgresql
|
||||||
@@ -58,6 +61,7 @@ __pycache__
|
|||||||
/dist
|
/dist
|
||||||
/*.egg-info
|
/*.egg-info
|
||||||
*.py[c,o]
|
*.py[c,o]
|
||||||
|
/.eggs
|
||||||
|
|
||||||
# JavaScript
|
# JavaScript
|
||||||
/Gruntfile.js
|
/Gruntfile.js
|
||||||
@@ -149,6 +153,9 @@ use_dev_supervisor.txt
|
|||||||
/sanity/
|
/sanity/
|
||||||
/awx_collection_build/
|
/awx_collection_build/
|
||||||
|
|
||||||
|
# Setup for metrics gathering
|
||||||
|
tools/prometheus/prometheus.yml
|
||||||
|
|
||||||
.idea/*
|
.idea/*
|
||||||
*.unison.tmp
|
*.unison.tmp
|
||||||
*.#
|
*.#
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ ignore: |
|
|||||||
# vault files
|
# vault files
|
||||||
awx/main/tests/data/ansible_utils/playbooks/valid/vault.yml
|
awx/main/tests/data/ansible_utils/playbooks/valid/vault.yml
|
||||||
awx/ui/test/e2e/tests/smoke-vars.yml
|
awx/ui/test/e2e/tests/smoke-vars.yml
|
||||||
|
awx/ui/node_modules
|
||||||
|
tools/docker-compose/_sources
|
||||||
|
|
||||||
extends: default
|
extends: default
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
line-length: disable
|
line-length: disable
|
||||||
|
truthy: disable
|
||||||
|
|||||||
519
CHANGELOG.md
519
CHANGELOG.md
@@ -1,520 +1,7 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
# 19.3.0 (August 12, 2021)
|
**Note:** This file is deprecated and will be removed at some point in a future release.
|
||||||
|
|
||||||
- Fixed threading bug that would sometimes cause jobs to randomly fail (https://github.com/ansible/awx/pull/10537)
|
Starting with AWX 20, release notes are published to [GitHub Releases](https://github.com/ansible/awx/releases).
|
||||||
- Fixed race where app would crash when postgres is not available (https://github.com/ansible/awx/pull/10583)
|
|
||||||
- Add support for workflow node aliasing via identifier field (https://github.com/ansible/awx/pull/10592)
|
|
||||||
- Add UI support for management jobs in workflows (https://github.com/ansible/awx/pull/10572)
|
|
||||||
- Show PAT as part of bulk delete list (https://github.com/ansible/awx/pull/10794)
|
|
||||||
- Return 404 for ad_hoc_command_events list api. Remove api endtpoint (https://github.com/ansible/awx/pull/10716)
|
|
||||||
- Fix multiple accessibility violations (https://github.com/ansible/awx/pull/10713)
|
|
||||||
- Fix ignoring --no-color for awx-manage list_instances command (https://github.com/ansible/awx/pull/10668)
|
|
||||||
- Fix to handle ask_* parameters correctly when set false (https://github.com/ansible/awx/pull/10108)
|
|
||||||
- Default source_project to organization for inventory source (https://github.com/ansible/awx/pull/10573)
|
|
||||||
- Fix headers missing in webhook notification request (https://github.com/ansible/awx/pull/10566)
|
|
||||||
- Avoid double LDAP updates (https://github.com/ansible/awx/pull/9703)
|
|
||||||
- introduced a pre-flight check for postgres 12 (https://github.com/ansible/awx/pull/10425)
|
|
||||||
- Fix Job Settings Page Break on Firefox (https://github.com/ansible/awx/pull/10523)
|
|
||||||
- bumped django version to 2.2.20 (https://github.com/ansible/awx/pull/10564)
|
|
||||||
- Add Thycotic SecretServer support (https://github.com/ansible/awx/pull/10632)
|
|
||||||
|
|
||||||
# 19.2.2 (June 28, 2021)
|
For older release notes, see https://github.com/ansible/awx/blob/19.3.0/CHANGELOG.md.
|
||||||
|
|
||||||
- Fixed bug where symlinks pointing to directories were not preserved (https://github.com/ansible/ansible-runner/pull/736)
|
|
||||||
- Various bugfixes found during testing (https://github.com/ansible/awx/pull/10532)
|
|
||||||
|
|
||||||
# 19.2.1 (June 17, 2021)
|
|
||||||
|
|
||||||
- There are now 2 default Instance Groups: 'controlplane' and 'default' (https://github.com/ansible/awx/pull/10324)
|
|
||||||
- Removed deprecated modules: `tower_send`, `tower_receive`, `tower_workflow_template` (https://github.com/ansible/awx/pull/9980)
|
|
||||||
- Improved UI performance when a large amount of events are being emitted by jobs (https://github.com/ansible/awx/pull/10053)
|
|
||||||
- Settings UI Revert All button now issues a DELETE instead of PATCHing all fields (https://github.com/ansible/awx/pull/10376)
|
|
||||||
- Fixed a bug with the schedule date/time picker in Firefox (https://github.com/ansible/awx/pull/10291)
|
|
||||||
- UI now preselects the system default Galaxy credential when creating a new organization (https://github.com/ansible/awx/pull/10395)
|
|
||||||
- Added favicon (https://github.com/ansible/awx/pull/10388)
|
|
||||||
- Removed `not` option from smart inventory host filter search as it's not supported by the API (https://github.com/ansible/awx/pull/10380)
|
|
||||||
- Added button to allow user to refetch project revision after project sync has finished (https://github.com/ansible/awx/pull/10334)
|
|
||||||
- Fixed bug where extraneous CONFIG requests were made on logout (https://github.com/ansible/awx/pull/10379)
|
|
||||||
- Fixed bug where users were unable to cancel inventory syncs (https://github.com/ansible/awx/pull/10346)
|
|
||||||
- Added missing dashboard graph filters (https://github.com/ansible/awx/pull/10349)
|
|
||||||
- Added support for typing in to single select lookup form fields (https://github.com/ansible/awx/pull/10257)
|
|
||||||
- Fixed various bugs related to user sessions (https://github.com/ansible/awx/pull/9908)
|
|
||||||
- Fixed bug where sorting in modals would close the modal (https://github.com/ansible/awx/pull/10215)
|
|
||||||
- Added support for Red Hat Insights as an inventory source (https://github.com/ansible/awx/pull/8650)
|
|
||||||
- Fixed bugs when selecting items in a list then sorting/paginating (https://github.com/ansible/awx/pull/10329)
|
|
||||||
|
|
||||||
# 19.2.0 (June 1, 2021)
|
|
||||||
- Fixed race condition that would sometimes cause jobs to error out at the very end of an otherwise successful run (https://github.com/ansible/receptor/pull/328)
|
|
||||||
- Fixes bug where users were unable to click on text next to checkboxes in modals (https://github.com/ansible/awx/pull/10279)
|
|
||||||
- Have the project update playbook warn if role/collection syncing is disabled. (https://github.com/ansible/awx/pull/10068)
|
|
||||||
- Move irc references to point to irc.libera.chat (https://github.com/ansible/awx/pull/10295)
|
|
||||||
- Fixes bug where activity stream changes were displaying as [object object] (https://github.com/ansible/awx/pull/10267)
|
|
||||||
- Update awxkit to enable export of Galaxy credentials associated to organizations (https://github.com/ansible/awx/pull/10271)
|
|
||||||
- Bump receptor and receptorctl versions to 1.0.0a2 (https://github.com/ansible/awx/pull/10261)
|
|
||||||
- Add the ability to disable local authentication (https://github.com/ansible/awx/pull/10102)
|
|
||||||
- Show error if no Execution Environment is found on project sync/job run (https://github.com/ansible/awx/pull/10183)
|
|
||||||
- Allow for editing and deleting managed_by_tower EEs from API/UI (https://github.com/ansible/awx/pull/10173)
|
|
||||||
|
|
||||||
|
|
||||||
# 19.1.0 (May 1, 2021)
|
|
||||||
|
|
||||||
- Custom inventory scripts have been removed from the API https://github.com/ansible/awx/pull/9822
|
|
||||||
- Old scripts can be exported via `awx-manage export_custom_scripts`
|
|
||||||
- Fixed a bug where ad-hoc commands targeted against multiple hosts would run against only 1 host https://github.com/ansible/awx/pull/9973
|
|
||||||
- AWX will now look for a top-level requirements.yml when installing collections / roles in project updates https://github.com/ansible/awx/pull/9945
|
|
||||||
- Improved error handling when Container Group pods fail to launch https://github.com/ansible/awx/pull/10025
|
|
||||||
- Added ability to set server-side password policies using Django's AUTH_PASSWORD_VALIDATORS setting https://github.com/ansible/awx/pull/9999
|
|
||||||
- Bumped versions of Ansible Runner & AWX EE https://github.com/ansible/awx/pull/10013
|
|
||||||
- If you have built any custom EEs on top of awx-ee 0.1.0, you will need to rebuild on top of 0.2.0.
|
|
||||||
- Remove legacy resource profiling code https://github.com/ansible/awx/pull/9883
|
|
||||||
|
|
||||||
# 19.0.0 (April 7, 2021)
|
|
||||||
|
|
||||||
- AWX now runs on Python 3.8 (https://github.com/ansible/awx/pull/8778/)
|
|
||||||
- Fixed inventories-from-projects when running in Kubernetes (https://github.com/ansible/awx/pull/9741)
|
|
||||||
- Fixed a bug where a slash was appended to invetory file paths in UI dropdown (https://github.com/ansible/awx/pull/9713)
|
|
||||||
- Fix a bug with large file parsing in project sync (https://github.com/ansible/awx/pull/9627)
|
|
||||||
- Fix k8s credentials that use a custom ca cert (https://github.com/ansible/awx/pull/9744)
|
|
||||||
- Fix a bug that allowed a user to attempt deleting a running job (https://github.com/ansible/awx/pull/9758)
|
|
||||||
- Fixed the Kubernetes Pod reaper to properly delete Pods launched by Receptor (https://github.com/ansible/awx/pull/9819)
|
|
||||||
- AWX Collection Modules: added ability to set instance groups for organization, job templates, and inventories. (https://github.com/ansible/awx/pull/9804)
|
|
||||||
- Fixed CSP violation errors on job details and job settings views (https://github.com/ansible/awx/pull/9818)
|
|
||||||
- Added support for convergence any/all on workflow nodes (https://github.com/ansible/awx/pull/9737)
|
|
||||||
- Fixed race condition that causes InvalidGitRepositoryError (https://github.com/ansible/awx/pull/9754)
|
|
||||||
- Added support for Execution Environments to the Activity Stream (https://github.com/ansible/awx/issues/9308)
|
|
||||||
- Fixed a bug that improperly formats OpenSSH keys specified in custom Credential Types (https://github.com/ansible/awx/issues/9361)
|
|
||||||
- Fixed an HTTP 500 error for unauthenticated users (https://github.com/ansible/awx/pull/9725)
|
|
||||||
- Added subscription wizard: https://github.com/ansible/awx/pull/9496
|
|
||||||
|
|
||||||
# 18.0.0 (March 23, 2021)
|
|
||||||
|
|
||||||
**IMPORTANT INSTALL AND UPGRADE NOTES**
|
|
||||||
|
|
||||||
Starting in version 18.0, the [AWX Operator](https://github.com/ansible/awx-operator) is the preferred way to install AWX: https://github.com/ansible/awx/blob/devel/INSTALL.md#installing-awx
|
|
||||||
|
|
||||||
If you have a pre-existing installation of AWX that utilizes the Docker-based installation method, this install method has ** notably changed** from 17.x to 18.x. For details, please see:
|
|
||||||
|
|
||||||
- https://groups.google.com/g/awx-project/c/47MjWSUQaOc/m/bCjSDn0eBQAJ
|
|
||||||
- https://github.com/ansible/awx/blob/devel/tools/docker-compose
|
|
||||||
- https://github.com/ansible/awx/blob/devel/tools/docker-compose/docs/data_migration.md
|
|
||||||
|
|
||||||
### Introducing Execution Environments
|
|
||||||
|
|
||||||
After a herculean effort from a number of contributors, we're excited to announce that AWX 18.0.0 introduces a new concept called Execution Environments.
|
|
||||||
|
|
||||||
Execution Environments are container images which consist of everything necessary to run a playbook within AWX, and which drive the entire management and lifecycle of playbook execution runtime in AWX: https://github.com/ansible/awx/issues/5157. This means that going forward, AWX no longer utilizes the [bubblewrap](https://github.com/containers/bubblewrap) project for playbook isolation, but instead utilizes a container per playbook run.
|
|
||||||
|
|
||||||
Much like custom virtualenvs, custom Execution Environments can be crafted to specify additional Python or system-level dependencies. [Ansible Builder](https://github.com/ansible/ansible-builder) outputs images you can upload to your registry which can *then* be defined in AWX and utilized for playbook runs.
|
|
||||||
|
|
||||||
To learn more about Ansible Builder and Execution Environments, see: https://www.ansible.com/blog/introduction-to-ansible-builder
|
|
||||||
|
|
||||||
### Other Notable Changes
|
|
||||||
|
|
||||||
- Removed `installer` directory.
|
|
||||||
- The Kubernetes installer has been removed in favor of [AWX Operator](https://github.com/ansible/awx-operator). Official images for Operator-based installs are no longer hosted on Docker Hub, but are instead available on [Quay](https://quay.io/repository/ansible/awx?tab=tags).
|
|
||||||
- The "Local Docker" install method has been removed in favor of the development environment. Details can be found at: https://github.com/ansible/awx/blob/devel/tools/docker-compose/README.md
|
|
||||||
- Removal of custom virtual environments https://github.com/ansible/awx/pull/9498
|
|
||||||
- Custom virtual environments have been replaced by Execution Environments https://github.com/ansible/awx/pull/9570
|
|
||||||
- The default Container Group Pod definition has changed. All custom Pod specs have been reset. https://github.com/ansible/awx/commit/05ef51f710dad8f8036bc5acee4097db4adc0d71
|
|
||||||
- Added user interface for the activity stream: https://github.com/ansible/awx/pull/9083
|
|
||||||
- Converted many of the top-level list views (Jobs, Teams, Hosts, Inventories, Projects, and more) to a new, permanent table component for substantially increased responsiveness, usability, maintainability, and other 'ility's: https://github.com/ansible/awx/pull/8970, https://github.com/ansible/awx/pull/9182 and many others!
|
|
||||||
- Added support for Centrify Vault (https://www.centrify.com) as a credential lookup plugin (https://github.com/ansible/awx/pull/9542)
|
|
||||||
- Added support for namespaces in Hashicorp Vault credential plugin (https://github.com/ansible/awx/pull/9590)
|
|
||||||
- Added click-to-expand details for job tables
|
|
||||||
- Added search filtering to job output https://github.com/ansible/awx/pull/9208
|
|
||||||
- Added the new migration, update, and "installation in progress" page https://github.com/ansible/awx/pull/9123
|
|
||||||
- Added the user interface for job settings https://github.com/ansible/awx/pull/8661
|
|
||||||
- Runtime errors from jobs are now displayed, along with an explanation for what went wrong, on the output page https://github.com/ansible/awx/pull/8726
|
|
||||||
- You can now cancel a running job from its output and details panel https://github.com/ansible/awx/pull/9199
|
|
||||||
- Fixed a bug where launch prompt inputs were unexpectedly deposited in the url: https://github.com/ansible/awx/pull/9231
|
|
||||||
- Playbook, credential type, and inventory file inputs now support type-ahead and manual type-in! https://github.com/ansible/awx/pull/9120
|
|
||||||
- Added ability to relaunch against failed hosts: https://github.com/ansible/awx/pull/9225
|
|
||||||
- Added pending workflow approval count to the application header https://github.com/ansible/awx/pull/9334
|
|
||||||
- Added user interface for management jobs: https://github.com/ansible/awx/pull/9224
|
|
||||||
- Added toast message to show notification template test result to notification templates list https://github.com/ansible/awx/pull/9318
|
|
||||||
- Replaced CodeMirror with AceEditor for editing template variables and notification templates https://github.com/ansible/awx/pull/9281
|
|
||||||
- Added support for filtering and pagination on job output https://github.com/ansible/awx/pull/9208
|
|
||||||
- Added support for html in custom login text https://github.com/ansible/awx/pull/9519
|
|
||||||
|
|
||||||
# 17.1.0 (March 9, 2021)
|
|
||||||
- Addressed a security issue in AWX (CVE-2021-20253)
|
|
||||||
- Fixed a bug permissions error related to redis in K8S-based deployments: https://github.com/ansible/awx/issues/9401
|
|
||||||
|
|
||||||
# 17.0.1 (January 26, 2021)
|
|
||||||
- Fixed pgdocker directory permissions issue with Local Docker installer: https://github.com/ansible/awx/pull/9152
|
|
||||||
- Fixed a bug in the UI which caused toggle settings to not be changed when clicked: https://github.com/ansible/awx/pull/9093
|
|
||||||
|
|
||||||
# 17.0.0 (January 22, 2021)
|
|
||||||
- AWX now requires PostgreSQL 12 by default: https://github.com/ansible/awx/pull/8943
|
|
||||||
**Note:** users who encounter permissions errors at upgrade time should `chown -R ~/.awx/pgdocker` to ensure it's owned by the user running the install playbook
|
|
||||||
- Added support for region name for OpenStack inventory: https://github.com/ansible/awx/issues/5080
|
|
||||||
- Added the ability to chain undefined attributes in custom notification templates: https://github.com/ansible/awx/issues/8677
|
|
||||||
- Dramatically simplified the `image_build` role: https://github.com/ansible/awx/pull/8980
|
|
||||||
- Fixed a bug which can cause schema migrations to fail at install time: https://github.com/ansible/awx/issues/9077
|
|
||||||
- Fixed a bug which caused the `is_superuser` user property to be out of date in certain circumstances: https://github.com/ansible/awx/pull/8833
|
|
||||||
- Fixed a bug which sometimes results in race conditions on setting access: https://github.com/ansible/awx/pull/8580
|
|
||||||
- Fixed a bug which sometimes causes an unexpected delay in stdout for some playbooks: https://github.com/ansible/awx/issues/9085
|
|
||||||
- (UI) Added support for credential password prompting on job launch: https://github.com/ansible/awx/pull/9028
|
|
||||||
- (UI) Added the ability to configure LDAP settings in the UI: https://github.com/ansible/awx/issues/8291
|
|
||||||
- (UI) Added a sync button to the Project detail view: https://github.com/ansible/awx/issues/8847
|
|
||||||
- (UI) Added a form for configuring Google Outh 2.0 settings: https://github.com/ansible/awx/pull/8762
|
|
||||||
- (UI) Added searchable keys and related keys to the Credentials list: https://github.com/ansible/awx/issues/8603
|
|
||||||
- (UI) Added support for advanced search and copying to Notification Templates: https://github.com/ansible/awx/issues/7879
|
|
||||||
- (UI) Added support for prompting on workflow nodes: https://github.com/ansible/awx/issues/5913
|
|
||||||
- (UI) Added support for session timeouts: https://github.com/ansible/awx/pull/8250
|
|
||||||
- (UI) Fixed a bug that broke websocket streaming for the insecure ws:// protocol: https://github.com/ansible/awx/pull/8877
|
|
||||||
- (UI) Fixed a bug in the user interface when a translation for the browser's preferred locale isn't available: https://github.com/ansible/awx/issues/8884
|
|
||||||
- (UI) Fixed bug where navigating from one survey question form directly to another wasn't reloading the form: https://github.com/ansible/awx/issues/7522
|
|
||||||
- (UI) Fixed a bug which can cause an uncaught error while launching a Job Template: https://github.com/ansible/awx/issues/8936
|
|
||||||
- Updated autobahn to address CVE-2020-35678
|
|
||||||
|
|
||||||
## 16.0.0 (December 10, 2020)
|
|
||||||
- AWX now ships with a reimagined user interface. **Please read this before upgrading:** https://groups.google.com/g/awx-project/c/KuT5Ao92HWo
|
|
||||||
- Removed support for syncing inventory from Red Hat CloudForms - https://github.com/ansible/awx/commit/0b701b3b2
|
|
||||||
- Removed support for Mercurial-based project updates - https://github.com/ansible/awx/issues/7932
|
|
||||||
- Upgraded NodeJS to actively maintained LTS 14.15.1 - https://github.com/ansible/awx/pull/8766
|
|
||||||
- Added Git-LFS to the default image build - https://github.com/ansible/awx/pull/8700
|
|
||||||
- Added the ability to specify `metadata.labels` in the podspec for container groups - https://github.com/ansible/awx/issues/8486
|
|
||||||
- Added support for Kubernetes pod annotations - https://github.com/ansible/awx/pull/8434
|
|
||||||
- Added the ability to label the web container in local Docker installs - https://github.com/ansible/awx/pull/8449
|
|
||||||
- Added additional metadata (as an extra var) to playbook runs to report the SCM branch name - https://github.com/ansible/awx/pull/8433
|
|
||||||
- Fixed a bug that caused k8s installations to fail due to an incorrect Helm repo - https://github.com/ansible/awx/issues/8715
|
|
||||||
- Fixed a bug that prevented certain Workflow Approval resources from being deleted - https://github.com/ansible/awx/pull/8612
|
|
||||||
- Fixed a bug that prevented the deletion of inventories stuck in "pending deletion" state - https://github.com/ansible/awx/issues/8525
|
|
||||||
- Fixed a display bug in webhook notifications with certain unicode characters - https://github.com/ansible/awx/issues/7400
|
|
||||||
- Improved support for exporting dependent objects (Inventory Hosts and Groups) in the `awx export` CLI tool - https://github.com/ansible/awx/commit/607bc0788
|
|
||||||
|
|
||||||
## 15.0.1 (October 20, 2020)
|
|
||||||
- Added several optimizations to improve performance for a variety of high-load simultaneous job launch use cases https://github.com/ansible/awx/pull/8403
|
|
||||||
- Added the ability to source roles and collections from requirements.yaml files (not just requirements.yml) - https://github.com/ansible/awx/issues/4540
|
|
||||||
- awx.awx collection modules now provide a clearer error message for incompatible versions of awxkit - https://github.com/ansible/awx/issues/8127
|
|
||||||
- Fixed a bug in notification messages that contain certain unicode characters - https://github.com/ansible/awx/issues/7400
|
|
||||||
- Fixed a bug that prevents the deletion of Workflow Approval records - https://github.com/ansible/awx/issues/8305
|
|
||||||
- Fixed a bug that broke the selection of webhook credentials - https://github.com/ansible/awx/issues/7892
|
|
||||||
- Fixed a bug which can cause confusing behavior for social auth logins across distinct browser tabs - https://github.com/ansible/awx/issues/8154
|
|
||||||
- Fixed several bugs in the output of Workflow Job Templates using the `awx export` tool - https://github.com/ansible/awx/issues/7798 https://github.com/ansible/awx/pull/7847
|
|
||||||
- Fixed a race condition that can lead to missing hosts when running parallel inventory syncs - https://github.com/ansible/awx/issues/5571
|
|
||||||
- Fixed an HTTP 500 error when certain LDAP group parameters aren't properly set - https://github.com/ansible/awx/issues/7622
|
|
||||||
- Updated a few dependencies in response to several CVEs:
|
|
||||||
* CVE-2020-7720
|
|
||||||
* CVE-2020-7743
|
|
||||||
* CVE-2020-7676
|
|
||||||
|
|
||||||
## 15.0.0 (September 30, 2020)
|
|
||||||
- Added improved support for fetching Ansible collections from private Galaxy content sources (such as https://github.com/ansible/galaxy_ng) - https://github.com/ansible/awx/issues/7813
|
|
||||||
**Note:** as part of this change, new Organizations created in the AWX API will _no longer_ automatically synchronize roles and collections from galaxy.ansible.com by default. More details on this change can be found at: https://github.com/ansible/awx/issues/8341#issuecomment-707310633
|
|
||||||
- AWX now utilizes a version of certifi that auto-discovers certificates in the system certificate store - https://github.com/ansible/awx/pull/8242
|
|
||||||
- Added support for arbitrary custom inventory plugin configuration: https://github.com/ansible/awx/issues/5150
|
|
||||||
- Added an optional setting to disable the auto-creation of organizations and teams on successful SAML login. - https://github.com/ansible/awx/pull/8069
|
|
||||||
- Added a number of optimizations to AWX's callback receiver to improve the speed of stdout processing for simultaneous playbooks runs - https://github.com/ansible/awx/pull/8193 https://github.com/ansible/awx/pull/8191
|
|
||||||
- Added the ability to use `!include` and `!import` constructors when constructing YAML for use with the AWX CLI - https://github.com/ansible/awx/issues/8135
|
|
||||||
- Fixed a bug that prevented certain users from being able to edit approval nodes in Workflows - https://github.com/ansible/awx/pull/8253
|
|
||||||
- Fixed a bug that broke password prompting for credentials in certain cases - https://github.com/ansible/awx/issues/8202
|
|
||||||
- Fixed a bug which can cause PostgreSQL deadlocks when running many parallel playbooks against large shared inventories - https://github.com/ansible/awx/issues/8145
|
|
||||||
- Fixed a bug which can cause delays in AWX's task manager when large numbers of simultaneous jobs are scheduled - https://github.com/ansible/awx/issues/7655
|
|
||||||
- Fixed a bug which can cause certain scheduled jobs - those that run every X minute(s) or hour(s) - to fail to run at the proper time - https://github.com/ansible/awx/issues/8071
|
|
||||||
- Fixed a performance issue for playbooks that store large amounts of data using the `set_stats` module - https://github.com/ansible/awx/issues/8006
|
|
||||||
- Fixed a bug related to AWX's handling of the auth_path argument for the HashiVault KeyValue credential plugin - https://github.com/ansible/awx/pull/7991
|
|
||||||
- Fixed a bug that broke support for Remote Archive SCM Type project syncs on platforms that utilize Python2 - https://github.com/ansible/awx/pull/8057
|
|
||||||
- Updated to the latest version of Django Rest Framework to address CVE-2020-25626
|
|
||||||
- Updated to the latest version of Django to address CVE-2020-24583 and CVE-2020-24584
|
|
||||||
- Updated to the latest verson of channels_redis to address a bug that slowly causes Daphne processes to leak memory over time - https://github.com/django/channels_redis/issues/212
|
|
||||||
|
|
||||||
## 14.1.0 (Aug 25, 2020)
|
|
||||||
- AWX images can now be built on ARM64 - https://github.com/ansible/awx/pull/7607
|
|
||||||
- Added the Remote Archive SCM Type to support using immutable artifacts and releases (such as tarballs and zip files) as projects - https://github.com/ansible/awx/issues/7954
|
|
||||||
- Deprecated official support for Mercurial-based project updates - https://github.com/ansible/awx/issues/7932
|
|
||||||
- Added resource import/export support to the official AWX collection - https://github.com/ansible/awx/issues/7329
|
|
||||||
- Added the ability to import YAML-based resources (instead of just JSON) when using the AWX CLI - https://github.com/ansible/awx/pull/7808
|
|
||||||
- Users upgrading from older versions of AWX may encounter an issue that causes their postgres container to restart in a loop (https://github.com/ansible/awx/issues/7854) - if you encounter this, bring your containers down and then back up (e.g., `docker-compose down && docker-compose up -d`) after upgrading to 14.1.0.
|
|
||||||
- Updated the AWX CLI to export labels associated with Workflow Job Templates - https://github.com/ansible/awx/pull/7847
|
|
||||||
- Updated to the latest python-ldap to address a bug - https://github.com/ansible/awx/issues/7868
|
|
||||||
- Upgraded git-python to fix a bug that caused workflows to sometimes fail - https://github.com/ansible/awx/issues/6119
|
|
||||||
- Worked around a bug in the channels_redis library that slowly causes Daphne processes to leak memory over time - https://github.com/django/channels_redis/issues/212
|
|
||||||
- Fixed a bug in the AWX CLI that prevented Workflow nodes from importing properly - https://github.com/ansible/awx/issues/7793
|
|
||||||
- Fixed a bug in the awx.awx collection release process that templated the wrong version - https://github.com/ansible/awx/issues/7870
|
|
||||||
- Fixed a bug that caused errors rendering stdout that contained UTF-16 surrogate pairs - https://github.com/ansible/awx/pull/7918
|
|
||||||
|
|
||||||
## 14.0.0 (Aug 6, 2020)
|
|
||||||
- As part of our commitment to inclusivity in open source, we recently took some time to audit AWX's source code and user interface and replace certain terminology with more inclusive language. Strictly speaking, this isn't a bug or a feature, but we think it's important and worth calling attention to:
|
|
||||||
* https://github.com/ansible/awx/commit/78229f58715fbfbf88177e54031f532543b57acc
|
|
||||||
* https://www.redhat.com/en/blog/making-open-source-more-inclusive-eradicating-problematic-language
|
|
||||||
- Installing roles and collections via requirements.yml as part of Project Updates now requires at least Ansible 2.9 - https://github.com/ansible/awx/issues/7769
|
|
||||||
- Deprecated the use of the `PRIMARY_GALAXY_USERNAME` and `PRIMARY_GALAXY_PASSWORD` settings. We recommend using tokens to access Galaxy or Automation Hub.
|
|
||||||
- Added local caching for downloaded roles and collections so they are not re-downloaded on nodes where they are up to date with the project - https://github.com/ansible/awx/issues/5518
|
|
||||||
- Added the ability to associate K8S/OpenShift credentials to Job Template for playbook interaction with the `community.kubernetes` collection - https://github.com/ansible/awx/issues/5735
|
|
||||||
- Added the ability to include HTML in the Custom Login Info presented on the login page - https://github.com/ansible/awx/issues/7600
|
|
||||||
- Fixed https://access.redhat.com/security/cve/cve-2020-14327 - Server-side request forgery on credentials
|
|
||||||
- Fixed https://access.redhat.com/security/cve/cve-2020-14328 - Server-side request forgery on webhooks
|
|
||||||
- Fixed https://access.redhat.com/security/cve/cve-2020-14329 - Sensitive data exposure on labels
|
|
||||||
- Fixed https://access.redhat.com/security/cve/cve-2020-14337 - Named URLs allow for testing the presence or absence of objects
|
|
||||||
- Fixed a number of bugs in the user interface related to an upgrade of jQuery:
|
|
||||||
* https://github.com/ansible/awx/issues/7530
|
|
||||||
* https://github.com/ansible/awx/issues/7546
|
|
||||||
* https://github.com/ansible/awx/issues/7534
|
|
||||||
* https://github.com/ansible/awx/issues/7606
|
|
||||||
- Fixed a bug that caused the `-f yaml` flag of the AWX CLI to not print properly formatted YAML - https://github.com/ansible/awx/issues/7795
|
|
||||||
- Fixed a bug in the installer that caused errors when `docker_registry_password` was set - https://github.com/ansible/awx/issues/7695
|
|
||||||
- Fixed a permissions error that prevented certain users from starting AWX services - https://github.com/ansible/awx/issues/7545
|
|
||||||
- Fixed a bug that allows superusers to run unsafe Jinja code when defining custom Credential Types - https://github.com/ansible/awx/pull/7584/
|
|
||||||
- Fixed a bug that prevented users from creating (or editing) custom Credential Types containing boolean fields - https://github.com/ansible/awx/issues/7483
|
|
||||||
- Fixed a bug that prevented users with postgres usernames containing uppercase letters from restoring backups succesfully - https://github.com/ansible/awx/pull/7519
|
|
||||||
- Fixed a bug which allowed the creation (in the Tower API) of Groups and Hosts with the same name - https://github.com/ansible/awx/issues/4680
|
|
||||||
|
|
||||||
## 13.0.0 (Jun 23, 2020)
|
|
||||||
- Added import and export commands to the official AWX CLI, replacing send and receive from the old tower-cli (https://github.com/ansible/awx/pull/6125).
|
|
||||||
- Removed scripts as a means of running inventory updates of built-in types (https://github.com/ansible/awx/pull/6911)
|
|
||||||
- Ansible 2.8 is now partially unsupported; some inventory source types are known to no longer work.
|
|
||||||
- Fixed an issue where the vmware inventory source ssl_verify source variable was not recognized (https://github.com/ansible/awx/pull/7360)
|
|
||||||
- Fixed a bug that caused redis' listen socket to have too-permissive file permissions (https://github.com/ansible/awx/pull/7317)
|
|
||||||
- Fixed a bug that caused rsyslogd's configuration file to have world-readable file permissions, potentially leaking secrets (CVE-2020-10782)
|
|
||||||
|
|
||||||
## 12.0.0 (Jun 9, 2020)
|
|
||||||
- Removed memcached as a dependency of AWX (https://github.com/ansible/awx/pull/7240)
|
|
||||||
- Moved to a single container image build instead of separate awx_web and awx_task images. The container image is just `awx` (https://github.com/ansible/awx/pull/7228)
|
|
||||||
- Official AWX container image builds now use a two-stage container build process that notably reduces the size of our published images (https://github.com/ansible/awx/pull/7017)
|
|
||||||
- Removed support for HipChat notifications ([EoL announcement](https://www.atlassian.com/partnerships/slack/faq#faq-98b17ca3-247f-423b-9a78-70a91681eff0)); all previously-created HipChat notification templates will be deleted due to this removal.
|
|
||||||
- Fixed a bug which broke AWX installations with oc version 4.3 (https://github.com/ansible/awx/pull/6948/)
|
|
||||||
- Fixed a performance issue that caused notable delay of stdout processing for playbooks run against large numbers of hosts (https://github.com/ansible/awx/issues/6991)
|
|
||||||
- Fixed a bug that caused CyberArk AIM credential plugin looks to hang forever in some environments (https://github.com/ansible/awx/issues/6986)
|
|
||||||
- Fixed a bug that caused ANY/ALL converage settings not to properly save when editing approval nodes in the UI (https://github.com/ansible/awx/issues/6998)
|
|
||||||
- Fixed a bug that broke support for the satellite6_group_prefix source variable (https://github.com/ansible/awx/issues/7031)
|
|
||||||
- Fixed a bug that prevented changes to workflow node convergence settings when approval nodes were in use (https://github.com/ansible/awx/issues/7063)
|
|
||||||
- Fixed a bug that caused notifications to fail on newer version of Mattermost (https://github.com/ansible/awx/issues/7264)
|
|
||||||
- Fixed a bug (by upgrading to 0.8.1 of the foreman collection) that prevented host_filters from working properly with Foreman-based inventory (https://github.com/ansible/awx/issues/7225)
|
|
||||||
- Fixed a bug that prevented the usage of the Conjur credential plugin with secrets that contain spaces (https://github.com/ansible/awx/issues/7191)
|
|
||||||
- Fixed a bug in awx-manage run_wsbroadcast --status in kubernetes (https://github.com/ansible/awx/pull/7009)
|
|
||||||
- Fixed a bug that broke notification toggles for system jobs in the UI (https://github.com/ansible/awx/pull/7042)
|
|
||||||
- Fixed a bug that broke local pip installs of awxkit (https://github.com/ansible/awx/issues/7107)
|
|
||||||
- Fixed a bug that prevented PagerDuty notifications from sending for workflow job template approvals (https://github.com/ansible/awx/issues/7094)
|
|
||||||
- Fixed a bug that broke external log aggregation support for URL paths that include the = character (such as the tokens for SumoLogic) (https://github.com/ansible/awx/issues/7139)
|
|
||||||
- Fixed a bug that prevented organization admins from removing labels from workflow job templates (https://github.com/ansible/awx/pull/7143)
|
|
||||||
|
|
||||||
## 11.2.0 (Apr 29, 2020)
|
|
||||||
|
|
||||||
- Inventory updates now use collection-based plugins by default (in Ansible 2.9+):
|
|
||||||
- amazon.aws.aws_ec2
|
|
||||||
- community.vmware.vmware_vm_inventory
|
|
||||||
- azure.azcollection.azure_rm
|
|
||||||
- google.cloud.gcp_compute
|
|
||||||
- theforeman.foreman.foreman
|
|
||||||
- openstack.cloud.openstack
|
|
||||||
- ovirt.ovirt_collection.ovirt
|
|
||||||
- awx.awx.tower
|
|
||||||
- Added support for Approle and LDAP/AD mechanisms to the Hashicorp Vault credential plugin (https://github.com/ansible/awx/issues/5076)
|
|
||||||
- Added Project (Domain Name) support for the OpenStack Keystone v3 API (https://github.com/ansible/awx/issues/6831)
|
|
||||||
- Added a new setting for raising log verbosity for rsyslogd (https://github.com/ansible/awx/pull/6818)
|
|
||||||
- Added the ability to monitor stdout in the CLI for running jobs and workflow jobs (https://github.com/ansible/awx/issues/6165)
|
|
||||||
- Fixed a bug which prevented the AWX CLI from properly installing with newer versions of pip (https://github.com/ansible/awx/issues/6870)
|
|
||||||
- Fixed a bug which broke AWX's external logging support when configured with HTTPS endpoints that utilize self-signed certificates (https://github.com/ansible/awx/issues/6851)
|
|
||||||
- Fixed a local docker installer bug that mistakenly attempted to upgrade PostgreSQL when an external pg_hostname is specified (https://github.com/ansible/awx/pull/5398)
|
|
||||||
- Fixed a race condition that caused task container crashes when pods are quickly brought down and back up (https://github.com/ansible/awx/issues/6750)
|
|
||||||
- Fixed a bug that caused 404 errors when attempting to view the second page of the workflow approvals view (https://github.com/ansible/awx/issues/6803)
|
|
||||||
- Fixed a bug that prevented the use of ANSIBLE_SSH_ARGS for ad-hoc-commands (https://github.com/ansible/awx/pull/6811)
|
|
||||||
- Fixed a bug that broke AWX installs/upgrades on Red Hat OpenShift (https://github.com/ansible/awx/issues/6791)
|
|
||||||
|
|
||||||
|
|
||||||
## 11.1.0 (Apr 22, 2020)
|
|
||||||
- Changed rsyslogd to persist queued events to disk (to prevent a risk of out-of-memory errors) (https://github.com/ansible/awx/issues/6746)
|
|
||||||
- Added the ability to configure the destination and maximum disk size of rsyslogd spool (in the event of a log aggregator outage) (https://github.com/ansible/awx/pull/6763)
|
|
||||||
- Added the ability to discover playbooks in project clones from symlinked directories (https://github.com/ansible/awx/pull/6773)
|
|
||||||
- Fixed a bug that caused certain log aggregator settings to break logging integration (https://github.com/ansible/awx/issues/6760)
|
|
||||||
- Fixed a bug that caused playbook execution in container groups to sometimes unexpectedly deadlock (https://github.com/ansible/awx/issues/6692)
|
|
||||||
- Improved stability of the new redis clustering implementation (https://github.com/ansible/awx/pull/6739 https://github.com/ansible/awx/pull/6720)
|
|
||||||
- Improved stability of the new rsyslogd-based logging implementation (https://github.com/ansible/awx/pull/6796)
|
|
||||||
|
|
||||||
## 11.0.0 (Apr 16, 2020)
|
|
||||||
- As of AWX 11.0.0, Kubernetes-based deployments use a Deployment rather than a StatefulSet.
|
|
||||||
- Reimplemented external logging support using rsyslogd to improve reliability and address a number of issues (https://github.com/ansible/awx/issues/5155)
|
|
||||||
- Changed activity stream logs to include summary fields for related objects (https://github.com/ansible/awx/issues/1761)
|
|
||||||
- Added code to more gracefully attempt to reconnect to redis if it restarts/becomes unavailable (https://github.com/ansible/awx/pull/6670)
|
|
||||||
- Fixed a bug that caused REFRESH_TOKEN_EXPIRE_SECONDS to not properly be respected for OAuth2.0 refresh tokens generated by AWX (https://github.com/ansible/awx/issues/6630)
|
|
||||||
- Fixed a bug that broke schedules containing RRULES with very old DTSTART dates (https://github.com/ansible/awx/pull/6550)
|
|
||||||
- Fixed a bug that broke installs on older versions of Ansible packaged with certain Linux distributions (https://github.com/ansible/awx/issues/5501)
|
|
||||||
- Fixed a bug that caused the activity stream to sometimes report the incorrect actor when associating user membership on SAML login (https://github.com/ansible/awx/pull/6525)
|
|
||||||
- Fixed a bug in AWX's Grafana notification support when annotation tags are omitted (https://github.com/ansible/awx/issues/6580)
|
|
||||||
- Fixed a bug that prevented some users from searching for Source Control credentials in the AWX user interface (https://github.com/ansible/awx/issues/6600)
|
|
||||||
- Fixed a bug that prevented disassociating orphaned users from credentials (https://github.com/ansible/awx/pull/6554)
|
|
||||||
- Updated Twisted to address CVE-2020-10108 and CVE-2020-10109.
|
|
||||||
|
|
||||||
## 10.0.0 (Mar 30, 2020)
|
|
||||||
- As of AWX 10.0.0, the official AWX CLI no longer supports Python 2 (it requires at least Python 3.6) (https://github.com/ansible/awx/pull/6327)
|
|
||||||
- AWX no longer relies on RabbitMQ; Redis is added as a new dependency (https://github.com/ansible/awx/issues/5443)
|
|
||||||
- Altered AWX's event tables to allow more than ~2 billion total events (https://github.com/ansible/awx/issues/6010)
|
|
||||||
- Improved the performance (time to execute, and memory consumption) of the periodic job cleanup system job (https://github.com/ansible/awx/pull/6166)
|
|
||||||
- Updated Job Templates so they now have an explicit Organization field (it is no longer inferred from the associated Project) (https://github.com/ansible/awx/issues/3903)
|
|
||||||
- Updated social-auth-core to address an upcoming GitHub API deprecation (https://github.com/ansible/awx/issues/5970)
|
|
||||||
- Updated to ansible-runner 1.4.6 to address various bugs.
|
|
||||||
- Updated Django to address CVE-2020-9402
|
|
||||||
- Updated pyyaml version to address CVE-2017-18342
|
|
||||||
- Fixed a bug which prevented the new `scm_branch` field from being used in custom notification templates (https://github.com/ansible/awx/issues/6258)
|
|
||||||
- Fixed a race condition that sometimes causes success/failure notifications to include an incomplete list of hosts (https://github.com/ansible/awx/pull/6290)
|
|
||||||
- Fixed a bug that can cause certain setting pages to lose unsaved form edits when a playbook is launched (https://github.com/ansible/awx/issues/5265)
|
|
||||||
- Fixed a bug that can prevent the "Use TLS/SSL" field from properly saving when editing email notification templates (https://github.com/ansible/awx/issues/6383)
|
|
||||||
- Fixed a race condition that sometimes broke event/stdout processing for jobs launched in container groups (https://github.com/ansible/awx/issues/6280)
|
|
||||||
|
|
||||||
## 9.3.0 (Mar 12, 2020)
|
|
||||||
- Added the ability to specify an OAuth2 token description in the AWX CLI (https://github.com/ansible/awx/issues/6122)
|
|
||||||
- Added support for K8S service account annotations to the installer (https://github.com/ansible/awx/pull/6007)
|
|
||||||
- Added support for K8S imagePullSecrets to the installer (https://github.com/ansible/awx/pull/5989)
|
|
||||||
- Launching jobs (and workflows) using the --monitor flag in the AWX CLI now returns a non-zero exit code on job failure (https://github.com/ansible/awx/issues/5920)
|
|
||||||
- Improved UI performance for various job views when many simultaneous users are logged into AWX (https://github.com/ansible/awx/issues/5883)
|
|
||||||
- Updated to the latest version of Django to address a few open CVEs (https://github.com/ansible/awx/pull/6080)
|
|
||||||
- Fixed a critical bug which can cause AWX to hang and stop launching playbooks after a periodic of time (https://github.com/ansible/awx/issues/5617)
|
|
||||||
- Fixed a bug which caused delays in project update stdout for certain large SCM clones (as of Ansible 2.9+) (https://github.com/ansible/awx/pull/6254)
|
|
||||||
- Fixed a bug which caused certain smart inventory filters to mistakenly return duplicate hosts (https://github.com/ansible/awx/pull/5972)
|
|
||||||
- Fixed an unclear server error when creating smart inventories with the AWX collection (https://github.com/ansible/awx/issues/6250)
|
|
||||||
- Fixed a bug that broke Grafana notification support (https://github.com/ansible/awx/issues/6137)
|
|
||||||
- Fixed a UI bug which prevent users with read access to an organization from editing credentials for that organization (https://github.com/ansible/awx/pull/6241)
|
|
||||||
- Fixed a bug which prevent workflow approval records from recording a `started` and `elapsed` date (https://github.com/ansible/awx/issues/6202)
|
|
||||||
- Fixed a bug which caused workflow nodes to have a confusing option for `verbosity` (https://github.com/ansible/awx/issues/6196)
|
|
||||||
- Fixed an RBAC bug which prevented projects and inventory schedules from being created by certain users in certain contexts (https://github.com/ansible/awx/issues/5717)
|
|
||||||
- Fixed a bug that caused `role_path` in a project's config to not be respected due to an error processing `/etc/ansible/ansible.cfg` (https://github.com/ansible/awx/pull/6038)
|
|
||||||
- Fixed a bug that broke inventory updates for installs with custom home directories for the awx user (https://github.com/ansible/awx/pull/6152)
|
|
||||||
- Fixed a bug that broke fact data collection when AWX encounters invalid/unexpected fact data (https://github.com/ansible/awx/issues/5935)
|
|
||||||
|
|
||||||
|
|
||||||
## 9.2.0 (Feb 12, 2020)
|
|
||||||
- Added the ability to configure the convergence behavior of workflow nodes https://github.com/ansible/awx/issues/3054
|
|
||||||
- AWX now allows for a configurable global limit for fork count (per-job run). The default maximum is 200. https://github.com/ansible/awx/pull/5604
|
|
||||||
- Added the ability to specify AZURE_PUBLIC_CLOUD (for e.g., Azure Government KeyVault support) for the Azure credential plugin https://github.com/ansible/awx/issues/5138
|
|
||||||
- Added support for several additional parameters for Satellite dynamic inventory https://github.com/ansible/awx/pull/5598
|
|
||||||
- Added a new field to jobs for tracking the date/time a job is cancelled https://github.com/ansible/awx/pull/5610
|
|
||||||
- Made a series of additional optimizations to the callback receiver to further improve stdout write speed for running playbooks https://github.com/ansible/awx/pull/5677 https://github.com/ansible/awx/pull/5739
|
|
||||||
- Updated AWX to be compatible with Helm 3.x (https://github.com/ansible/awx/pull/5776)
|
|
||||||
- Optimized AWX's job dependency/scheduling code to drastically improve processing time in scenarios where there are many pending jobs scheduled simultaneously https://github.com/ansible/awx/issues/5154
|
|
||||||
- Fixed a bug which could cause SCM authentication details (basic auth passwords) to be reported to external loggers in certain failure scenarios (e.g., when a git clone fails and ansible itself prints an error message to stdout) https://github.com/ansible/awx/pull/5812
|
|
||||||
- Fixed a k8s installer bug that caused installs to fail in certain situations https://github.com/ansible/awx/issues/5574
|
|
||||||
- Fixed a number of issues that caused analytics gathering and reporting to run more often than necessary https://github.com/ansible/awx/pull/5721
|
|
||||||
- Fixed a bug in the AWX CLI that prevented JSON-type settings from saving properly https://github.com/ansible/awx/issues/5528
|
|
||||||
- Improved support for fetching custom virtualenv dependencies when AWX is installed behind a proxy https://github.com/ansible/awx/pull/5805
|
|
||||||
- Updated the bundled version of openstacksdk to address a known issue https://github.com/ansible/awx/issues/5821
|
|
||||||
- Updated the bundled vmware_inventory plugin to the latest version to address a bug https://github.com/ansible/awx/pull/5668
|
|
||||||
- Fixed a bug that can cause inventory updates to fail to properly save their output when run within a workflow https://github.com/ansible/awx/pull/5666
|
|
||||||
- Removed a number of pre-computed fields from the Host and Group models to improve AWX performance. As part of this change, inventory group UIs throughout the interface no longer display status icons https://github.com/ansible/awx/pull/5448
|
|
||||||
|
|
||||||
## 9.1.1 (Jan 14, 2020)
|
|
||||||
|
|
||||||
- Fixed a bug that caused database migrations on Kubernetes installs to hang https://github.com/ansible/awx/pull/5579
|
|
||||||
- Upgraded Python-level app dependencies in AWX virtual environment https://github.com/ansible/awx/pull/5407
|
|
||||||
- Running jobs no longer block associated inventory updates https://github.com/ansible/awx/pull/5519
|
|
||||||
- Fixed invalid_response SAML error https://github.com/ansible/awx/pull/5577
|
|
||||||
- Optimized the callback receiver to drastically improve the write speed of stdout for parallel jobs (https://github.com/ansible/awx/pull/5618)
|
|
||||||
|
|
||||||
## 9.1.0 (Dec 17, 2019)
|
|
||||||
- Added a command to generate a new SECRET_KEY and rekey the secrets in the database
|
|
||||||
- Removed project update locking when jobs using it are running
|
|
||||||
- Fixed slow queries for /api/v2/instances and /api/v2/instance_groups when smart inventories are used
|
|
||||||
- Fixed a partial password disclosure when special characters existed in the RabbitMQ password (CVE-2019-19342)
|
|
||||||
- Fixed hang in error handling for source control checkouts
|
|
||||||
- Fixed an error on subsequent job runs that override the branch of a project on an instance that did not have a prior project checkout
|
|
||||||
- Fixed an issue where jobs launched in isolated or container groups would incorrectly timeout
|
|
||||||
- Fixed an incorrect link to instance groups documentation in the user interface
|
|
||||||
- Fixed editing of inventory on Workflow templates
|
|
||||||
- Fixed multiple issues with OAuth2 token cleanup system jobs
|
|
||||||
- Fixed a bug that broke email notifications for workflow approval/deny https://github.com/ansible/awx/issues/5401
|
|
||||||
- Updated SAML implementation to automatically login if authorization already exists
|
|
||||||
- Updated AngularJS to 1.7.9 for CVE-2019-10768
|
|
||||||
|
|
||||||
## 9.0.1 (Nov 4, 2019)
|
|
||||||
|
|
||||||
- Fixed a bug in the installer that broke certain types of k8s installs https://github.com/ansible/awx/issues/5205
|
|
||||||
|
|
||||||
## 9.0.0 (Oct 31, 2019)
|
|
||||||
|
|
||||||
- Updated AWX images to use centos:8 as the parent image.
|
|
||||||
- Updated to ansible-runner 1.4.4 to address various bugs.
|
|
||||||
- Added oc and kubectl to the AWX images to support new container-based execution introduced in 8.0.0.
|
|
||||||
- Added some optimizations to speed up the deletion of large Inventory Groups.
|
|
||||||
- Fixed a bug that broke webhook launches for Job Templates that define a survey (https://github.com/ansible/awx/issues/5062).
|
|
||||||
- Fixed a bug in the CLI which incorrectly parsed launch time arguments for `awx job_templates launch` and `awx workflow_job_templates launch` (https://github.com/ansible/awx/issues/5093).
|
|
||||||
- Fixed a bug that caused inventory updates using "sourced from a project" to stop working (https://github.com/ansible/awx/issues/4750).
|
|
||||||
- Fixed a bug that caused Slack notifications to sometimes show the wrong bot avatar (https://github.com/ansible/awx/pull/5125).
|
|
||||||
- Fixed a bug that prevented the use of digits in AWX's URL settings (https://github.com/ansible/awx/issues/5081).
|
|
||||||
|
|
||||||
## 8.0.0 (Oct 21, 2019)
|
|
||||||
|
|
||||||
- The Ansible Tower Ansible modules have been migrated to a new official Ansible AWX collection: https://galaxy.ansible.com/awx/AWX
|
|
||||||
Please note that this functionality is only supported in Ansible 2.9+
|
|
||||||
- AWX now supports the ability to launch jobs from external webhooks (GitHub and GitLab integration are supported).
|
|
||||||
- AWX now supports Container Groups, a new feature that allows you to schedule and run playbooks on single-use kubernetes pods on-demand.
|
|
||||||
- AWX now supports sending notifications when Workflow steps are approved, denied, or time out.
|
|
||||||
- AWX now records the user who approved or denied Workflow steps.
|
|
||||||
- AWX now supports fetching Ansible Collections from private galaxy servers.
|
|
||||||
- AWX now checks the user's ansible.cfg for paths where role/collections may live when running project updates.
|
|
||||||
- AWX now uses PostgreSQL 10 by default.
|
|
||||||
- AWX now warns more loudly about underlying AMQP connectivity issues (https://github.com/ansible/awx/pull/4857).
|
|
||||||
- Added a few optimizations to drastically improve dashboard performance for larger AWX installs (installs with several hundred thousand jobs or more).
|
|
||||||
- Updated to the latest version of Ansible's VMWare inventory script (which adds support for vmware_guest_facts).
|
|
||||||
- Deprecated /api/v2/inventory_scripts/ (this endpoint - and the Custom Inventory Script feature - will be removed in a future release of AWX).
|
|
||||||
- Fixed a bug which prevented Organization Admins from removing users from their own Organization (https://github.com/ansible/awx/issues/2979)
|
|
||||||
- Fixed a bug which sometimes caused cluster nodes to fail to re-join with a cryptic error, "No instance found with the current cluster host id" (https://github.com/ansible/awx/issues/4294)
|
|
||||||
- Fixed a bug that prevented the use of launch-time passphrases when using credential plugins (https://github.com/ansible/awx/pull/4807)
|
|
||||||
- Fixed a bug that caused notifications assigned at the Organization level not to take effect for Workflows in that Organization (https://github.com/ansible/awx/issues/4712)
|
|
||||||
- Fixed a bug which caused a notable amount of CPU overhead on RabbitMQ health checks (https://github.com/ansible/awx/pull/5009)
|
|
||||||
- Fixed a bug which sometimes caused the <return> key to stop functioning in <textarea> elements (https://github.com/ansible/awx/issues/4192)
|
|
||||||
- Fixed a bug which caused request contention when the same OAuth2.0 token was used in multiple simultaneous requests (https://github.com/ansible/awx/issues/4694)
|
|
||||||
- Fixed a bug related to parsing multiple choice survey options (https://github.com/ansible/awx/issues/4452).
|
|
||||||
- Fixed a bug that caused single-sign-on icons on the login page to fail to render in certain Windows browsers (https://github.com/ansible/awx/issues/3924)
|
|
||||||
- Fixed a number of bugs that caused certain OAuth2 settings to not be properly respected, such as REFRESH_TOKEN_EXPIRE_SECONDS.
|
|
||||||
- Fixed a number of bugs in the AWX CLI, including a bug which sometimes caused long lines of stdout output to be unexpectedly truncated.
|
|
||||||
- Fixed a number of bugs on the job details UI which sometimes caused auto-scrolling stdout to become stuck.
|
|
||||||
- Fixed a bug which caused LDAP authentication to fail if the TLD of the server URL contained digits (https://github.com/ansible/awx/issues/3646)
|
|
||||||
- Fixed a bug which broke HashiCorp Vault integration on older versions of HashiCorp Vault.
|
|
||||||
|
|
||||||
## 7.0.0 (Sept 4, 2019)
|
|
||||||
|
|
||||||
- AWX now detects and installs Ansible Collections defined in your project (note - this feature only works in Ansible 2.9+) (https://github.com/ansible/awx/issues/2534)
|
|
||||||
- AWX now includes an official command line client. Keep an eye out for a follow-up email on this mailing list for information on how to install it and try it out.
|
|
||||||
- Added the ability to provide a specific SCM branch on jobs (https://github.com/ansible/awx/issues/282)
|
|
||||||
- Added support for Workflow Approval Nodes, a new feature which allows you to add "pause and wait for approval" steps into your workflows (https://github.com/ansible/awx/issues/1206)
|
|
||||||
- Added the ability to specify a specific HTTP method for webhook notifications (POST vs PUT) (https://github.com/ansible/awx/pull/4124)
|
|
||||||
- Added the ability to specify a username and password for HTTP Basic Authorization for webhook notifications (https://github.com/ansible/awx/pull/4124)
|
|
||||||
- Added support for customizing the text content of notifications (https://github.com/ansible/awx/issues/79)
|
|
||||||
- Added the ability to enable and disable hosts in dynamic inventory (https://github.com/ansible/awx/pull/4420)
|
|
||||||
- Added the description (if any) to the Job Template list (https://github.com/ansible/awx/issues/4359)
|
|
||||||
- Added new metrics for instance hostnames and pending jobs to the /api/v2/metrics/ endpoint (https://github.com/ansible/awx/pull/4375)
|
|
||||||
- Changed AWX's on/off toggle buttons to a non-text based style to simplify internationalization (https://github.com/ansible/awx/pull/4425)
|
|
||||||
- Events emitted by ansible for adhoc commands are now sent to the external log aggregrator (https://github.com/ansible/awx/issues/4545)
|
|
||||||
- Fixed a bug which allowed a user to make an organization credential in another organization without permissions to that organization (https://github.com/ansible/awx/pull/4483)
|
|
||||||
- Fixed a bug that caused `extra_vars` on workflows to break when edited (https://github.com/ansible/awx/issues/4293)
|
|
||||||
- Fixed a slow SQL query that caused performance issues when large numbers of groups exist (https://github.com/ansible/awx/issues/4461)
|
|
||||||
- Fixed a few minor bugs in survey field validation (https://github.com/ansible/awx/pull/4509) (https://github.com/ansible/awx/pull/4479)
|
|
||||||
- Fixed a bug that sometimes resulted in orphaned `ansible_runner_pi` directories in `/tmp` after playbook execution (https://github.com/ansible/awx/pull/4409)
|
|
||||||
- Fixed a bug that caused the `is_system_auditor` flag in LDAP configuration to not work (https://github.com/ansible/awx/pull/4396)
|
|
||||||
- Fixed a bug which caused schedules to disappear from the UI when toggled off (https://github.com/ansible/awx/pull/4378)
|
|
||||||
- Fixed a bug that sometimes caused stdout content to contain extraneous blank lines in newer versions of Ansible (https://github.com/ansible/awx/pull/4391)
|
|
||||||
- Updated to the latest Django security release, 2.2.4 (https://github.com/ansible/awx/pull/4410) (https://www.djangoproject.com/weblog/2019/aug/01/security-releases/)
|
|
||||||
- Updated the default version of git to a version that includes support for x509 certificates (https://github.com/ansible/awx/issues/4362)
|
|
||||||
- Removed the deprecated `credential` field from `/api/v2/workflow_job_templates/N/` (as part of the `/api/v1/` removal in prior AWX versions - https://github.com/ansible/awx/pull/4490).
|
|
||||||
|
|
||||||
## 6.1.0 (Jul 18, 2019)
|
|
||||||
|
|
||||||
- Updated AWX to use Django 2.2.2.
|
|
||||||
- Updated the provided openstacksdk version to support new functionality (such as Nova scheduler_hints)
|
|
||||||
- Added the ability to specify a custom cacert for the HashiCorp Vault credential plugin
|
|
||||||
- Fixed a number of bugs related to path lookups for the HashiCorp Vault credential plugin
|
|
||||||
- Fixed a bug which prevented signed SSH certificates from working, including the HashiCorp Vault Signed SSH backend
|
|
||||||
- Fixed a bug which prevented custom logos from displaying on the login page (as a result of a new Content Security Policy in 6.0.0)
|
|
||||||
- Fixed a bug which broke websocket connectivity in Apple Safari (as a result of a new Content Security Policy in 6.0.0)
|
|
||||||
- Fixed a bug on the job output page that occasionally caused the "up" and "down" buttons to not load additional output
|
|
||||||
- Fixed a bug on the job output page that caused quoted task names to display incorrectly
|
|
||||||
|
|
||||||
## 6.0.0 (Jul 1, 2019)
|
|
||||||
|
|
||||||
- Removed support for "Any" notification templates and their API endpoints e.g., /api/v2/job_templates/N/notification_templates/any/ (https://github.com/ansible/awx/issues/4022)
|
|
||||||
- Fixed a bug which prevented credentials from properly being applied to inventory sources (https://github.com/ansible/awx/issues/4059)
|
|
||||||
- Fixed a bug which can cause the task dispatcher to hang indefinitely when external logging support (e.g., Splunk, Logstash) is enabled (https://github.com/ansible/awx/issues/4181)
|
|
||||||
- Fixed a bug which causes slow stdout display when running jobs against smart inventories. (https://github.com/ansible/awx/issues/3106)
|
|
||||||
- Fixed a bug that caused SSL verification flags to fail to be respected for LDAP authentication in certain environments. (https://github.com/ansible/awx/pull/4190)
|
|
||||||
- Added a simple Content Security Policy (https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to restrict access to third-party resources in the browser. (https://github.com/ansible/awx/pull/4167)
|
|
||||||
- Updated ovirt4 library dependencies to work with newer versions of oVirt (https://github.com/ansible/awx/issues/4138)
|
|
||||||
|
|
||||||
## 5.0.0 (Jun 21, 2019)
|
|
||||||
|
|
||||||
- Bump Django Rest Framework from 3.7.7 to 3.9.4
|
|
||||||
- Bump setuptools / pip dependencies
|
|
||||||
- Fixed bug where Recent Notification list would not appear
|
|
||||||
- Added notifications on job start
|
|
||||||
- Default to Ansible 2.8
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Have questions about this document or anything not covered here? Come chat with
|
|||||||
- [Building API Documentation](#building-api-documentation)
|
- [Building API Documentation](#building-api-documentation)
|
||||||
- [Accessing the AWX web interface](#accessing-the-awx-web-interface)
|
- [Accessing the AWX web interface](#accessing-the-awx-web-interface)
|
||||||
- [Purging containers and images](#purging-containers-and-images)
|
- [Purging containers and images](#purging-containers-and-images)
|
||||||
|
- [Pre commit hooks](#pre-commit-hooks)
|
||||||
- [What should I work on?](#what-should-i-work-on)
|
- [What should I work on?](#what-should-i-work-on)
|
||||||
- [Submitting Pull Requests](#submitting-pull-requests)
|
- [Submitting Pull Requests](#submitting-pull-requests)
|
||||||
- [PR Checks run by Zuul](#pr-checks-run-by-zuul)
|
- [PR Checks run by Zuul](#pr-checks-run-by-zuul)
|
||||||
@@ -104,13 +105,21 @@ When necessary, remove any AWX containers and images by running the following:
|
|||||||
(host)$ make docker-clean
|
(host)$ make docker-clean
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Pre commit hooks
|
||||||
|
|
||||||
|
When you attempt to perform a `git commit` there will be a pre-commit hook that gets run before the commit is allowed to your local repository. For example, python's [black](https://pypi.org/project/black/) will be run to test the formatting of any python files.
|
||||||
|
|
||||||
|
While you can use environment variables to skip the pre-commit hooks GitHub will run similar tests and prevent merging of PRs if the tests do not pass.
|
||||||
|
|
||||||
|
If you would like to add additional commit hooks for your own usage you can create a directory in the root of the repository called `pre-commit-user`. Any executable file in that directory will be executed as part of the pre-commit hooks. If any of the pre-commit checks fail the commit will be halted. For your convenience in user scripts, a variable called `CHANGED_FILES` will be set with any changed files present in the commit.
|
||||||
|
|
||||||
## What should I work on?
|
## What should I work on?
|
||||||
|
|
||||||
For feature work, take a look at the current [Enhancements](https://github.com/ansible/awx/issues?q=is%3Aissue+is%3Aopen+label%3Atype%3Aenhancement).
|
For feature work, take a look at the current [Enhancements](https://github.com/ansible/awx/issues?q=is%3Aissue+is%3Aopen+label%3Atype%3Aenhancement).
|
||||||
|
|
||||||
If it has someone assigned to it then that person is the person responsible for working the enhancement. If you feel like you could contribute then reach out to that person.
|
If it has someone assigned to it then that person is the person responsible for working the enhancement. If you feel like you could contribute then reach out to that person.
|
||||||
|
|
||||||
Fixing bugs, adding translations, and updating the documentation are always appreciated, so reviewing the backlog of issues is always a good place to start. For extra information on debugging tools, see [Debugging](https://github.com/ansible/awx/blob/devel/docs/debugging.md).
|
Fixing bugs, adding translations, and updating the documentation are always appreciated, so reviewing the backlog of issues is always a good place to start. For extra information on debugging tools, see [Debugging](./docs/debugging/).
|
||||||
|
|
||||||
**NOTE**
|
**NOTE**
|
||||||
|
|
||||||
|
|||||||
208
Makefile
208
Makefile
@@ -1,70 +1,57 @@
|
|||||||
PYTHON ?= python3.8
|
PYTHON ?= python3.9
|
||||||
PYTHON_VERSION = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_version; print(get_python_version())")
|
|
||||||
SITELIB=$(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
|
|
||||||
OFFICIAL ?= no
|
OFFICIAL ?= no
|
||||||
PACKER ?= packer
|
|
||||||
PACKER_BUILD_OPTS ?= -var 'official=$(OFFICIAL)' -var 'aw_repo_url=$(AW_REPO_URL)'
|
|
||||||
NODE ?= node
|
NODE ?= node
|
||||||
NPM_BIN ?= npm
|
NPM_BIN ?= npm
|
||||||
CHROMIUM_BIN=/tmp/chrome-linux/chrome
|
CHROMIUM_BIN=/tmp/chrome-linux/chrome
|
||||||
DEPS_SCRIPT ?= packaging/bundle/deps.py
|
|
||||||
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
MANAGEMENT_COMMAND ?= awx-manage
|
MANAGEMENT_COMMAND ?= awx-manage
|
||||||
IMAGE_REPOSITORY_AUTH ?=
|
VERSION := $(shell $(PYTHON) tools/scripts/scm_version.py)
|
||||||
IMAGE_REPOSITORY_BASE ?= https://gcr.io
|
COLLECTION_VERSION := $(shell $(PYTHON) tools/scripts/scm_version.py | cut -d . -f 1-3)
|
||||||
VERSION := $(shell cat VERSION)
|
|
||||||
|
|
||||||
# NOTE: This defaults the container image version to the branch that's active
|
# NOTE: This defaults the container image version to the branch that's active
|
||||||
COMPOSE_TAG ?= $(GIT_BRANCH)
|
COMPOSE_TAG ?= $(GIT_BRANCH)
|
||||||
COMPOSE_HOST ?= $(shell hostname)
|
MAIN_NODE_TYPE ?= hybrid
|
||||||
|
# If set to true docker-compose will also start a keycloak instance
|
||||||
|
KEYCLOAK ?= false
|
||||||
|
# If set to true docker-compose will also start an ldap instance
|
||||||
|
LDAP ?= false
|
||||||
|
# If set to true docker-compose will also start a splunk instance
|
||||||
|
SPLUNK ?= false
|
||||||
|
# If set to true docker-compose will also start a prometheus instance
|
||||||
|
PROMETHEUS ?= false
|
||||||
|
# If set to true docker-compose will also start a grafana instance
|
||||||
|
GRAFANA ?= false
|
||||||
|
|
||||||
VENV_BASE ?= /var/lib/awx/venv/
|
VENV_BASE ?= /var/lib/awx/venv
|
||||||
SCL_PREFIX ?=
|
|
||||||
CELERY_SCHEDULE_FILE ?= /var/lib/awx/beat.db
|
|
||||||
|
|
||||||
DEV_DOCKER_TAG_BASE ?= quay.io/awx
|
DEV_DOCKER_TAG_BASE ?= ghcr.io/ansible
|
||||||
DEVEL_IMAGE_NAME ?= $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG)
|
DEVEL_IMAGE_NAME ?= $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG)
|
||||||
|
|
||||||
|
RECEPTOR_IMAGE ?= quay.io/ansible/receptor:devel
|
||||||
|
|
||||||
# Python packages to install only from source (not from binary wheels)
|
# Python packages to install only from source (not from binary wheels)
|
||||||
# Comma separated list
|
# Comma separated list
|
||||||
SRC_ONLY_PKGS ?= cffi,pycparser,psycopg2,twilio
|
SRC_ONLY_PKGS ?= cffi,pycparser,psycopg2,twilio
|
||||||
# These should be upgraded in the AWX and Ansible venv before attempting
|
# These should be upgraded in the AWX and Ansible venv before attempting
|
||||||
# to install the actual requirements
|
# to install the actual requirements
|
||||||
VENV_BOOTSTRAP ?= pip==19.3.1 setuptools==41.6.0 wheel==0.36.2
|
VENV_BOOTSTRAP ?= pip==21.2.4 setuptools==58.2.0 setuptools_scm[toml]==6.4.2 wheel==0.36.2
|
||||||
|
|
||||||
# Determine appropriate shasum command
|
|
||||||
UNAME_S := $(shell uname -s)
|
|
||||||
ifeq ($(UNAME_S),Linux)
|
|
||||||
SHASUM_BIN ?= sha256sum
|
|
||||||
endif
|
|
||||||
ifeq ($(UNAME_S),Darwin)
|
|
||||||
SHASUM_BIN ?= shasum -a 256
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Get the branch information from git
|
|
||||||
GIT_DATE := $(shell git log -n 1 --format="%ai")
|
|
||||||
DATE := $(shell date -u +%Y%m%d%H%M)
|
|
||||||
|
|
||||||
NAME ?= awx
|
NAME ?= awx
|
||||||
GIT_REMOTE_URL = $(shell git config --get remote.origin.url)
|
|
||||||
|
|
||||||
# TAR build parameters
|
# TAR build parameters
|
||||||
SDIST_TAR_NAME=$(NAME)-$(VERSION)
|
SDIST_TAR_NAME=$(NAME)-$(VERSION)
|
||||||
WHEEL_NAME=$(NAME)-$(VERSION)
|
|
||||||
|
|
||||||
SDIST_COMMAND ?= sdist
|
SDIST_COMMAND ?= sdist
|
||||||
WHEEL_COMMAND ?= bdist_wheel
|
|
||||||
SDIST_TAR_FILE ?= $(SDIST_TAR_NAME).tar.gz
|
SDIST_TAR_FILE ?= $(SDIST_TAR_NAME).tar.gz
|
||||||
WHEEL_FILE ?= $(WHEEL_NAME)-py2-none-any.whl
|
|
||||||
|
|
||||||
I18N_FLAG_FILE = .i18n_built
|
I18N_FLAG_FILE = .i18n_built
|
||||||
|
|
||||||
.PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \
|
.PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \
|
||||||
develop refresh adduser migrate dbchange \
|
develop refresh adduser migrate dbchange \
|
||||||
receiver test test_unit test_coverage coverage_html \
|
receiver test test_unit test_coverage coverage_html \
|
||||||
dev_build release_build sdist \
|
sdist \
|
||||||
ui-release ui-devel \
|
ui-release ui-devel \
|
||||||
VERSION docker-compose-sources \
|
VERSION PYTHON_VERSION docker-compose-sources \
|
||||||
.git/hooks/pre-commit
|
.git/hooks/pre-commit
|
||||||
|
|
||||||
clean-tmp:
|
clean-tmp:
|
||||||
@@ -166,15 +153,6 @@ version_file:
|
|||||||
fi; \
|
fi; \
|
||||||
$(PYTHON) -c "import awx; print(awx.__version__)" > /var/lib/awx/.awx_version; \
|
$(PYTHON) -c "import awx; print(awx.__version__)" > /var/lib/awx/.awx_version; \
|
||||||
|
|
||||||
# Do any one-time init tasks.
|
|
||||||
comma := ,
|
|
||||||
init:
|
|
||||||
if [ "$(VENV_BASE)" ]; then \
|
|
||||||
. $(VENV_BASE)/awx/bin/activate; \
|
|
||||||
fi; \
|
|
||||||
$(MANAGEMENT_COMMAND) provision_instance --hostname=$(COMPOSE_HOST); \
|
|
||||||
$(MANAGEMENT_COMMAND) register_queue --queuename=controlplane --instance_percent=100;
|
|
||||||
|
|
||||||
# Refresh development environment after pulling new code.
|
# Refresh development environment after pulling new code.
|
||||||
refresh: clean requirements_dev version_file develop migrate
|
refresh: clean requirements_dev version_file develop migrate
|
||||||
|
|
||||||
@@ -205,7 +183,7 @@ collectstatic:
|
|||||||
fi; \
|
fi; \
|
||||||
mkdir -p awx/public/static && $(PYTHON) manage.py collectstatic --clear --noinput > /dev/null 2>&1
|
mkdir -p awx/public/static && $(PYTHON) manage.py collectstatic --clear --noinput > /dev/null 2>&1
|
||||||
|
|
||||||
UWSGI_DEV_RELOAD_COMMAND ?= supervisorctl restart tower-processes:awx-dispatcher tower-processes:awx-receiver
|
DEV_RELOAD_COMMAND ?= supervisorctl restart tower-processes:*
|
||||||
|
|
||||||
uwsgi: collectstatic
|
uwsgi: collectstatic
|
||||||
@if [ "$(VENV_BASE)" ]; then \
|
@if [ "$(VENV_BASE)" ]; then \
|
||||||
@@ -220,12 +198,13 @@ uwsgi: collectstatic
|
|||||||
--processes=5 \
|
--processes=5 \
|
||||||
--harakiri=120 --master \
|
--harakiri=120 --master \
|
||||||
--no-orphans \
|
--no-orphans \
|
||||||
--py-autoreload 1 \
|
|
||||||
--max-requests=1000 \
|
--max-requests=1000 \
|
||||||
--stats /tmp/stats.socket \
|
--stats /tmp/stats.socket \
|
||||||
--lazy-apps \
|
--lazy-apps \
|
||||||
--logformat "%(addr) %(method) %(uri) - %(proto) %(status)" \
|
--logformat "%(addr) %(method) %(uri) - %(proto) %(status)"
|
||||||
--hook-accepting1="exec: $(UWSGI_DEV_RELOAD_COMMAND)"
|
|
||||||
|
awx-autoreload:
|
||||||
|
@/awx_devel/tools/docker-compose/awx-autoreload /awx_devel/awx "$(DEV_RELOAD_COMMAND)"
|
||||||
|
|
||||||
daphne:
|
daphne:
|
||||||
@if [ "$(VENV_BASE)" ]; then \
|
@if [ "$(VENV_BASE)" ]; then \
|
||||||
@@ -294,18 +273,17 @@ api-lint:
|
|||||||
yamllint -s .
|
yamllint -s .
|
||||||
|
|
||||||
awx-link:
|
awx-link:
|
||||||
[ -d "/awx_devel/awx.egg-info" ] || $(PYTHON) /awx_devel/setup.py egg_info_dev
|
[ -d "/awx_devel/awx.egg-info" ] || $(PYTHON) /awx_devel/tools/scripts/egg_info_dev
|
||||||
cp -f /tmp/awx.egg-link /var/lib/awx/venv/awx/lib/python$(PYTHON_VERSION)/site-packages/awx.egg-link
|
cp -f /tmp/awx.egg-link /var/lib/awx/venv/awx/lib/$(PYTHON)/site-packages/awx.egg-link
|
||||||
|
|
||||||
TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests
|
TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests
|
||||||
|
PYTEST_ARGS ?= -n auto
|
||||||
# Run all API unit tests.
|
# Run all API unit tests.
|
||||||
test:
|
test:
|
||||||
if [ "$(VENV_BASE)" ]; then \
|
if [ "$(VENV_BASE)" ]; then \
|
||||||
. $(VENV_BASE)/awx/bin/activate; \
|
. $(VENV_BASE)/awx/bin/activate; \
|
||||||
fi; \
|
fi; \
|
||||||
PYTHONDONTWRITEBYTECODE=1 py.test -p no:cacheprovider -n auto $(TEST_DIRS)
|
PYTHONDONTWRITEBYTECODE=1 py.test -p no:cacheprovider $(PYTEST_ARGS) $(TEST_DIRS)
|
||||||
cmp VERSION awxkit/VERSION || "VERSION and awxkit/VERSION *must* match"
|
|
||||||
cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3
|
cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3
|
||||||
awx-manage check_migrations --dry-run --check -n 'missing_migration_file'
|
awx-manage check_migrations --dry-run --check -n 'missing_migration_file'
|
||||||
|
|
||||||
@@ -314,6 +292,7 @@ COLLECTION_TEST_TARGET ?=
|
|||||||
COLLECTION_PACKAGE ?= awx
|
COLLECTION_PACKAGE ?= awx
|
||||||
COLLECTION_NAMESPACE ?= awx
|
COLLECTION_NAMESPACE ?= awx
|
||||||
COLLECTION_INSTALL = ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE)
|
COLLECTION_INSTALL = ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE)
|
||||||
|
COLLECTION_TEMPLATE_VERSION ?= false
|
||||||
|
|
||||||
test_collection:
|
test_collection:
|
||||||
rm -f $(shell ls -d $(VENV_BASE)/awx/lib/python* | head -n 1)/no-global-site-packages.txt
|
rm -f $(shell ls -d $(VENV_BASE)/awx/lib/python* | head -n 1)/no-global-site-packages.txt
|
||||||
@@ -336,13 +315,19 @@ symlink_collection:
|
|||||||
mkdir -p ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE) # in case it does not exist
|
mkdir -p ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE) # in case it does not exist
|
||||||
ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL)
|
ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL)
|
||||||
|
|
||||||
build_collection:
|
awx_collection_build: $(shell find awx_collection -type f)
|
||||||
ansible-playbook -i localhost, awx_collection/tools/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION) -e '{"awx_template_version":false}'
|
ansible-playbook -i localhost, awx_collection/tools/template_galaxy.yml \
|
||||||
|
-e collection_package=$(COLLECTION_PACKAGE) \
|
||||||
|
-e collection_namespace=$(COLLECTION_NAMESPACE) \
|
||||||
|
-e collection_version=$(COLLECTION_VERSION) \
|
||||||
|
-e '{"awx_template_version": $(COLLECTION_TEMPLATE_VERSION)}'
|
||||||
ansible-galaxy collection build awx_collection_build --force --output-path=awx_collection_build
|
ansible-galaxy collection build awx_collection_build --force --output-path=awx_collection_build
|
||||||
|
|
||||||
|
build_collection: awx_collection_build
|
||||||
|
|
||||||
install_collection: build_collection
|
install_collection: build_collection
|
||||||
rm -rf $(COLLECTION_INSTALL)
|
rm -rf $(COLLECTION_INSTALL)
|
||||||
ansible-galaxy collection install awx_collection_build/$(COLLECTION_NAMESPACE)-$(COLLECTION_PACKAGE)-$(VERSION).tar.gz
|
ansible-galaxy collection install awx_collection_build/$(COLLECTION_NAMESPACE)-$(COLLECTION_PACKAGE)-$(COLLECTION_VERSION).tar.gz
|
||||||
|
|
||||||
test_collection_sanity: install_collection
|
test_collection_sanity: install_collection
|
||||||
cd $(COLLECTION_INSTALL) && ansible-test sanity
|
cd $(COLLECTION_INSTALL) && ansible-test sanity
|
||||||
@@ -393,9 +378,9 @@ clean-ui:
|
|||||||
rm -rf $(UI_BUILD_FLAG_FILE)
|
rm -rf $(UI_BUILD_FLAG_FILE)
|
||||||
|
|
||||||
awx/ui/node_modules:
|
awx/ui/node_modules:
|
||||||
NODE_OPTIONS=--max-old-space-size=4096 $(NPM_BIN) --prefix awx/ui --loglevel warn ci
|
NODE_OPTIONS=--max-old-space-size=6144 $(NPM_BIN) --prefix awx/ui --loglevel warn ci
|
||||||
|
|
||||||
$(UI_BUILD_FLAG_FILE):
|
$(UI_BUILD_FLAG_FILE): awx/ui/node_modules
|
||||||
$(PYTHON) tools/scripts/compilemessages.py
|
$(PYTHON) tools/scripts/compilemessages.py
|
||||||
$(NPM_BIN) --prefix awx/ui --loglevel warn run compile-strings
|
$(NPM_BIN) --prefix awx/ui --loglevel warn run compile-strings
|
||||||
$(NPM_BIN) --prefix awx/ui --loglevel warn run build
|
$(NPM_BIN) --prefix awx/ui --loglevel warn run build
|
||||||
@@ -407,7 +392,9 @@ $(UI_BUILD_FLAG_FILE):
|
|||||||
cp -r awx/ui/build/static/media/* awx/public/static/media
|
cp -r awx/ui/build/static/media/* awx/public/static/media
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
ui-release: awx/ui/node_modules $(UI_BUILD_FLAG_FILE)
|
|
||||||
|
|
||||||
|
ui-release: $(UI_BUILD_FLAG_FILE)
|
||||||
|
|
||||||
ui-devel: awx/ui/node_modules
|
ui-devel: awx/ui/node_modules
|
||||||
@$(MAKE) -B $(UI_BUILD_FLAG_FILE)
|
@$(MAKE) -B $(UI_BUILD_FLAG_FILE)
|
||||||
@@ -425,44 +412,34 @@ ui-lint:
|
|||||||
|
|
||||||
ui-test:
|
ui-test:
|
||||||
$(NPM_BIN) --prefix awx/ui install
|
$(NPM_BIN) --prefix awx/ui install
|
||||||
$(NPM_BIN) run --prefix awx/ui test -- --coverage --maxWorkers=4 --watchAll=false
|
$(NPM_BIN) run --prefix awx/ui test
|
||||||
|
|
||||||
|
ui-test-screens:
|
||||||
|
$(NPM_BIN) --prefix awx/ui install
|
||||||
|
$(NPM_BIN) run --prefix awx/ui pretest
|
||||||
|
$(NPM_BIN) run --prefix awx/ui test-screens --runInBand
|
||||||
|
|
||||||
# Build a pip-installable package into dist/ with a timestamped version number.
|
ui-test-general:
|
||||||
dev_build:
|
$(NPM_BIN) --prefix awx/ui install
|
||||||
$(PYTHON) setup.py dev_build
|
$(NPM_BIN) run --prefix awx/ui pretest
|
||||||
|
$(NPM_BIN) run --prefix awx/ui/ test-general --runInBand
|
||||||
|
|
||||||
# Build a pip-installable package into dist/ with the release version number.
|
HEADLESS ?= no
|
||||||
release_build:
|
ifeq ($(HEADLESS), yes)
|
||||||
$(PYTHON) setup.py release_build
|
dist/$(SDIST_TAR_FILE):
|
||||||
|
else
|
||||||
dist/$(SDIST_TAR_FILE): ui-release VERSION
|
dist/$(SDIST_TAR_FILE): $(UI_BUILD_FLAG_FILE)
|
||||||
$(PYTHON) setup.py $(SDIST_COMMAND)
|
endif
|
||||||
|
$(PYTHON) -m build -s
|
||||||
dist/$(WHEEL_FILE): ui-release
|
ln -sf $(SDIST_TAR_FILE) dist/awx.tar.gz
|
||||||
$(PYTHON) setup.py $(WHEEL_COMMAND)
|
|
||||||
|
|
||||||
sdist: dist/$(SDIST_TAR_FILE)
|
sdist: dist/$(SDIST_TAR_FILE)
|
||||||
|
echo $(HEADLESS)
|
||||||
@echo "#############################################"
|
@echo "#############################################"
|
||||||
@echo "Artifacts:"
|
@echo "Artifacts:"
|
||||||
@echo dist/$(SDIST_TAR_FILE)
|
@echo dist/$(SDIST_TAR_FILE)
|
||||||
@echo "#############################################"
|
@echo "#############################################"
|
||||||
|
|
||||||
wheel: dist/$(WHEEL_FILE)
|
|
||||||
@echo "#############################################"
|
|
||||||
@echo "Artifacts:"
|
|
||||||
@echo dist/$(WHEEL_FILE)
|
|
||||||
@echo "#############################################"
|
|
||||||
|
|
||||||
# Build setup bundle tarball
|
|
||||||
setup-bundle-build:
|
|
||||||
mkdir -p $@
|
|
||||||
|
|
||||||
docker-auth:
|
|
||||||
@if [ "$(IMAGE_REPOSITORY_AUTH)" ]; then \
|
|
||||||
echo "$(IMAGE_REPOSITORY_AUTH)" | docker login -u oauth2accesstoken --password-stdin $(IMAGE_REPOSITORY_BASE); \
|
|
||||||
fi;
|
|
||||||
|
|
||||||
# This directory is bind-mounted inside of the development container and
|
# This directory is bind-mounted inside of the development container and
|
||||||
# needs to be pre-created for permissions to be set correctly. Otherwise,
|
# needs to be pre-created for permissions to be set correctly. Otherwise,
|
||||||
# Docker will create this directory as root.
|
# Docker will create this directory as root.
|
||||||
@@ -470,7 +447,9 @@ awx/projects:
|
|||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
|
|
||||||
COMPOSE_UP_OPTS ?=
|
COMPOSE_UP_OPTS ?=
|
||||||
CLUSTER_NODE_COUNT ?= 1
|
COMPOSE_OPTS ?=
|
||||||
|
CONTROL_PLANE_NODE_COUNT ?= 1
|
||||||
|
EXECUTION_NODE_COUNT ?= 2
|
||||||
MINIKUBE_CONTAINER_GROUP ?= false
|
MINIKUBE_CONTAINER_GROUP ?= false
|
||||||
|
|
||||||
docker-compose-sources: .git/hooks/pre-commit
|
docker-compose-sources: .git/hooks/pre-commit
|
||||||
@@ -481,18 +460,25 @@ docker-compose-sources: .git/hooks/pre-commit
|
|||||||
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/sources.yml \
|
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/sources.yml \
|
||||||
-e awx_image=$(DEV_DOCKER_TAG_BASE)/awx_devel \
|
-e awx_image=$(DEV_DOCKER_TAG_BASE)/awx_devel \
|
||||||
-e awx_image_tag=$(COMPOSE_TAG) \
|
-e awx_image_tag=$(COMPOSE_TAG) \
|
||||||
-e cluster_node_count=$(CLUSTER_NODE_COUNT) \
|
-e receptor_image=$(RECEPTOR_IMAGE) \
|
||||||
-e minikube_container_group=$(MINIKUBE_CONTAINER_GROUP)
|
-e control_plane_node_count=$(CONTROL_PLANE_NODE_COUNT) \
|
||||||
|
-e execution_node_count=$(EXECUTION_NODE_COUNT) \
|
||||||
|
-e minikube_container_group=$(MINIKUBE_CONTAINER_GROUP) \
|
||||||
|
-e enable_keycloak=$(KEYCLOAK) \
|
||||||
|
-e enable_ldap=$(LDAP) \
|
||||||
|
-e enable_splunk=$(SPLUNK) \
|
||||||
|
-e enable_prometheus=$(PROMETHEUS) \
|
||||||
|
-e enable_grafana=$(GRAFANA)
|
||||||
|
|
||||||
|
|
||||||
docker-compose: docker-auth awx/projects docker-compose-sources
|
docker-compose: awx/projects docker-compose-sources
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml up $(COMPOSE_UP_OPTS)
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_OPTS) up $(COMPOSE_UP_OPTS) --remove-orphans
|
||||||
|
|
||||||
docker-compose-credential-plugins: docker-auth awx/projects docker-compose-sources
|
docker-compose-credential-plugins: awx/projects docker-compose-sources
|
||||||
echo -e "\033[0;31mTo generate a CyberArk Conjur API key: docker exec -it tools_conjur_1 conjurctl account create quick-start\033[0m"
|
echo -e "\033[0;31mTo generate a CyberArk Conjur API key: docker exec -it tools_conjur_1 conjurctl account create quick-start\033[0m"
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/docker-credential-plugins-override.yml up --no-recreate awx_1
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/docker-credential-plugins-override.yml up --no-recreate awx_1 --remove-orphans
|
||||||
|
|
||||||
docker-compose-test: docker-auth awx/projects docker-compose-sources
|
docker-compose-test: awx/projects docker-compose-sources
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx_1 /bin/bash
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports awx_1 /bin/bash
|
||||||
|
|
||||||
docker-compose-runtest: awx/projects docker-compose-sources
|
docker-compose-runtest: awx/projects docker-compose-sources
|
||||||
@@ -501,8 +487,9 @@ docker-compose-runtest: awx/projects docker-compose-sources
|
|||||||
docker-compose-build-swagger: awx/projects docker-compose-sources
|
docker-compose-build-swagger: awx/projects docker-compose-sources
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports --no-deps awx_1 /start_tests.sh swagger
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml run --rm --service-ports --no-deps awx_1 /start_tests.sh swagger
|
||||||
|
|
||||||
|
SCHEMA_DIFF_BASE_BRANCH ?= devel
|
||||||
detect-schema-change: genschema
|
detect-schema-change: genschema
|
||||||
curl https://s3.amazonaws.com/awx-public-ci-files/schema.json -o reference-schema.json
|
curl https://s3.amazonaws.com/awx-public-ci-files/$(SCHEMA_DIFF_BASE_BRANCH)/schema.json -o reference-schema.json
|
||||||
# Ignore differences in whitespace with -b
|
# Ignore differences in whitespace with -b
|
||||||
diff -u -b reference-schema.json schema.json
|
diff -u -b reference-schema.json schema.json
|
||||||
|
|
||||||
@@ -517,30 +504,29 @@ docker-compose-container-group-clean:
|
|||||||
|
|
||||||
# Base development image build
|
# Base development image build
|
||||||
docker-compose-build:
|
docker-compose-build:
|
||||||
ansible-playbook tools/ansible/dockerfile.yml -e build_dev=True
|
ansible-playbook tools/ansible/dockerfile.yml -e build_dev=True -e receptor_image=$(RECEPTOR_IMAGE)
|
||||||
DOCKER_BUILDKIT=1 docker build -t $(DEVEL_IMAGE_NAME) \
|
DOCKER_BUILDKIT=1 docker build -t $(DEVEL_IMAGE_NAME) \
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) .
|
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) .
|
||||||
|
|
||||||
docker-clean:
|
docker-clean:
|
||||||
$(foreach container_id,$(shell docker ps -f name=tools_awx -aq),docker stop $(container_id); docker rm -f $(container_id);)
|
$(foreach container_id,$(shell docker ps -f name=tools_awx -aq && docker ps -f name=tools_receptor -aq),docker stop $(container_id); docker rm -f $(container_id);)
|
||||||
docker images | grep "awx_devel" | awk '{print $$1 ":" $$2}' | xargs docker rmi
|
if [ "$(shell docker images | grep awx_devel)" ]; then \
|
||||||
|
docker images | grep awx_devel | awk '{print $$3}' | xargs docker rmi --force; \
|
||||||
|
fi
|
||||||
|
|
||||||
docker-clean-volumes: docker-compose-clean docker-compose-container-group-clean
|
docker-clean-volumes: docker-compose-clean docker-compose-container-group-clean
|
||||||
docker volume rm tools_awx_db
|
docker volume rm -f tools_awx_db tools_grafana_storage tools_prometheus_storage $(docker volume ls --filter name=tools_redis_socket_ -q)
|
||||||
|
|
||||||
docker-refresh: docker-clean docker-compose
|
docker-refresh: docker-clean docker-compose
|
||||||
|
|
||||||
# Docker Development Environment with Elastic Stack Connected
|
# Docker Development Environment with Elastic Stack Connected
|
||||||
docker-compose-elk: docker-auth awx/projects docker-compose-sources
|
docker-compose-elk: awx/projects docker-compose-sources
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/elastic/docker-compose.logstash-link.yml -f tools/elastic/docker-compose.elastic-override.yml up --no-recreate
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/elastic/docker-compose.logstash-link.yml -f tools/elastic/docker-compose.elastic-override.yml up --no-recreate
|
||||||
|
|
||||||
docker-compose-cluster-elk: docker-auth awx/projects docker-compose-sources
|
docker-compose-cluster-elk: awx/projects docker-compose-sources
|
||||||
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/elastic/docker-compose.logstash-link-cluster.yml -f tools/elastic/docker-compose.elastic-override.yml up --no-recreate
|
docker-compose -f tools/docker-compose/_sources/docker-compose.yml -f tools/elastic/docker-compose.logstash-link-cluster.yml -f tools/elastic/docker-compose.elastic-override.yml up --no-recreate
|
||||||
|
|
||||||
prometheus:
|
|
||||||
docker run -u0 --net=tools_default --link=`docker ps | egrep -o "tools_awx(_run)?_([^ ]+)?"`:awxweb --volume `pwd`/tools/prometheus:/prometheus --name prometheus -d -p 0.0.0.0:9090:9090 prom/prometheus --web.enable-lifecycle --config.file=/prometheus/prometheus.yml
|
|
||||||
|
|
||||||
docker-compose-container-group:
|
docker-compose-container-group:
|
||||||
MINIKUBE_CONTAINER_GROUP=true make docker-compose
|
MINIKUBE_CONTAINER_GROUP=true make docker-compose
|
||||||
|
|
||||||
@@ -558,18 +544,23 @@ psql-container:
|
|||||||
VERSION:
|
VERSION:
|
||||||
@echo "awx: $(VERSION)"
|
@echo "awx: $(VERSION)"
|
||||||
|
|
||||||
|
PYTHON_VERSION:
|
||||||
|
@echo "$(PYTHON)" | sed 's:python::'
|
||||||
|
|
||||||
Dockerfile: tools/ansible/roles/dockerfile/templates/Dockerfile.j2
|
Dockerfile: tools/ansible/roles/dockerfile/templates/Dockerfile.j2
|
||||||
ansible-playbook tools/ansible/dockerfile.yml
|
ansible-playbook tools/ansible/dockerfile.yml -e receptor_image=$(RECEPTOR_IMAGE)
|
||||||
|
|
||||||
Dockerfile.kube-dev: tools/ansible/roles/dockerfile/templates/Dockerfile.j2
|
Dockerfile.kube-dev: tools/ansible/roles/dockerfile/templates/Dockerfile.j2
|
||||||
ansible-playbook tools/ansible/dockerfile.yml \
|
ansible-playbook tools/ansible/dockerfile.yml \
|
||||||
-e dockerfile_name=Dockerfile.kube-dev \
|
-e dockerfile_name=Dockerfile.kube-dev \
|
||||||
-e kube_dev=True \
|
-e kube_dev=True \
|
||||||
-e template_dest=_build_kube_dev
|
-e template_dest=_build_kube_dev \
|
||||||
|
-e receptor_image=$(RECEPTOR_IMAGE)
|
||||||
|
|
||||||
awx-kube-dev-build: Dockerfile.kube-dev
|
awx-kube-dev-build: Dockerfile.kube-dev
|
||||||
docker build -f Dockerfile.kube-dev \
|
DOCKER_BUILDKIT=1 docker build -f Dockerfile.kube-dev \
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
|
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) \
|
||||||
-t $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) .
|
-t $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) .
|
||||||
|
|
||||||
|
|
||||||
@@ -591,3 +582,6 @@ messages:
|
|||||||
. $(VENV_BASE)/awx/bin/activate; \
|
. $(VENV_BASE)/awx/bin/activate; \
|
||||||
fi; \
|
fi; \
|
||||||
$(PYTHON) manage.py makemessages -l $(LANG) --keep-pot
|
$(PYTHON) manage.py makemessages -l $(LANG) --keep-pot
|
||||||
|
|
||||||
|
print-%:
|
||||||
|
@echo $($*)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://github.com/ansible/awx/actions/workflows/ci.yml) [](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) [](https://github.com/ansible/awx/blob/devel/LICENSE.md) [](https://groups.google.com/g/awx-project)
|
[](https://github.com/ansible/awx/actions/workflows/ci.yml) [](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) [](https://github.com/ansible/awx/blob/devel/LICENSE.md) [](https://groups.google.com/g/awx-project)
|
||||||
[](https://libera.chat)
|
[](https://libera.chat)
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/ansible/awx-logos/master/awx/ui/client/assets/logo-login.svg?sanitize=true" width=200 alt="AWX" />
|
<img src="https://raw.githubusercontent.com/ansible/awx-logos/master/awx/ui/client/assets/logo-login.svg?sanitize=true" width=200 alt="AWX" />
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
---
|
|
||||||
name: "\U0001F525 Security bug report"
|
|
||||||
about: How to report security vulnerabilities
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
For all security related bugs, email security@ansible.com instead of using this issue tracker and you will receive a prompt response.
|
For all security related bugs, email security@ansible.com instead of using this issue tracker and you will receive a prompt response.
|
||||||
|
|
||||||
For more information on the Ansible community's practices regarding responsible disclosure, see https://www.ansible.com/security
|
For more information on the Ansible community's practices regarding responsible disclosure, see https://www.ansible.com/security
|
||||||
@@ -6,9 +6,40 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from pkg_resources import get_distribution
|
|
||||||
|
|
||||||
__version__ = get_distribution('awx').version
|
def get_version():
|
||||||
|
version_from_file = get_version_from_file()
|
||||||
|
if version_from_file:
|
||||||
|
return version_from_file
|
||||||
|
else:
|
||||||
|
from setuptools_scm import get_version
|
||||||
|
|
||||||
|
version = get_version(root='..', relative_to=__file__)
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def get_version_from_file():
|
||||||
|
vf = version_file()
|
||||||
|
if vf:
|
||||||
|
with open(vf, 'r') as file:
|
||||||
|
return file.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def version_file():
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
version_file = os.path.join(current_dir, '..', 'VERSION')
|
||||||
|
|
||||||
|
if os.path.exists(version_file):
|
||||||
|
return version_file
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
__version__ = pkg_resources.get_distribution('awx').version
|
||||||
|
except pkg_resources.DistributionNotFound:
|
||||||
|
__version__ = get_version()
|
||||||
|
|
||||||
__all__ = ['__version__']
|
__all__ = ['__version__']
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +52,6 @@ try:
|
|||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
MODE = 'production'
|
MODE = 'production'
|
||||||
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -36,7 +66,6 @@ else:
|
|||||||
from django.db.backends.utils import names_digest
|
from django.db.backends.utils import names_digest
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
if HAS_DJANGO is True:
|
if HAS_DJANGO is True:
|
||||||
|
|
||||||
# See upgrade blocker note in requirements/README.md
|
# See upgrade blocker note in requirements/README.md
|
||||||
@@ -79,9 +108,10 @@ def oauth2_getattribute(self, attr):
|
|||||||
# Custom method to override
|
# Custom method to override
|
||||||
# oauth2_provider.settings.OAuth2ProviderSettings.__getattribute__
|
# oauth2_provider.settings.OAuth2ProviderSettings.__getattribute__
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from oauth2_provider.settings import DEFAULTS
|
||||||
|
|
||||||
val = None
|
val = None
|
||||||
if 'migrate' not in sys.argv:
|
if (isinstance(attr, str)) and (attr in DEFAULTS) and (not attr.startswith('_')):
|
||||||
# certain Django OAuth Toolkit migrations actually reference
|
# certain Django OAuth Toolkit migrations actually reference
|
||||||
# setting lookups for references to model classes (e.g.,
|
# setting lookups for references to model classes (e.g.,
|
||||||
# oauth2_settings.REFRESH_TOKEN_MODEL)
|
# oauth2_settings.REFRESH_TOKEN_MODEL)
|
||||||
@@ -151,7 +181,7 @@ def manage():
|
|||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
# enforce the postgres version is equal to 12. if not, then terminate program with exit code of 1
|
# enforce the postgres version is equal to 12. if not, then terminate program with exit code of 1
|
||||||
if not MODE == 'development':
|
if not os.getenv('SKIP_PG_VERSION_CHECK', False) and not MODE == 'development':
|
||||||
if (connection.pg_version // 10000) < 12:
|
if (connection.pg_version // 10000) < 12:
|
||||||
sys.stderr.write("Postgres version 12 is required\n")
|
sys.stderr.write("Postgres version 12 is required\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logging
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
@@ -24,7 +24,7 @@ class LoggedBasicAuthentication(authentication.BasicAuthentication):
|
|||||||
ret = super(LoggedBasicAuthentication, self).authenticate(request)
|
ret = super(LoggedBasicAuthentication, self).authenticate(request)
|
||||||
if ret:
|
if ret:
|
||||||
username = ret[0].username if ret[0] else '<none>'
|
username = ret[0].username if ret[0] else '<none>'
|
||||||
logger.info(smart_text(u"User {} performed a {} to {} through the API".format(username, request.method, request.path)))
|
logger.info(smart_str(u"User {} performed a {} to {} through the API".format(username, request.method, request.path)))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def authenticate_header(self, request):
|
def authenticate_header(self, request):
|
||||||
@@ -45,7 +45,7 @@ class LoggedOAuth2Authentication(OAuth2Authentication):
|
|||||||
user, token = ret
|
user, token = ret
|
||||||
username = user.username if user else '<none>'
|
username = user.username if user else '<none>'
|
||||||
logger.info(
|
logger.info(
|
||||||
smart_text(u"User {} performed a {} to {} through the API using OAuth 2 token {}.".format(username, request.method, request.path, token.pk))
|
smart_str(u"User {} performed a {} to {} through the API using OAuth 2 token {}.".format(username, request.method, request.path, token.pk))
|
||||||
)
|
)
|
||||||
setattr(user, 'oauth_scopes', [x for x in token.scope.split() if x])
|
setattr(user, 'oauth_scopes', [x for x in token.scope.split() if x])
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
@@ -13,7 +13,7 @@ class ActiveJobConflict(ValidationError):
|
|||||||
|
|
||||||
def __init__(self, active_jobs):
|
def __init__(self, active_jobs):
|
||||||
# During APIException.__init__(), Django Rest Framework
|
# During APIException.__init__(), Django Rest Framework
|
||||||
# turn everything in self.detail into string by using force_text.
|
# turn everything in self.detail into string by using force_str.
|
||||||
# Declare detail afterwards circumvent this behavior.
|
# Declare detail afterwards circumvent this behavior.
|
||||||
super(ActiveJobConflict, self).__init__()
|
super(ActiveJobConflict, self).__init__()
|
||||||
self.detail = {"error": _("Resource is being used by running jobs."), "active_jobs": active_jobs}
|
self.detail = {"error": _("Resource is being used by running jobs."), "active_jobs": active_jobs}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
@@ -28,13 +28,17 @@ class NullFieldMixin(object):
|
|||||||
return (is_empty_value, data)
|
return (is_empty_value, data)
|
||||||
|
|
||||||
|
|
||||||
class BooleanNullField(NullFieldMixin, serializers.NullBooleanField):
|
class BooleanNullField(NullFieldMixin, serializers.BooleanField):
|
||||||
"""
|
"""
|
||||||
Custom boolean field that allows null and empty string as False values.
|
Custom boolean field that allows null and empty string as False values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs['allow_null'] = True
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
return bool(super(BooleanNullField, self).to_internal_value(data))
|
return bool(super().to_internal_value(data))
|
||||||
|
|
||||||
|
|
||||||
class CharNullField(NullFieldMixin, serializers.CharField):
|
class CharNullField(NullFieldMixin, serializers.CharField):
|
||||||
@@ -47,7 +51,7 @@ class CharNullField(NullFieldMixin, serializers.CharField):
|
|||||||
super(CharNullField, self).__init__(**kwargs)
|
super(CharNullField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
return super(CharNullField, self).to_internal_value(data or u'')
|
return super(CharNullField, self).to_internal_value(data or '')
|
||||||
|
|
||||||
|
|
||||||
class ChoiceNullField(NullFieldMixin, serializers.ChoiceField):
|
class ChoiceNullField(NullFieldMixin, serializers.ChoiceField):
|
||||||
@@ -60,7 +64,7 @@ class ChoiceNullField(NullFieldMixin, serializers.ChoiceField):
|
|||||||
super(ChoiceNullField, self).__init__(**kwargs)
|
super(ChoiceNullField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
return super(ChoiceNullField, self).to_internal_value(data or u'')
|
return super(ChoiceNullField, self).to_internal_value(data or '')
|
||||||
|
|
||||||
|
|
||||||
class VerbatimField(serializers.Field):
|
class VerbatimField(serializers.Field):
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import json
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError, FieldDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, CharField, IntegerField, BooleanField
|
from django.db.models import Q, CharField, IntegerField, BooleanField, TextField, JSONField
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
|
||||||
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField, ForeignKey
|
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField, ForeignKey
|
||||||
|
from django.db.models.functions import Cast
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||||
@@ -185,16 +185,14 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
return (field_list[-1], new_lookup)
|
return (field_list[-1], new_lookup)
|
||||||
|
|
||||||
def to_python_related(self, value):
|
def to_python_related(self, value):
|
||||||
value = force_text(value)
|
value = force_str(value)
|
||||||
if value.lower() in ('none', 'null'):
|
if value.lower() in ('none', 'null'):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
def value_to_python_for_field(self, field, value):
|
def value_to_python_for_field(self, field, value):
|
||||||
if isinstance(field, models.NullBooleanField):
|
if isinstance(field, models.BooleanField):
|
||||||
return to_python_boolean(value, allow_none=True)
|
|
||||||
elif isinstance(field, models.BooleanField):
|
|
||||||
return to_python_boolean(value)
|
return to_python_boolean(value)
|
||||||
elif isinstance(field, (ForeignObjectRel, ManyToManyField, GenericForeignKey, ForeignKey)):
|
elif isinstance(field, (ForeignObjectRel, ManyToManyField, GenericForeignKey, ForeignKey)):
|
||||||
try:
|
try:
|
||||||
@@ -244,6 +242,8 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
new_lookups.append('{}__{}__icontains'.format(new_lookup[:-8], rm_field.name))
|
new_lookups.append('{}__{}__icontains'.format(new_lookup[:-8], rm_field.name))
|
||||||
return value, new_lookups, needs_distinct
|
return value, new_lookups, needs_distinct
|
||||||
else:
|
else:
|
||||||
|
if isinstance(field, JSONField):
|
||||||
|
new_lookup = new_lookup.replace(field.name, f'{field.name}_as_txt')
|
||||||
value = self.value_to_python_for_field(field, value)
|
value = self.value_to_python_for_field(field, value)
|
||||||
return value, new_lookup, needs_distinct
|
return value, new_lookup, needs_distinct
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
search_filter_relation = 'AND'
|
search_filter_relation = 'AND'
|
||||||
values = reduce(lambda list1, list2: list1 + list2, [i.split(',') for i in values])
|
values = reduce(lambda list1, list2: list1 + list2, [i.split(',') for i in values])
|
||||||
for value in values:
|
for value in values:
|
||||||
search_value, new_keys, _ = self.value_to_python(queryset.model, key, force_text(value))
|
search_value, new_keys, _ = self.value_to_python(queryset.model, key, force_str(value))
|
||||||
assert isinstance(new_keys, list)
|
assert isinstance(new_keys, list)
|
||||||
search_filters[search_value] = new_keys
|
search_filters[search_value] = new_keys
|
||||||
# by definition, search *only* joins across relations,
|
# by definition, search *only* joins across relations,
|
||||||
@@ -325,6 +325,9 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
value, new_key, distinct = self.value_to_python(queryset.model, key, value)
|
value, new_key, distinct = self.value_to_python(queryset.model, key, value)
|
||||||
if distinct:
|
if distinct:
|
||||||
needs_distinct = True
|
needs_distinct = True
|
||||||
|
if '_as_txt' in new_key:
|
||||||
|
fname = next(item for item in new_key.split('__') if item.endswith('_as_txt'))
|
||||||
|
queryset = queryset.annotate(**{fname: Cast(fname[:-7], output_field=TextField())})
|
||||||
if q_chain:
|
if q_chain:
|
||||||
chain_filters.append((q_not, new_key, value))
|
chain_filters.append((q_not, new_key, value))
|
||||||
elif q_or:
|
elif q_or:
|
||||||
@@ -395,11 +398,11 @@ class OrderByBackend(BaseFilterBackend):
|
|||||||
order_by = value.split(',')
|
order_by = value.split(',')
|
||||||
else:
|
else:
|
||||||
order_by = (value,)
|
order_by = (value,)
|
||||||
if order_by is None:
|
default_order_by = self.get_default_ordering(view)
|
||||||
order_by = self.get_default_ordering(view)
|
# glue the order by and default order by together so that the default is the backup option
|
||||||
|
order_by = list(order_by or []) + list(default_order_by or [])
|
||||||
if order_by:
|
if order_by:
|
||||||
order_by = self._validate_ordering_fields(queryset.model, order_by)
|
order_by = self._validate_ordering_fields(queryset.model, order_by)
|
||||||
|
|
||||||
# Special handling of the type field for ordering. In this
|
# Special handling of the type field for ordering. In this
|
||||||
# case, we're not sorting exactly on the type field, but
|
# case, we're not sorting exactly on the type field, but
|
||||||
# given the limited number of views with multiple types,
|
# given the limited number of views with multiple types,
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ import urllib.parse
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
|
||||||
from django.db.models.fields.related import OneToOneRel
|
from django.db.models.fields.related import OneToOneRel
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_str
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.contrib.auth import views as auth_views
|
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ParseError, NotAcceptable, UnsupportedMediaType
|
from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ParseError, NotAcceptable, UnsupportedMediaType
|
||||||
@@ -44,6 +44,7 @@ from awx.main.views import ApiErrorView
|
|||||||
from awx.api.serializers import ResourceAccessListElementSerializer, CopySerializer, UserSerializer
|
from awx.api.serializers import ResourceAccessListElementSerializer, CopySerializer, UserSerializer
|
||||||
from awx.api.versioning import URLPathVersioning
|
from awx.api.versioning import URLPathVersioning
|
||||||
from awx.api.metadata import SublistAttachDetatchMetadata, Metadata
|
from awx.api.metadata import SublistAttachDetatchMetadata, Metadata
|
||||||
|
from awx.conf import settings_registry
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'APIView',
|
'APIView',
|
||||||
@@ -92,17 +93,18 @@ class LoggedLoginView(auth_views.LoginView):
|
|||||||
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
||||||
current_user = getattr(request, 'user', None)
|
current_user = getattr(request, 'user', None)
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
logger.info(smart_text(u"User {} logged in from {}".format(self.request.user.username, request.META.get('REMOTE_ADDR', None))))
|
logger.info(smart_str(u"User {} logged in from {}".format(self.request.user.username, request.META.get('REMOTE_ADDR', None))))
|
||||||
ret.set_cookie('userLoggedIn', 'true')
|
ret.set_cookie('userLoggedIn', 'true')
|
||||||
current_user = UserSerializer(self.request.user)
|
current_user = UserSerializer(self.request.user)
|
||||||
current_user = smart_text(JSONRenderer().render(current_user.data))
|
current_user = smart_str(JSONRenderer().render(current_user.data))
|
||||||
current_user = urllib.parse.quote('%s' % current_user, '')
|
current_user = urllib.parse.quote('%s' % current_user, '')
|
||||||
ret.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None)
|
ret.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None)
|
||||||
|
ret.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
if 'username' in self.request.POST:
|
if 'username' in self.request.POST:
|
||||||
logger.warn(smart_text(u"Login failed for user {} from {}".format(self.request.POST.get('username'), request.META.get('REMOTE_ADDR', None))))
|
logger.warning(smart_str(u"Login failed for user {} from {}".format(self.request.POST.get('username'), request.META.get('REMOTE_ADDR', None))))
|
||||||
ret.status_code = 401
|
ret.status_code = 401
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -208,12 +210,27 @@ class APIView(views.APIView):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
if response.status_code >= 400:
|
if response.status_code >= 400:
|
||||||
status_msg = "status %s received by user %s attempting to access %s from %s" % (
|
msg_data = {
|
||||||
response.status_code,
|
'status_code': response.status_code,
|
||||||
request.user,
|
'user_name': request.user,
|
||||||
request.path,
|
'url_path': request.path,
|
||||||
request.META.get('REMOTE_ADDR', None),
|
'remote_addr': request.META.get('REMOTE_ADDR', None),
|
||||||
)
|
}
|
||||||
|
|
||||||
|
if type(response.data) is dict:
|
||||||
|
msg_data['error'] = response.data.get('error', response.status_text)
|
||||||
|
elif type(response.data) is list:
|
||||||
|
msg_data['error'] = ", ".join(list(map(lambda x: x.get('error', response.status_text), response.data)))
|
||||||
|
else:
|
||||||
|
msg_data['error'] = response.status_text
|
||||||
|
|
||||||
|
try:
|
||||||
|
status_msg = getattr(settings, 'API_400_ERROR_LOG_FORMAT').format(**msg_data)
|
||||||
|
except Exception as e:
|
||||||
|
if getattr(settings, 'API_400_ERROR_LOG_FORMAT', None):
|
||||||
|
logger.error("Unable to format API_400_ERROR_LOG_FORMAT setting, defaulting log message: {}".format(e))
|
||||||
|
status_msg = settings_registry.get_setting_field('API_400_ERROR_LOG_FORMAT').get_default().format(**msg_data)
|
||||||
|
|
||||||
if hasattr(self, '__init_request_error__'):
|
if hasattr(self, '__init_request_error__'):
|
||||||
response = self.handle_exception(self.__init_request_error__)
|
response = self.handle_exception(self.__init_request_error__)
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
@@ -221,6 +238,7 @@ class APIView(views.APIView):
|
|||||||
logger.info(status_msg)
|
logger.info(status_msg)
|
||||||
else:
|
else:
|
||||||
logger.warning(status_msg)
|
logger.warning(status_msg)
|
||||||
|
|
||||||
response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
|
response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
|
||||||
time_started = getattr(self, 'time_started', None)
|
time_started = getattr(self, 'time_started', None)
|
||||||
response['X-API-Product-Version'] = get_awx_version()
|
response['X-API-Product-Version'] = get_awx_version()
|
||||||
@@ -374,8 +392,8 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
|||||||
if hasattr(self.model._meta, "verbose_name"):
|
if hasattr(self.model._meta, "verbose_name"):
|
||||||
d.update(
|
d.update(
|
||||||
{
|
{
|
||||||
'model_verbose_name': smart_text(self.model._meta.verbose_name),
|
'model_verbose_name': smart_str(self.model._meta.verbose_name),
|
||||||
'model_verbose_name_plural': smart_text(self.model._meta.verbose_name_plural),
|
'model_verbose_name_plural': smart_str(self.model._meta.verbose_name_plural),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
serializer = self.get_serializer()
|
serializer = self.get_serializer()
|
||||||
@@ -506,8 +524,8 @@ class SubListAPIView(ParentMixin, ListAPIView):
|
|||||||
d = super(SubListAPIView, self).get_description_context()
|
d = super(SubListAPIView, self).get_description_context()
|
||||||
d.update(
|
d.update(
|
||||||
{
|
{
|
||||||
'parent_model_verbose_name': smart_text(self.parent_model._meta.verbose_name),
|
'parent_model_verbose_name': smart_str(self.parent_model._meta.verbose_name),
|
||||||
'parent_model_verbose_name_plural': smart_text(self.parent_model._meta.verbose_name_plural),
|
'parent_model_verbose_name_plural': smart_str(self.parent_model._meta.verbose_name_plural),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return d
|
return d
|
||||||
@@ -620,6 +638,11 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
# attaching/detaching them from the parent.
|
# attaching/detaching them from the parent.
|
||||||
|
|
||||||
def is_valid_relation(self, parent, sub, created=False):
|
def is_valid_relation(self, parent, sub, created=False):
|
||||||
|
"Override in subclasses to do efficient validation of attaching"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_valid_removal(self, parent, sub):
|
||||||
|
"Same as is_valid_relation but called on disassociation"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_description_context(self):
|
def get_description_context(self):
|
||||||
@@ -704,6 +727,11 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
if not request.user.can_access(self.parent_model, 'unattach', parent, sub, self.relationship, request.data):
|
if not request.user.can_access(self.parent_model, 'unattach', parent, sub, self.relationship, request.data):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
# Verify that removing the relationship is valid.
|
||||||
|
unattach_errors = self.is_valid_removal(parent, sub)
|
||||||
|
if unattach_errors is not None:
|
||||||
|
return Response(unattach_errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if parent_key:
|
if parent_key:
|
||||||
sub.delete()
|
sub.delete()
|
||||||
else:
|
else:
|
||||||
@@ -817,7 +845,7 @@ class ResourceAccessList(ParentMixin, ListAPIView):
|
|||||||
|
|
||||||
|
|
||||||
def trigger_delayed_deep_copy(*args, **kwargs):
|
def trigger_delayed_deep_copy(*args, **kwargs):
|
||||||
from awx.main.tasks import deep_copy_model_obj
|
from awx.main.tasks.system import deep_copy_model_obj
|
||||||
|
|
||||||
connection.on_commit(lambda: deep_copy_model_obj.delay(*args, **kwargs))
|
connection.on_commit(lambda: deep_copy_model_obj.delay(*args, **kwargs))
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ from uuid import UUID
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db.models import JSONField
|
||||||
from django.db.models.fields import PositiveIntegerField, BooleanField
|
from django.db.models.fields import PositiveIntegerField, BooleanField
|
||||||
from django.db.models.fields.related import ForeignKey
|
from django.db.models.fields.related import ForeignKey
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.encoding import force_text, smart_text
|
from django.utils.encoding import force_str, smart_str
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
@@ -22,7 +23,7 @@ from rest_framework.request import clone_request
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.fields import ChoiceNullField
|
from awx.api.fields import ChoiceNullField
|
||||||
from awx.main.fields import JSONField, ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.models import NotificationTemplate
|
from awx.main.models import NotificationTemplate
|
||||||
from awx.main.utils.execution_environments import get_default_pod_spec
|
from awx.main.utils.execution_environments import get_default_pod_spec
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
for attr in text_attrs:
|
for attr in text_attrs:
|
||||||
value = getattr(field, attr, None)
|
value = getattr(field, attr, None)
|
||||||
if value is not None and value != '':
|
if value is not None and value != '':
|
||||||
field_info[attr] = force_text(value, strings_only=True)
|
field_info[attr] = force_str(value, strings_only=True)
|
||||||
|
|
||||||
placeholder = getattr(field, 'placeholder', serializers.empty)
|
placeholder = getattr(field, 'placeholder', serializers.empty)
|
||||||
if placeholder is not serializers.empty:
|
if placeholder is not serializers.empty:
|
||||||
@@ -77,7 +78,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
}
|
}
|
||||||
if field.field_name in field_help_text:
|
if field.field_name in field_help_text:
|
||||||
opts = serializer.Meta.model._meta.concrete_model._meta
|
opts = serializer.Meta.model._meta.concrete_model._meta
|
||||||
verbose_name = smart_text(opts.verbose_name)
|
verbose_name = smart_str(opts.verbose_name)
|
||||||
field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
|
field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
|
||||||
|
|
||||||
if field.field_name == 'type':
|
if field.field_name == 'type':
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import MetricsView
|
from awx.api.views import MetricsView
|
||||||
|
|
||||||
|
|
||||||
urls = [url(r'^$', MetricsView.as_view(), name='metrics_view')]
|
urls = [re_path(r'^$', MetricsView.as_view(), name='metrics_view')]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import json
|
|||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import parsers
|
from rest_framework import parsers
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
# Python
|
# Python
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
|
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
@@ -25,7 +23,7 @@ __all__ = [
|
|||||||
'ProjectUpdatePermission',
|
'ProjectUpdatePermission',
|
||||||
'InventoryInventorySourcesUpdatePermission',
|
'InventoryInventorySourcesUpdatePermission',
|
||||||
'UserPermission',
|
'UserPermission',
|
||||||
'IsSuperUser',
|
'IsSystemAdminOrAuditor',
|
||||||
'InstanceGroupTowerPermission',
|
'InstanceGroupTowerPermission',
|
||||||
'WorkflowApprovalPermission',
|
'WorkflowApprovalPermission',
|
||||||
]
|
]
|
||||||
@@ -236,20 +234,18 @@ class UserPermission(ModelAccessPermission):
|
|||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
class IsSuperUser(permissions.BasePermission):
|
class IsSystemAdminOrAuditor(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
Allows access only to admin users.
|
Allows write access only to system admin users.
|
||||||
|
Allows read access only to system auditor users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return request.user and request.user.is_superuser
|
if not (request.user and request.user.is_authenticated):
|
||||||
|
|
||||||
|
|
||||||
class InstanceGroupTowerPermission(ModelAccessPermission):
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.method == 'DELETE' and obj.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
|
||||||
return False
|
return False
|
||||||
return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj)
|
if request.method == 'GET':
|
||||||
|
return request.user.is_superuser or request.user.is_system_auditor
|
||||||
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
class WebhookKeyPermission(permissions.BasePermission):
|
class WebhookKeyPermission(permissions.BasePermission):
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ from django.contrib.auth.password_validation import validate_password as django_
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -57,6 +57,7 @@ from awx.main.models import (
|
|||||||
Host,
|
Host,
|
||||||
Instance,
|
Instance,
|
||||||
InstanceGroup,
|
InstanceGroup,
|
||||||
|
InstanceLink,
|
||||||
Inventory,
|
Inventory,
|
||||||
InventorySource,
|
InventorySource,
|
||||||
InventoryUpdate,
|
InventoryUpdate,
|
||||||
@@ -96,7 +97,7 @@ from awx.main.models import (
|
|||||||
)
|
)
|
||||||
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
|
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
|
||||||
from awx.main.models.rbac import get_roles_on_resource, role_summary_fields_generator
|
from awx.main.models.rbac import get_roles_on_resource, role_summary_fields_generator
|
||||||
from awx.main.fields import ImplicitRoleField, JSONBField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.utils import (
|
from awx.main.utils import (
|
||||||
get_type_for_model,
|
get_type_for_model,
|
||||||
get_model_for_type,
|
get_model_for_type,
|
||||||
@@ -112,6 +113,7 @@ from awx.main.utils import (
|
|||||||
)
|
)
|
||||||
from awx.main.utils.filters import SmartFilter
|
from awx.main.utils.filters import SmartFilter
|
||||||
from awx.main.utils.named_url_graph import reset_counters
|
from awx.main.utils.named_url_graph import reset_counters
|
||||||
|
from awx.main.scheduler.task_manager_models import TaskManagerInstanceGroups, TaskManagerInstances
|
||||||
from awx.main.redact import UriCleaner, REPLACE_STR
|
from awx.main.redact import UriCleaner, REPLACE_STR
|
||||||
|
|
||||||
from awx.main.validators import vars_validate_or_raise
|
from awx.main.validators import vars_validate_or_raise
|
||||||
@@ -356,7 +358,7 @@ class BaseSerializer(serializers.ModelSerializer, metaclass=BaseSerializerMetacl
|
|||||||
}
|
}
|
||||||
choices = []
|
choices = []
|
||||||
for t in self.get_types():
|
for t in self.get_types():
|
||||||
name = _(type_name_map.get(t, force_text(get_model_for_type(t)._meta.verbose_name).title()))
|
name = _(type_name_map.get(t, force_str(get_model_for_type(t)._meta.verbose_name).title()))
|
||||||
choices.append((t, name))
|
choices.append((t, name))
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
@@ -378,19 +380,22 @@ class BaseSerializer(serializers.ModelSerializer, metaclass=BaseSerializerMetacl
|
|||||||
def _get_related(self, obj):
|
def _get_related(self, obj):
|
||||||
return {} if obj is None else self.get_related(obj)
|
return {} if obj is None else self.get_related(obj)
|
||||||
|
|
||||||
def _generate_named_url(self, url_path, obj, node):
|
def _generate_friendly_id(self, obj, node):
|
||||||
url_units = url_path.split('/')
|
|
||||||
reset_counters()
|
reset_counters()
|
||||||
named_url = node.generate_named_url(obj)
|
return node.generate_named_url(obj)
|
||||||
url_units[4] = named_url
|
|
||||||
return '/'.join(url_units)
|
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = OrderedDict()
|
res = OrderedDict()
|
||||||
view = self.context.get('view', None)
|
view = self.context.get('view', None)
|
||||||
if view and (hasattr(view, 'retrieve') or view.request.method == 'POST') and type(obj) in settings.NAMED_URL_GRAPH:
|
if view and (hasattr(view, 'retrieve') or view.request.method == 'POST') and type(obj) in settings.NAMED_URL_GRAPH:
|
||||||
original_url = self.get_url(obj)
|
original_path = self.get_url(obj)
|
||||||
res['named_url'] = self._generate_named_url(original_url, obj, settings.NAMED_URL_GRAPH[type(obj)])
|
path_components = original_path.lstrip('/').rstrip('/').split('/')
|
||||||
|
|
||||||
|
friendly_id = self._generate_friendly_id(obj, settings.NAMED_URL_GRAPH[type(obj)])
|
||||||
|
path_components[-1] = friendly_id
|
||||||
|
|
||||||
|
new_path = '/' + '/'.join(path_components) + '/'
|
||||||
|
res['named_url'] = new_path
|
||||||
if getattr(obj, 'created_by', None):
|
if getattr(obj, 'created_by', None):
|
||||||
res['created_by'] = self.reverse('api:user_detail', kwargs={'pk': obj.created_by.pk})
|
res['created_by'] = self.reverse('api:user_detail', kwargs={'pk': obj.created_by.pk})
|
||||||
if getattr(obj, 'modified_by', None):
|
if getattr(obj, 'modified_by', None):
|
||||||
@@ -641,7 +646,7 @@ class BaseSerializer(serializers.ModelSerializer, metaclass=BaseSerializerMetacl
|
|||||||
v2.extend(e)
|
v2.extend(e)
|
||||||
else:
|
else:
|
||||||
v2.append(e)
|
v2.append(e)
|
||||||
d[k] = list(map(force_text, v2))
|
d[k] = list(map(force_str, v2))
|
||||||
raise ValidationError(d)
|
raise ValidationError(d)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@@ -861,7 +866,7 @@ class UnifiedJobSerializer(BaseSerializer):
|
|||||||
if 'elapsed' in ret:
|
if 'elapsed' in ret:
|
||||||
if obj and obj.pk and obj.started and not obj.finished:
|
if obj and obj.pk and obj.started and not obj.finished:
|
||||||
td = now() - obj.started
|
td = now() - obj.started
|
||||||
ret['elapsed'] = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / (10 ** 6 * 1.0)
|
ret['elapsed'] = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / (10**6 * 1.0)
|
||||||
ret['elapsed'] = float(ret['elapsed'])
|
ret['elapsed'] = float(ret['elapsed'])
|
||||||
# Because this string is saved in the db in the source language,
|
# Because this string is saved in the db in the source language,
|
||||||
# it must be marked for translation after it is pulled from the db, not when set
|
# it must be marked for translation after it is pulled from the db, not when set
|
||||||
@@ -1259,6 +1264,12 @@ class OAuth2ApplicationSerializer(BaseSerializer):
|
|||||||
activity_stream=self.reverse('api:o_auth2_application_activity_stream_list', kwargs={'pk': obj.pk}),
|
activity_stream=self.reverse('api:o_auth2_application_activity_stream_list', kwargs={'pk': obj.pk}),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if obj.organization_id:
|
||||||
|
res.update(
|
||||||
|
dict(
|
||||||
|
organization=self.reverse('api:organization_detail', kwargs={'pk': obj.organization_id}),
|
||||||
|
)
|
||||||
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_modified(self, obj):
|
def get_modified(self, obj):
|
||||||
@@ -1596,7 +1607,6 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer):
|
|||||||
|
|
||||||
class ProjectUpdateDetailSerializer(ProjectUpdateSerializer):
|
class ProjectUpdateDetailSerializer(ProjectUpdateSerializer):
|
||||||
|
|
||||||
host_status_counts = serializers.SerializerMethodField(help_text=_('A count of hosts uniquely assigned to each status.'))
|
|
||||||
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1611,14 +1621,6 @@ class ProjectUpdateDetailSerializer(ProjectUpdateSerializer):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_host_status_counts(self, obj):
|
|
||||||
try:
|
|
||||||
counts = obj.project_update_events.only('event_data').get(event='playbook_on_stats').get_host_status_counts()
|
|
||||||
except ProjectUpdateEvent.DoesNotExist:
|
|
||||||
counts = {}
|
|
||||||
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSerializer):
|
class ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1639,7 +1641,25 @@ class BaseSerializerWithVariables(BaseSerializer):
|
|||||||
return vars_validate_or_raise(value)
|
return vars_validate_or_raise(value)
|
||||||
|
|
||||||
|
|
||||||
class InventorySerializer(BaseSerializerWithVariables):
|
class LabelsListMixin(object):
|
||||||
|
def _summary_field_labels(self, obj):
|
||||||
|
label_list = [{'id': x.id, 'name': x.name} for x in obj.labels.all()[:10]]
|
||||||
|
if has_model_field_prefetched(obj, 'labels'):
|
||||||
|
label_ct = len(obj.labels.all())
|
||||||
|
else:
|
||||||
|
if len(label_list) < 10:
|
||||||
|
label_ct = len(label_list)
|
||||||
|
else:
|
||||||
|
label_ct = obj.labels.count()
|
||||||
|
return {'count': label_ct, 'results': label_list}
|
||||||
|
|
||||||
|
def get_summary_fields(self, obj):
|
||||||
|
res = super(LabelsListMixin, self).get_summary_fields(obj)
|
||||||
|
res['labels'] = self._summary_field_labels(obj)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class InventorySerializer(LabelsListMixin, BaseSerializerWithVariables):
|
||||||
show_capabilities = ['edit', 'delete', 'adhoc', 'copy']
|
show_capabilities = ['edit', 'delete', 'adhoc', 'copy']
|
||||||
capabilities_prefetch = ['admin', 'adhoc', {'copy': 'organization.inventory_admin'}]
|
capabilities_prefetch = ['admin', 'adhoc', {'copy': 'organization.inventory_admin'}]
|
||||||
|
|
||||||
@@ -1680,6 +1700,7 @@ class InventorySerializer(BaseSerializerWithVariables):
|
|||||||
object_roles=self.reverse('api:inventory_object_roles_list', kwargs={'pk': obj.pk}),
|
object_roles=self.reverse('api:inventory_object_roles_list', kwargs={'pk': obj.pk}),
|
||||||
instance_groups=self.reverse('api:inventory_instance_groups_list', kwargs={'pk': obj.pk}),
|
instance_groups=self.reverse('api:inventory_instance_groups_list', kwargs={'pk': obj.pk}),
|
||||||
copy=self.reverse('api:inventory_copy', kwargs={'pk': obj.pk}),
|
copy=self.reverse('api:inventory_copy', kwargs={'pk': obj.pk}),
|
||||||
|
labels=self.reverse('api:inventory_label_list', kwargs={'pk': obj.pk}),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if obj.organization:
|
if obj.organization:
|
||||||
@@ -1695,7 +1716,7 @@ class InventorySerializer(BaseSerializerWithVariables):
|
|||||||
def validate_host_filter(self, host_filter):
|
def validate_host_filter(self, host_filter):
|
||||||
if host_filter:
|
if host_filter:
|
||||||
try:
|
try:
|
||||||
for match in JSONBField.get_lookups().keys():
|
for match in models.JSONField.get_lookups().keys():
|
||||||
if match == 'exact':
|
if match == 'exact':
|
||||||
# __exact is allowed
|
# __exact is allowed
|
||||||
continue
|
continue
|
||||||
@@ -1824,11 +1845,11 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
if port < 1 or port > 65535:
|
if port < 1 or port > 65535:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise serializers.ValidationError(_(u'Invalid port specification: %s') % force_text(port))
|
raise serializers.ValidationError(_(u'Invalid port specification: %s') % force_str(port))
|
||||||
return name, port
|
return name, port
|
||||||
|
|
||||||
def validate_name(self, value):
|
def validate_name(self, value):
|
||||||
name = force_text(value or '')
|
name = force_str(value or '')
|
||||||
# Validate here only, update in main validate method.
|
# Validate here only, update in main validate method.
|
||||||
host, port = self._get_host_port_from_name(name)
|
host, port = self._get_host_port_from_name(name)
|
||||||
return value
|
return value
|
||||||
@@ -1842,13 +1863,13 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
return vars_validate_or_raise(value)
|
return vars_validate_or_raise(value)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
name = force_text(attrs.get('name', self.instance and self.instance.name or ''))
|
name = force_str(attrs.get('name', self.instance and self.instance.name or ''))
|
||||||
inventory = attrs.get('inventory', self.instance and self.instance.inventory or '')
|
inventory = attrs.get('inventory', self.instance and self.instance.inventory or '')
|
||||||
host, port = self._get_host_port_from_name(name)
|
host, port = self._get_host_port_from_name(name)
|
||||||
|
|
||||||
if port:
|
if port:
|
||||||
attrs['name'] = host
|
attrs['name'] = host
|
||||||
variables = force_text(attrs.get('variables', self.instance and self.instance.variables or ''))
|
variables = force_str(attrs.get('variables', self.instance and self.instance.variables or ''))
|
||||||
vars_dict = parse_yaml_or_json(variables)
|
vars_dict = parse_yaml_or_json(variables)
|
||||||
vars_dict['ansible_ssh_port'] = port
|
vars_dict['ansible_ssh_port'] = port
|
||||||
attrs['variables'] = json.dumps(vars_dict)
|
attrs['variables'] = json.dumps(vars_dict)
|
||||||
@@ -1921,7 +1942,7 @@ class GroupSerializer(BaseSerializerWithVariables):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
name = force_text(attrs.get('name', self.instance and self.instance.name or ''))
|
name = force_str(attrs.get('name', self.instance and self.instance.name or ''))
|
||||||
inventory = attrs.get('inventory', self.instance and self.instance.inventory or '')
|
inventory = attrs.get('inventory', self.instance and self.instance.inventory or '')
|
||||||
if Host.objects.filter(name=name, inventory=inventory).exists():
|
if Host.objects.filter(name=name, inventory=inventory).exists():
|
||||||
raise serializers.ValidationError(_('A Host with that name already exists.'))
|
raise serializers.ValidationError(_('A Host with that name already exists.'))
|
||||||
@@ -2052,7 +2073,7 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventorySource
|
model = InventorySource
|
||||||
fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout', 'source_project', 'update_on_project_update') + (
|
fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout', 'source_project') + (
|
||||||
'last_update_failed',
|
'last_update_failed',
|
||||||
'last_updated',
|
'last_updated',
|
||||||
) # Backwards compatibility.
|
) # Backwards compatibility.
|
||||||
@@ -2115,11 +2136,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
raise serializers.ValidationError(_("Cannot use manual project for SCM-based inventory."))
|
raise serializers.ValidationError(_("Cannot use manual project for SCM-based inventory."))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_update_on_project_update(self, value):
|
|
||||||
if value and self.instance and self.instance.schedules.exists():
|
|
||||||
raise serializers.ValidationError(_("Setting not compatible with existing schedules."))
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate_inventory(self, value):
|
def validate_inventory(self, value):
|
||||||
if value and value.kind == 'smart':
|
if value and value.kind == 'smart':
|
||||||
raise serializers.ValidationError({"detail": _("Cannot create Inventory Source for Smart Inventory")})
|
raise serializers.ValidationError({"detail": _("Cannot create Inventory Source for Smart Inventory")})
|
||||||
@@ -2170,7 +2186,7 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
if ('source' in attrs or 'source_project' in attrs) and get_field_from_model_or_attrs('source_project') is None:
|
if ('source' in attrs or 'source_project' in attrs) and get_field_from_model_or_attrs('source_project') is None:
|
||||||
raise serializers.ValidationError({"source_project": _("Project required for scm type sources.")})
|
raise serializers.ValidationError({"source_project": _("Project required for scm type sources.")})
|
||||||
else:
|
else:
|
||||||
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path', 'update_on_project_update']))
|
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path']))
|
||||||
if redundant_scm_fields:
|
if redundant_scm_fields:
|
||||||
raise serializers.ValidationError({"detail": _("Cannot set %s if not SCM type." % ' '.join(redundant_scm_fields))})
|
raise serializers.ValidationError({"detail": _("Cannot set %s if not SCM type." % ' '.join(redundant_scm_fields))})
|
||||||
|
|
||||||
@@ -2215,7 +2231,6 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri
|
|||||||
'source_project_update',
|
'source_project_update',
|
||||||
'custom_virtualenv',
|
'custom_virtualenv',
|
||||||
'instance_group',
|
'instance_group',
|
||||||
'-controller_node',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
@@ -2290,7 +2305,6 @@ class InventoryUpdateDetailSerializer(InventoryUpdateSerializer):
|
|||||||
class InventoryUpdateListSerializer(InventoryUpdateSerializer, UnifiedJobListSerializer):
|
class InventoryUpdateListSerializer(InventoryUpdateSerializer, UnifiedJobListSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryUpdate
|
model = InventoryUpdate
|
||||||
fields = ('*', '-controller_node') # field removal undone by UJ serializer
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdateCancelSerializer(InventoryUpdateSerializer):
|
class InventoryUpdateCancelSerializer(InventoryUpdateSerializer):
|
||||||
@@ -2643,6 +2657,13 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
|
|
||||||
return credential_type
|
return credential_type
|
||||||
|
|
||||||
|
def validate_inputs(self, inputs):
|
||||||
|
if self.instance and self.instance.credential_type.kind == "vault":
|
||||||
|
if 'vault_id' in inputs and inputs['vault_id'] != self.instance.inputs['vault_id']:
|
||||||
|
raise ValidationError(_('Vault IDs cannot be changed once they have been created.'))
|
||||||
|
|
||||||
|
return inputs
|
||||||
|
|
||||||
|
|
||||||
class CredentialSerializerCreate(CredentialSerializer):
|
class CredentialSerializerCreate(CredentialSerializer):
|
||||||
|
|
||||||
@@ -2749,24 +2770,6 @@ class OrganizationCredentialSerializerCreate(CredentialSerializerCreate):
|
|||||||
fields = ('*', '-user', '-team')
|
fields = ('*', '-user', '-team')
|
||||||
|
|
||||||
|
|
||||||
class LabelsListMixin(object):
|
|
||||||
def _summary_field_labels(self, obj):
|
|
||||||
label_list = [{'id': x.id, 'name': x.name} for x in obj.labels.all()[:10]]
|
|
||||||
if has_model_field_prefetched(obj, 'labels'):
|
|
||||||
label_ct = len(obj.labels.all())
|
|
||||||
else:
|
|
||||||
if len(label_list) < 10:
|
|
||||||
label_ct = len(label_list)
|
|
||||||
else:
|
|
||||||
label_ct = obj.labels.count()
|
|
||||||
return {'count': label_ct, 'results': label_list}
|
|
||||||
|
|
||||||
def get_summary_fields(self, obj):
|
|
||||||
res = super(LabelsListMixin, self).get_summary_fields(obj)
|
|
||||||
res['labels'] = self._summary_field_labels(obj)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = (
|
fields = (
|
||||||
@@ -2833,8 +2836,8 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
|||||||
if not project:
|
if not project:
|
||||||
raise serializers.ValidationError({'project': _('This field is required.')})
|
raise serializers.ValidationError({'project': _('This field is required.')})
|
||||||
playbook_not_found = bool(
|
playbook_not_found = bool(
|
||||||
(project and project.scm_type and (not project.allow_override) and playbook and force_text(playbook) not in project.playbook_files)
|
(project and project.scm_type and (not project.allow_override) and playbook and force_str(playbook) not in project.playbook_files)
|
||||||
or (project and not project.scm_type and playbook and force_text(playbook) not in project.playbooks) # manual
|
or (project and not project.scm_type and playbook and force_str(playbook) not in project.playbooks) # manual
|
||||||
)
|
)
|
||||||
if playbook_not_found:
|
if playbook_not_found:
|
||||||
raise serializers.ValidationError({'playbook': _('Playbook not found for project.')})
|
raise serializers.ValidationError({'playbook': _('Playbook not found for project.')})
|
||||||
@@ -3095,7 +3098,6 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
|||||||
|
|
||||||
class JobDetailSerializer(JobSerializer):
|
class JobDetailSerializer(JobSerializer):
|
||||||
|
|
||||||
host_status_counts = serializers.SerializerMethodField(help_text=_('A count of hosts uniquely assigned to each status.'))
|
|
||||||
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
||||||
custom_virtualenv = serializers.ReadOnlyField()
|
custom_virtualenv = serializers.ReadOnlyField()
|
||||||
|
|
||||||
@@ -3111,14 +3113,6 @@ class JobDetailSerializer(JobSerializer):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_host_status_counts(self, obj):
|
|
||||||
try:
|
|
||||||
counts = obj.get_event_queryset().only('event_data').get(event='playbook_on_stats').get_host_status_counts()
|
|
||||||
except JobEvent.DoesNotExist:
|
|
||||||
counts = {}
|
|
||||||
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
class JobCancelSerializer(BaseSerializer):
|
class JobCancelSerializer(BaseSerializer):
|
||||||
|
|
||||||
@@ -3307,21 +3301,10 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class AdHocCommandDetailSerializer(AdHocCommandSerializer):
|
class AdHocCommandDetailSerializer(AdHocCommandSerializer):
|
||||||
|
|
||||||
host_status_counts = serializers.SerializerMethodField(help_text=_('A count of hosts uniquely assigned to each status.'))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdHocCommand
|
model = AdHocCommand
|
||||||
fields = ('*', 'host_status_counts')
|
fields = ('*', 'host_status_counts')
|
||||||
|
|
||||||
def get_host_status_counts(self, obj):
|
|
||||||
try:
|
|
||||||
counts = obj.ad_hoc_command_events.only('event_data').get(event='playbook_on_stats').get_host_status_counts()
|
|
||||||
except AdHocCommandEvent.DoesNotExist:
|
|
||||||
counts = {}
|
|
||||||
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandCancelSerializer(AdHocCommandSerializer):
|
class AdHocCommandCancelSerializer(AdHocCommandSerializer):
|
||||||
|
|
||||||
@@ -3623,7 +3606,7 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
|||||||
job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
||||||
limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
||||||
skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
||||||
diff_mode = serializers.NullBooleanField(required=False, default=None)
|
diff_mode = serializers.BooleanField(required=False, allow_null=True, default=None)
|
||||||
verbosity = serializers.ChoiceField(allow_null=True, required=False, default=None, choices=VERBOSITY_CHOICES)
|
verbosity = serializers.ChoiceField(allow_null=True, required=False, default=None, choices=VERBOSITY_CHOICES)
|
||||||
exclude_errors = ()
|
exclude_errors = ()
|
||||||
|
|
||||||
@@ -4490,7 +4473,10 @@ class NotificationTemplateSerializer(BaseSerializer):
|
|||||||
body = messages[event].get('body', {})
|
body = messages[event].get('body', {})
|
||||||
if body:
|
if body:
|
||||||
try:
|
try:
|
||||||
potential_body = json.loads(body)
|
rendered_body = (
|
||||||
|
sandbox.ImmutableSandboxedEnvironment(undefined=DescriptiveUndefined).from_string(body).render(JobNotificationMixin.context_stub())
|
||||||
|
)
|
||||||
|
potential_body = json.loads(rendered_body)
|
||||||
if not isinstance(potential_body, dict):
|
if not isinstance(potential_body, dict):
|
||||||
error_list.append(
|
error_list.append(
|
||||||
_("Webhook body for '{}' should be a json dictionary. Found type '{}'.".format(event, type(potential_body).__name__))
|
_("Webhook body for '{}' should be a json dictionary. Found type '{}'.".format(event, type(potential_body).__name__))
|
||||||
@@ -4633,69 +4619,74 @@ class SchedulePreviewSerializer(BaseSerializer):
|
|||||||
|
|
||||||
# We reject rrules if:
|
# We reject rrules if:
|
||||||
# - DTSTART is not include
|
# - DTSTART is not include
|
||||||
# - INTERVAL is not included
|
# - Multiple DTSTART
|
||||||
# - SECONDLY is used
|
# - At least one of RRULE is not included
|
||||||
# - TZID is used
|
# - EXDATE or RDATE is included
|
||||||
# - BYDAY prefixed with a number (MO is good but not 20MO)
|
# For any rule in the ruleset:
|
||||||
# - BYYEARDAY
|
# - INTERVAL is not included
|
||||||
# - BYWEEKNO
|
# - SECONDLY is used
|
||||||
# - Multiple DTSTART or RRULE elements
|
# - BYDAY prefixed with a number (MO is good but not 20MO)
|
||||||
# - Can't contain both COUNT and UNTIL
|
# - Can't contain both COUNT and UNTIL
|
||||||
# - COUNT > 999
|
# - COUNT > 999
|
||||||
def validate_rrule(self, value):
|
def validate_rrule(self, value):
|
||||||
rrule_value = value
|
rrule_value = value
|
||||||
multi_by_month_day = r".*?BYMONTHDAY[\:\=][0-9]+,-*[0-9]+"
|
|
||||||
multi_by_month = r".*?BYMONTH[\:\=][0-9]+,[0-9]+"
|
|
||||||
by_day_with_numeric_prefix = r".*?BYDAY[\:\=][0-9]+[a-zA-Z]{2}"
|
by_day_with_numeric_prefix = r".*?BYDAY[\:\=][0-9]+[a-zA-Z]{2}"
|
||||||
match_count = re.match(r".*?(COUNT\=[0-9]+)", rrule_value)
|
|
||||||
match_multiple_dtstart = re.findall(r".*?(DTSTART(;[^:]+)?\:[0-9]+T[0-9]+Z?)", rrule_value)
|
match_multiple_dtstart = re.findall(r".*?(DTSTART(;[^:]+)?\:[0-9]+T[0-9]+Z?)", rrule_value)
|
||||||
match_native_dtstart = re.findall(r".*?(DTSTART:[0-9]+T[0-9]+) ", rrule_value)
|
match_native_dtstart = re.findall(r".*?(DTSTART:[0-9]+T[0-9]+) ", rrule_value)
|
||||||
match_multiple_rrule = re.findall(r".*?(RRULE\:)", rrule_value)
|
match_multiple_rrule = re.findall(r".*?(RULE\:[^\s]*)", rrule_value)
|
||||||
|
errors = []
|
||||||
if not len(match_multiple_dtstart):
|
if not len(match_multiple_dtstart):
|
||||||
raise serializers.ValidationError(_('Valid DTSTART required in rrule. Value should start with: DTSTART:YYYYMMDDTHHMMSSZ'))
|
errors.append(_('Valid DTSTART required in rrule. Value should start with: DTSTART:YYYYMMDDTHHMMSSZ'))
|
||||||
if len(match_native_dtstart):
|
if len(match_native_dtstart):
|
||||||
raise serializers.ValidationError(_('DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ.'))
|
errors.append(_('DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ.'))
|
||||||
if len(match_multiple_dtstart) > 1:
|
if len(match_multiple_dtstart) > 1:
|
||||||
raise serializers.ValidationError(_('Multiple DTSTART is not supported.'))
|
errors.append(_('Multiple DTSTART is not supported.'))
|
||||||
if not len(match_multiple_rrule):
|
if "rrule:" not in rrule_value.lower():
|
||||||
raise serializers.ValidationError(_('RRULE required in rrule.'))
|
errors.append(_('One or more rule required in rrule.'))
|
||||||
if len(match_multiple_rrule) > 1:
|
if "exdate:" in rrule_value.lower():
|
||||||
raise serializers.ValidationError(_('Multiple RRULE is not supported.'))
|
raise serializers.ValidationError(_('EXDATE not allowed in rrule.'))
|
||||||
if 'interval' not in rrule_value.lower():
|
if "rdate:" in rrule_value.lower():
|
||||||
raise serializers.ValidationError(_('INTERVAL required in rrule.'))
|
raise serializers.ValidationError(_('RDATE not allowed in rrule.'))
|
||||||
if 'secondly' in rrule_value.lower():
|
for a_rule in match_multiple_rrule:
|
||||||
raise serializers.ValidationError(_('SECONDLY is not supported.'))
|
if 'interval' not in a_rule.lower():
|
||||||
if re.match(multi_by_month_day, rrule_value):
|
errors.append("{0}: {1}".format(_('INTERVAL required in rrule'), a_rule))
|
||||||
raise serializers.ValidationError(_('Multiple BYMONTHDAYs not supported.'))
|
elif 'secondly' in a_rule.lower():
|
||||||
if re.match(multi_by_month, rrule_value):
|
errors.append("{0}: {1}".format(_('SECONDLY is not supported'), a_rule))
|
||||||
raise serializers.ValidationError(_('Multiple BYMONTHs not supported.'))
|
if re.match(by_day_with_numeric_prefix, a_rule):
|
||||||
if re.match(by_day_with_numeric_prefix, rrule_value):
|
errors.append("{0}: {1}".format(_("BYDAY with numeric prefix not supported"), a_rule))
|
||||||
raise serializers.ValidationError(_("BYDAY with numeric prefix not supported."))
|
if 'COUNT' in a_rule and 'UNTIL' in a_rule:
|
||||||
if 'byyearday' in rrule_value.lower():
|
errors.append("{0}: {1}".format(_("RRULE may not contain both COUNT and UNTIL"), a_rule))
|
||||||
raise serializers.ValidationError(_("BYYEARDAY not supported."))
|
match_count = re.match(r".*?(COUNT\=[0-9]+)", a_rule)
|
||||||
if 'byweekno' in rrule_value.lower():
|
if match_count:
|
||||||
raise serializers.ValidationError(_("BYWEEKNO not supported."))
|
count_val = match_count.groups()[0].strip().split("=")
|
||||||
if 'COUNT' in rrule_value and 'UNTIL' in rrule_value:
|
if int(count_val[1]) > 999:
|
||||||
raise serializers.ValidationError(_("RRULE may not contain both COUNT and UNTIL"))
|
errors.append("{0}: {1}".format(_("COUNT > 999 is unsupported"), a_rule))
|
||||||
if match_count:
|
|
||||||
count_val = match_count.groups()[0].strip().split("=")
|
|
||||||
if int(count_val[1]) > 999:
|
|
||||||
raise serializers.ValidationError(_("COUNT > 999 is unsupported."))
|
|
||||||
try:
|
try:
|
||||||
Schedule.rrulestr(rrule_value)
|
Schedule.rrulestr(rrule_value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
raise serializers.ValidationError(_("rrule parsing failed validation: {}").format(e))
|
errors.append(_("rrule parsing failed validation: {}").format(e))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise serializers.ValidationError(errors)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSerializer):
|
class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSerializer):
|
||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
|
|
||||||
timezone = serializers.SerializerMethodField()
|
timezone = serializers.SerializerMethodField(
|
||||||
until = serializers.SerializerMethodField()
|
help_text=_(
|
||||||
|
'The timezone this schedule runs in. This field is extracted from the RRULE. If the timezone in the RRULE is a link to another timezone, the link will be reflected in this field.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
until = serializers.SerializerMethodField(
|
||||||
|
help_text=_('The date this schedule will end. This field is computed from the RRULE. If the schedule does not end an emptry string will be returned'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Schedule
|
model = Schedule
|
||||||
@@ -4749,13 +4740,6 @@ class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSeria
|
|||||||
raise serializers.ValidationError(_('Inventory Source must be a cloud resource.'))
|
raise serializers.ValidationError(_('Inventory Source must be a cloud resource.'))
|
||||||
elif type(value) == Project and value.scm_type == '':
|
elif type(value) == Project and value.scm_type == '':
|
||||||
raise serializers.ValidationError(_('Manual Project cannot have a schedule set.'))
|
raise serializers.ValidationError(_('Manual Project cannot have a schedule set.'))
|
||||||
elif type(value) == InventorySource and value.source == 'scm' and value.update_on_project_update:
|
|
||||||
raise serializers.ValidationError(
|
|
||||||
_(
|
|
||||||
'Inventory sources with `update_on_project_update` cannot be scheduled. '
|
|
||||||
'Schedule its source project `{}` instead.'.format(value.source_project.name)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@@ -4767,6 +4751,28 @@ class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSeria
|
|||||||
return super(ScheduleSerializer, self).validate(attrs)
|
return super(ScheduleSerializer, self).validate(attrs)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceLinkSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = InstanceLink
|
||||||
|
fields = ('source', 'target')
|
||||||
|
|
||||||
|
source = serializers.SlugRelatedField(slug_field="hostname", read_only=True)
|
||||||
|
target = serializers.SlugRelatedField(slug_field="hostname", read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceNodeSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Instance
|
||||||
|
fields = ('id', 'hostname', 'node_type', 'node_state')
|
||||||
|
|
||||||
|
node_state = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_node_state(self, obj):
|
||||||
|
if not obj.enabled:
|
||||||
|
return "disabled"
|
||||||
|
return "error" if obj.errors else "healthy"
|
||||||
|
|
||||||
|
|
||||||
class InstanceSerializer(BaseSerializer):
|
class InstanceSerializer(BaseSerializer):
|
||||||
|
|
||||||
consumed_capacity = serializers.SerializerMethodField()
|
consumed_capacity = serializers.SerializerMethodField()
|
||||||
@@ -4786,6 +4792,9 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
"hostname",
|
"hostname",
|
||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
|
"last_seen",
|
||||||
|
"last_health_check",
|
||||||
|
"errors",
|
||||||
'capacity_adjustment',
|
'capacity_adjustment',
|
||||||
"version",
|
"version",
|
||||||
"capacity",
|
"capacity",
|
||||||
@@ -4806,6 +4815,9 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
res = super(InstanceSerializer, self).get_related(obj)
|
res = super(InstanceSerializer, self).get_related(obj)
|
||||||
res['jobs'] = self.reverse('api:instance_unified_jobs_list', kwargs={'pk': obj.pk})
|
res['jobs'] = self.reverse('api:instance_unified_jobs_list', kwargs={'pk': obj.pk})
|
||||||
res['instance_groups'] = self.reverse('api:instance_instance_groups_list', kwargs={'pk': obj.pk})
|
res['instance_groups'] = self.reverse('api:instance_instance_groups_list', kwargs={'pk': obj.pk})
|
||||||
|
if self.context['request'].user.is_superuser or self.context['request'].user.is_system_auditor:
|
||||||
|
if obj.node_type != 'hop':
|
||||||
|
res['health_check'] = self.reverse('api:instance_health_check', kwargs={'pk': obj.pk})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_consumed_capacity(self, obj):
|
def get_consumed_capacity(self, obj):
|
||||||
@@ -4817,12 +4829,23 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
else:
|
else:
|
||||||
return float("{0:.2f}".format(((float(obj.capacity) - float(obj.consumed_capacity)) / (float(obj.capacity))) * 100))
|
return float("{0:.2f}".format(((float(obj.capacity) - float(obj.consumed_capacity)) / (float(obj.capacity))) * 100))
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
if self.instance.node_type == 'hop':
|
||||||
|
raise serializers.ValidationError(_('Hop node instances may not be changed.'))
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceHealthCheckSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Instance
|
||||||
|
read_only_fields = ('uuid', 'hostname', 'version', 'last_health_check', 'errors', 'cpu', 'memory', 'cpu_capacity', 'mem_capacity', 'capacity')
|
||||||
|
fields = read_only_fields
|
||||||
|
|
||||||
|
|
||||||
class InstanceGroupSerializer(BaseSerializer):
|
class InstanceGroupSerializer(BaseSerializer):
|
||||||
|
|
||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
|
|
||||||
committed_capacity = serializers.SerializerMethodField()
|
|
||||||
consumed_capacity = serializers.SerializerMethodField()
|
consumed_capacity = serializers.SerializerMethodField()
|
||||||
percent_capacity_remaining = serializers.SerializerMethodField()
|
percent_capacity_remaining = serializers.SerializerMethodField()
|
||||||
jobs_running = serializers.IntegerField(
|
jobs_running = serializers.IntegerField(
|
||||||
@@ -4871,7 +4894,6 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
"capacity",
|
"capacity",
|
||||||
"committed_capacity",
|
|
||||||
"consumed_capacity",
|
"consumed_capacity",
|
||||||
"percent_capacity_remaining",
|
"percent_capacity_remaining",
|
||||||
"jobs_running",
|
"jobs_running",
|
||||||
@@ -4896,6 +4918,9 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def validate_policy_instance_list(self, value):
|
def validate_policy_instance_list(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if self.instance.policy_instance_list != value:
|
||||||
|
raise serializers.ValidationError(_('%s instance group policy_instance_list may not be changed.' % self.instance.name))
|
||||||
for instance_name in value:
|
for instance_name in value:
|
||||||
if value.count(instance_name) > 1:
|
if value.count(instance_name) > 1:
|
||||||
raise serializers.ValidationError(_('Duplicate entry {}.').format(instance_name))
|
raise serializers.ValidationError(_('Duplicate entry {}.').format(instance_name))
|
||||||
@@ -4906,6 +4931,11 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_policy_instance_percentage(self, value):
|
def validate_policy_instance_percentage(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if value != self.instance.policy_instance_percentage:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_('%s instance group policy_instance_percentage may not be changed from the initial value set by the installer.' % self.instance.name)
|
||||||
|
)
|
||||||
if value and self.instance and self.instance.is_container_group:
|
if value and self.instance and self.instance.is_container_group:
|
||||||
raise serializers.ValidationError(_('Containerized instances may not be managed via the API'))
|
raise serializers.ValidationError(_('Containerized instances may not be managed via the API'))
|
||||||
return value
|
return value
|
||||||
@@ -4924,6 +4954,13 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def validate_is_container_group(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if value != self.instance.is_container_group:
|
||||||
|
raise serializers.ValidationError(_('%s instance group is_container_group may not be changed.' % self.instance.name))
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
def validate_credential(self, value):
|
def validate_credential(self, value):
|
||||||
if value and not value.kubernetes:
|
if value and not value.kubernetes:
|
||||||
raise serializers.ValidationError(_('Only Kubernetes credentials can be associated with an Instance Group'))
|
raise serializers.ValidationError(_('Only Kubernetes credentials can be associated with an Instance Group'))
|
||||||
@@ -4937,30 +4974,29 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def get_capacity_dict(self):
|
def get_ig_mgr(self):
|
||||||
# Store capacity values (globally computed) in the context
|
# Store capacity values (globally computed) in the context
|
||||||
if 'capacity_map' not in self.context:
|
if 'task_manager_igs' not in self.context:
|
||||||
ig_qs = None
|
instance_groups_queryset = None
|
||||||
jobs_qs = UnifiedJob.objects.filter(status__in=('running', 'waiting'))
|
jobs_qs = UnifiedJob.objects.filter(status__in=('running', 'waiting'))
|
||||||
if self.parent: # Is ListView:
|
if self.parent: # Is ListView:
|
||||||
ig_qs = self.parent.instance
|
instance_groups_queryset = self.parent.instance
|
||||||
self.context['capacity_map'] = InstanceGroup.objects.capacity_values(qs=ig_qs, tasks=jobs_qs, breakdown=True)
|
|
||||||
return self.context['capacity_map']
|
instances = TaskManagerInstances(jobs_qs)
|
||||||
|
instance_groups = TaskManagerInstanceGroups(instances_by_hostname=instances, instance_groups_queryset=instance_groups_queryset)
|
||||||
|
|
||||||
|
self.context['task_manager_igs'] = instance_groups
|
||||||
|
return self.context['task_manager_igs']
|
||||||
|
|
||||||
def get_consumed_capacity(self, obj):
|
def get_consumed_capacity(self, obj):
|
||||||
return self.get_capacity_dict()[obj.name]['running_capacity']
|
ig_mgr = self.get_ig_mgr()
|
||||||
|
return ig_mgr.get_consumed_capacity(obj.name)
|
||||||
def get_committed_capacity(self, obj):
|
|
||||||
return self.get_capacity_dict()[obj.name]['committed_capacity']
|
|
||||||
|
|
||||||
def get_percent_capacity_remaining(self, obj):
|
def get_percent_capacity_remaining(self, obj):
|
||||||
if not obj.capacity:
|
if not obj.capacity:
|
||||||
return 0.0
|
return 0.0
|
||||||
consumed = self.get_consumed_capacity(obj)
|
ig_mgr = self.get_ig_mgr()
|
||||||
if consumed >= obj.capacity:
|
return float("{0:.2f}".format((float(ig_mgr.get_remaining_capacity(obj.name)) / (float(obj.capacity))) * 100))
|
||||||
return 0.0
|
|
||||||
else:
|
|
||||||
return float("{0:.2f}".format(((float(obj.capacity) - float(consumed)) / (float(obj.capacity))) * 100))
|
|
||||||
|
|
||||||
def get_instances(self, obj):
|
def get_instances(self, obj):
|
||||||
return obj.instances.count()
|
return obj.instances.count()
|
||||||
@@ -4991,6 +5027,7 @@ class ActivityStreamSerializer(BaseSerializer):
|
|||||||
('credential_type', ('id', 'name', 'description', 'kind', 'managed')),
|
('credential_type', ('id', 'name', 'description', 'kind', 'managed')),
|
||||||
('ad_hoc_command', ('id', 'name', 'status', 'limit')),
|
('ad_hoc_command', ('id', 'name', 'status', 'limit')),
|
||||||
('workflow_approval', ('id', 'name', 'unified_job_id')),
|
('workflow_approval', ('id', 'name', 'unified_job_id')),
|
||||||
|
('instance', ('id', 'hostname')),
|
||||||
]
|
]
|
||||||
return field_list
|
return field_list
|
||||||
|
|
||||||
@@ -5037,7 +5074,7 @@ class ActivityStreamSerializer(BaseSerializer):
|
|||||||
try:
|
try:
|
||||||
return json.loads(obj.changes)
|
return json.loads(obj.changes)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warn("Error deserializing activity stream json changes")
|
logger.warning("Error deserializing activity stream json changes")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_object_association(self, obj):
|
def get_object_association(self, obj):
|
||||||
|
|||||||
33
awx/api/templates/api/instance_health_check.md
Normal file
33
awx/api/templates/api/instance_health_check.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% ifmeth GET %}
|
||||||
|
# Health Check Data
|
||||||
|
|
||||||
|
Health checks are used to obtain important data about an instance.
|
||||||
|
Instance fields affected by the health check are shown in this view.
|
||||||
|
Fundamentally, health checks require running code on the machine in question.
|
||||||
|
|
||||||
|
- For instances with `node_type` of "control" or "hybrid", health checks are
|
||||||
|
performed as part of a periodic task that runs in the background.
|
||||||
|
- For instances with `node_type` of "execution", health checks are done by submitting
|
||||||
|
a work unit through the receptor mesh.
|
||||||
|
|
||||||
|
If ran through the receptor mesh, the invoked command is:
|
||||||
|
|
||||||
|
```
|
||||||
|
ansible-runner worker --worker-info
|
||||||
|
```
|
||||||
|
|
||||||
|
For execution nodes, these checks are _not_ performed on a regular basis.
|
||||||
|
Health checks against functional nodes will be ran when the node is first discovered.
|
||||||
|
Health checks against nodes with errors will be repeated at a reduced frequency.
|
||||||
|
|
||||||
|
{% endifmeth %}
|
||||||
|
|
||||||
|
{% ifmeth POST %}
|
||||||
|
# Manually Initiate a Health Check
|
||||||
|
For purposes of error remediation or debugging, a health check can be
|
||||||
|
manually initiated by making a POST request to this endpoint.
|
||||||
|
|
||||||
|
This will submit the work unit to the target node through the receptor mesh and wait for it to finish.
|
||||||
|
The model will be updated with the result.
|
||||||
|
Up-to-date values of the fields will be returned in the response data.
|
||||||
|
{% endifmeth %}
|
||||||
102
awx/api/templates/api/job_job_events_children_summary.md
Normal file
102
awx/api/templates/api/job_job_events_children_summary.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# View a summary of children events
|
||||||
|
|
||||||
|
Special view to facilitate processing job output in the UI.
|
||||||
|
In order to collapse events and their children, the UI needs to know how
|
||||||
|
many children exist for a given event.
|
||||||
|
The UI also needs to know the order of the event (0 based index), which
|
||||||
|
usually matches the counter, but not always.
|
||||||
|
This view returns a JSON object where the key is the event counter, and the value
|
||||||
|
includes the number of children (and grandchildren) events.
|
||||||
|
Only events with children are included in the output.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
e.g. Demo Job Template job
|
||||||
|
tuple(event counter, uuid, parent_uuid)
|
||||||
|
|
||||||
|
```
|
||||||
|
(1, '4598d19e-93b4-4e33-a0ae-b387a7348964', '')
|
||||||
|
(2, 'aae0d189-e3cb-102a-9f00-000000000006', '4598d19e-93b4-4e33-a0ae-b387a7348964')
|
||||||
|
(3, 'aae0d189-e3cb-102a-9f00-00000000000c', 'aae0d189-e3cb-102a-9f00-000000000006')
|
||||||
|
(4, 'f4194f14-e406-4124-8519-0fdb08b18f4b', 'aae0d189-e3cb-102a-9f00-00000000000c')
|
||||||
|
(5, '39f7ad99-dbf3-41e0-93f8-9999db4004f2', 'aae0d189-e3cb-102a-9f00-00000000000c')
|
||||||
|
(6, 'aae0d189-e3cb-102a-9f00-000000000008', 'aae0d189-e3cb-102a-9f00-000000000006')
|
||||||
|
(7, '39a49992-5ca4-4b6c-b178-e56d0b0333da', 'aae0d189-e3cb-102a-9f00-000000000008')
|
||||||
|
(8, '504f3b28-3ea8-4f6f-bd82-60cf8e807cc0', 'aae0d189-e3cb-102a-9f00-000000000008')
|
||||||
|
(9, 'a242be54-ebe6-4021-afab-f2878bff2e9f', '4598d19e-93b4-4e33-a0ae-b387a7348964')
|
||||||
|
```
|
||||||
|
|
||||||
|
output
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"rowNumber": 0,
|
||||||
|
"numChildren": 8
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"rowNumber": 1,
|
||||||
|
"numChildren": 6
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"rowNumber": 2,
|
||||||
|
"numChildren": 2
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"rowNumber": 5,
|
||||||
|
"numChildren": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"meta_event_nested_parent_uuid": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
counter 1 is event 0, and has 8 children
|
||||||
|
counter 2 is event 1, and has 6 children
|
||||||
|
etc.
|
||||||
|
|
||||||
|
The UI also needs to be able to collapse over "meta" events -- events that
|
||||||
|
show up due to verbosity or warnings from the system while the play is running.
|
||||||
|
These events have a 0 level event, with no parent uuid.
|
||||||
|
|
||||||
|
```
|
||||||
|
playbook_on_start
|
||||||
|
verbose
|
||||||
|
playbook_on_play_start
|
||||||
|
playbook_on_task_start
|
||||||
|
runner_on_start <- level 3
|
||||||
|
verbose <- jump to level 0
|
||||||
|
verbose
|
||||||
|
runner_on_ok <- jump back to level 3
|
||||||
|
playbook_on_task_start
|
||||||
|
runner_on_start
|
||||||
|
runner_on_ok
|
||||||
|
verbose
|
||||||
|
verbose
|
||||||
|
playbook_on_stats
|
||||||
|
```
|
||||||
|
|
||||||
|
These verbose statements that fall in the middle of a series of children events
|
||||||
|
are problematic for the UI.
|
||||||
|
To help, this view will attempt to place the events into the hierarchy, without
|
||||||
|
the event level jumps.
|
||||||
|
|
||||||
|
```
|
||||||
|
playbook_on_start
|
||||||
|
verbose
|
||||||
|
playbook_on_play_start
|
||||||
|
playbook_on_task_start
|
||||||
|
runner_on_start <- A
|
||||||
|
verbose <- this maps to the uuid of A
|
||||||
|
verbose
|
||||||
|
runner_on_ok
|
||||||
|
playbook_on_task_start <- B
|
||||||
|
runner_on_start
|
||||||
|
runner_on_ok
|
||||||
|
verbose <- this maps to the uuid of B
|
||||||
|
verbose
|
||||||
|
playbook_on_stats
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will include a JSON object where the key is the event counter,
|
||||||
|
and the value is the assigned nested uuid.
|
||||||
1
awx/api/templates/api/mesh_visualizer.md
Normal file
1
awx/api/templates/api/mesh_visualizer.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Make a GET request to this resource to obtain a list all Receptor Nodes and their links.
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import ActivityStreamList, ActivityStreamDetail
|
from awx.api.views import ActivityStreamList, ActivityStreamDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ActivityStreamList.as_view(), name='activity_stream_list'),
|
re_path(r'^$', ActivityStreamList.as_view(), name='activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', ActivityStreamDetail.as_view(), name='activity_stream_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ActivityStreamDetail.as_view(), name='activity_stream_detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
AdHocCommandList,
|
AdHocCommandList,
|
||||||
@@ -16,14 +16,14 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', AdHocCommandList.as_view(), name='ad_hoc_command_list'),
|
re_path(r'^$', AdHocCommandList.as_view(), name='ad_hoc_command_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', AdHocCommandDetail.as_view(), name='ad_hoc_command_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', AdHocCommandDetail.as_view(), name='ad_hoc_command_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', AdHocCommandCancel.as_view(), name='ad_hoc_command_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', AdHocCommandCancel.as_view(), name='ad_hoc_command_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/relaunch/$', AdHocCommandRelaunch.as_view(), name='ad_hoc_command_relaunch'),
|
re_path(r'^(?P<pk>[0-9]+)/relaunch/$', AdHocCommandRelaunch.as_view(), name='ad_hoc_command_relaunch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/events/$', AdHocCommandAdHocCommandEventsList.as_view(), name='ad_hoc_command_ad_hoc_command_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/events/$', AdHocCommandAdHocCommandEventsList.as_view(), name='ad_hoc_command_ad_hoc_command_events_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', AdHocCommandActivityStreamList.as_view(), name='ad_hoc_command_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', AdHocCommandActivityStreamList.as_view(), name='ad_hoc_command_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', AdHocCommandNotificationsList.as_view(), name='ad_hoc_command_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', AdHocCommandNotificationsList.as_view(), name='ad_hoc_command_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/stdout/$', AdHocCommandStdout.as_view(), name='ad_hoc_command_stdout'),
|
re_path(r'^(?P<pk>[0-9]+)/stdout/$', AdHocCommandStdout.as_view(), name='ad_hoc_command_stdout'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import AdHocCommandEventDetail
|
from awx.api.views import AdHocCommandEventDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^(?P<pk>[0-9]+)/$', AdHocCommandEventDetail.as_view(), name='ad_hoc_command_event_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', AdHocCommandEventDetail.as_view(), name='ad_hoc_command_event_detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
CredentialList,
|
CredentialList,
|
||||||
@@ -18,16 +18,16 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', CredentialList.as_view(), name='credential_list'),
|
re_path(r'^$', CredentialList.as_view(), name='credential_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', CredentialActivityStreamList.as_view(), name='credential_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', CredentialActivityStreamList.as_view(), name='credential_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', CredentialDetail.as_view(), name='credential_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', CredentialDetail.as_view(), name='credential_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', CredentialAccessList.as_view(), name='credential_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', CredentialAccessList.as_view(), name='credential_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', CredentialObjectRolesList.as_view(), name='credential_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', CredentialObjectRolesList.as_view(), name='credential_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/owner_users/$', CredentialOwnerUsersList.as_view(), name='credential_owner_users_list'),
|
re_path(r'^(?P<pk>[0-9]+)/owner_users/$', CredentialOwnerUsersList.as_view(), name='credential_owner_users_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/owner_teams/$', CredentialOwnerTeamsList.as_view(), name='credential_owner_teams_list'),
|
re_path(r'^(?P<pk>[0-9]+)/owner_teams/$', CredentialOwnerTeamsList.as_view(), name='credential_owner_teams_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', CredentialCopy.as_view(), name='credential_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', CredentialCopy.as_view(), name='credential_copy'),
|
||||||
url(r'^(?P<pk>[0-9]+)/input_sources/$', CredentialInputSourceSubList.as_view(), name='credential_input_source_sublist'),
|
re_path(r'^(?P<pk>[0-9]+)/input_sources/$', CredentialInputSourceSubList.as_view(), name='credential_input_source_sublist'),
|
||||||
url(r'^(?P<pk>[0-9]+)/test/$', CredentialExternalTest.as_view(), name='credential_external_test'),
|
re_path(r'^(?P<pk>[0-9]+)/test/$', CredentialExternalTest.as_view(), name='credential_external_test'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Copyright (c) 2019 Ansible, Inc.
|
# Copyright (c) 2019 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import CredentialInputSourceDetail, CredentialInputSourceList
|
from awx.api.views import CredentialInputSourceDetail, CredentialInputSourceList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', CredentialInputSourceList.as_view(), name='credential_input_source_list'),
|
re_path(r'^$', CredentialInputSourceList.as_view(), name='credential_input_source_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', CredentialInputSourceDetail.as_view(), name='credential_input_source_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', CredentialInputSourceDetail.as_view(), name='credential_input_source_detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import CredentialTypeList, CredentialTypeDetail, CredentialTypeCredentialList, CredentialTypeActivityStreamList, CredentialTypeExternalTest
|
from awx.api.views import CredentialTypeList, CredentialTypeDetail, CredentialTypeCredentialList, CredentialTypeActivityStreamList, CredentialTypeExternalTest
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', CredentialTypeList.as_view(), name='credential_type_list'),
|
re_path(r'^$', CredentialTypeList.as_view(), name='credential_type_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', CredentialTypeDetail.as_view(), name='credential_type_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', CredentialTypeDetail.as_view(), name='credential_type_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', CredentialTypeCredentialList.as_view(), name='credential_type_credential_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', CredentialTypeCredentialList.as_view(), name='credential_type_credential_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', CredentialTypeActivityStreamList.as_view(), name='credential_type_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', CredentialTypeActivityStreamList.as_view(), name='credential_type_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/test/$', CredentialTypeExternalTest.as_view(), name='credential_type_external_test'),
|
re_path(r'^(?P<pk>[0-9]+)/test/$', CredentialTypeExternalTest.as_view(), name='credential_type_external_test'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
ExecutionEnvironmentList,
|
ExecutionEnvironmentList,
|
||||||
@@ -10,11 +10,11 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ExecutionEnvironmentList.as_view(), name='execution_environment_list'),
|
re_path(r'^$', ExecutionEnvironmentList.as_view(), name='execution_environment_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', ExecutionEnvironmentDetail.as_view(), name='execution_environment_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ExecutionEnvironmentDetail.as_view(), name='execution_environment_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/unified_job_templates/$', ExecutionEnvironmentJobTemplateList.as_view(), name='execution_environment_job_template_list'),
|
re_path(r'^(?P<pk>[0-9]+)/unified_job_templates/$', ExecutionEnvironmentJobTemplateList.as_view(), name='execution_environment_job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', ExecutionEnvironmentCopy.as_view(), name='execution_environment_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', ExecutionEnvironmentCopy.as_view(), name='execution_environment_copy'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', ExecutionEnvironmentActivityStreamList.as_view(), name='execution_environment_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', ExecutionEnvironmentActivityStreamList.as_view(), name='execution_environment_activity_stream_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
GroupList,
|
GroupList,
|
||||||
@@ -20,18 +20,18 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', GroupList.as_view(), name='group_list'),
|
re_path(r'^$', GroupList.as_view(), name='group_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', GroupDetail.as_view(), name='group_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', GroupDetail.as_view(), name='group_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/children/$', GroupChildrenList.as_view(), name='group_children_list'),
|
re_path(r'^(?P<pk>[0-9]+)/children/$', GroupChildrenList.as_view(), name='group_children_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/hosts/$', GroupHostsList.as_view(), name='group_hosts_list'),
|
re_path(r'^(?P<pk>[0-9]+)/hosts/$', GroupHostsList.as_view(), name='group_hosts_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/all_hosts/$', GroupAllHostsList.as_view(), name='group_all_hosts_list'),
|
re_path(r'^(?P<pk>[0-9]+)/all_hosts/$', GroupAllHostsList.as_view(), name='group_all_hosts_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', GroupVariableData.as_view(), name='group_variable_data'),
|
re_path(r'^(?P<pk>[0-9]+)/variable_data/$', GroupVariableData.as_view(), name='group_variable_data'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_events/$', GroupJobEventsList.as_view(), name='group_job_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_events/$', GroupJobEventsList.as_view(), name='group_job_events_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', GroupJobHostSummariesList.as_view(), name='group_job_host_summaries_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_host_summaries/$', GroupJobHostSummariesList.as_view(), name='group_job_host_summaries_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/potential_children/$', GroupPotentialChildrenList.as_view(), name='group_potential_children_list'),
|
re_path(r'^(?P<pk>[0-9]+)/potential_children/$', GroupPotentialChildrenList.as_view(), name='group_potential_children_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', GroupActivityStreamList.as_view(), name='group_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', GroupActivityStreamList.as_view(), name='group_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', GroupInventorySourcesList.as_view(), name='group_inventory_sources_list'),
|
re_path(r'^(?P<pk>[0-9]+)/inventory_sources/$', GroupInventorySourcesList.as_view(), name='group_inventory_sources_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', GroupAdHocCommandsList.as_view(), name='group_ad_hoc_commands_list'),
|
re_path(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', GroupAdHocCommandsList.as_view(), name='group_ad_hoc_commands_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
HostList,
|
HostList,
|
||||||
@@ -20,18 +20,18 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', HostList.as_view(), name='host_list'),
|
re_path(r'^$', HostList.as_view(), name='host_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', HostDetail.as_view(), name='host_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', HostDetail.as_view(), name='host_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', HostVariableData.as_view(), name='host_variable_data'),
|
re_path(r'^(?P<pk>[0-9]+)/variable_data/$', HostVariableData.as_view(), name='host_variable_data'),
|
||||||
url(r'^(?P<pk>[0-9]+)/groups/$', HostGroupsList.as_view(), name='host_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/groups/$', HostGroupsList.as_view(), name='host_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/all_groups/$', HostAllGroupsList.as_view(), name='host_all_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/all_groups/$', HostAllGroupsList.as_view(), name='host_all_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_events/', HostJobEventsList.as_view(), name='host_job_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_events/', HostJobEventsList.as_view(), name='host_job_events_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', HostJobHostSummariesList.as_view(), name='host_job_host_summaries_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_host_summaries/$', HostJobHostSummariesList.as_view(), name='host_job_host_summaries_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', HostActivityStreamList.as_view(), name='host_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', HostActivityStreamList.as_view(), name='host_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', HostInventorySourcesList.as_view(), name='host_inventory_sources_list'),
|
re_path(r'^(?P<pk>[0-9]+)/inventory_sources/$', HostInventorySourcesList.as_view(), name='host_inventory_sources_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/smart_inventories/$', HostSmartInventoriesList.as_view(), name='host_smart_inventories_list'),
|
re_path(r'^(?P<pk>[0-9]+)/smart_inventories/$', HostSmartInventoriesList.as_view(), name='host_smart_inventories_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', HostAdHocCommandsList.as_view(), name='host_ad_hoc_commands_list'),
|
re_path(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', HostAdHocCommandsList.as_view(), name='host_ad_hoc_commands_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_command_events/$', HostAdHocCommandEventsList.as_view(), name='host_ad_hoc_command_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/ad_hoc_command_events/$', HostAdHocCommandEventsList.as_view(), name='host_ad_hoc_command_events_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import InstanceList, InstanceDetail, InstanceUnifiedJobsList, InstanceInstanceGroupsList
|
from awx.api.views import InstanceList, InstanceDetail, InstanceUnifiedJobsList, InstanceInstanceGroupsList, InstanceHealthCheck
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', InstanceList.as_view(), name='instance_list'),
|
re_path(r'^$', InstanceList.as_view(), name='instance_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', InstanceDetail.as_view(), name='instance_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', InstanceDetail.as_view(), name='instance_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', InstanceUnifiedJobsList.as_view(), name='instance_unified_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', InstanceUnifiedJobsList.as_view(), name='instance_unified_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/instance_groups/$', InstanceInstanceGroupsList.as_view(), name='instance_instance_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', InstanceInstanceGroupsList.as_view(), name='instance_instance_groups_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/health_check/$', InstanceHealthCheck.as_view(), name='instance_health_check'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import InstanceGroupList, InstanceGroupDetail, InstanceGroupUnifiedJobsList, InstanceGroupInstanceList
|
from awx.api.views import InstanceGroupList, InstanceGroupDetail, InstanceGroupUnifiedJobsList, InstanceGroupInstanceList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', InstanceGroupList.as_view(), name='instance_group_list'),
|
re_path(r'^$', InstanceGroupList.as_view(), name='instance_group_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', InstanceGroupDetail.as_view(), name='instance_group_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', InstanceGroupDetail.as_view(), name='instance_group_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', InstanceGroupUnifiedJobsList.as_view(), name='instance_group_unified_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', InstanceGroupUnifiedJobsList.as_view(), name='instance_group_unified_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/instances/$', InstanceGroupInstanceList.as_view(), name='instance_group_instance_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instances/$', InstanceGroupInstanceList.as_view(), name='instance_group_instance_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
InventoryList,
|
InventoryList,
|
||||||
@@ -20,28 +20,30 @@ from awx.api.views import (
|
|||||||
InventoryAccessList,
|
InventoryAccessList,
|
||||||
InventoryObjectRolesList,
|
InventoryObjectRolesList,
|
||||||
InventoryInstanceGroupsList,
|
InventoryInstanceGroupsList,
|
||||||
|
InventoryLabelList,
|
||||||
InventoryCopy,
|
InventoryCopy,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', InventoryList.as_view(), name='inventory_list'),
|
re_path(r'^$', InventoryList.as_view(), name='inventory_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', InventoryDetail.as_view(), name='inventory_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', InventoryDetail.as_view(), name='inventory_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/hosts/$', InventoryHostsList.as_view(), name='inventory_hosts_list'),
|
re_path(r'^(?P<pk>[0-9]+)/hosts/$', InventoryHostsList.as_view(), name='inventory_hosts_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/groups/$', InventoryGroupsList.as_view(), name='inventory_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/groups/$', InventoryGroupsList.as_view(), name='inventory_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/root_groups/$', InventoryRootGroupsList.as_view(), name='inventory_root_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/root_groups/$', InventoryRootGroupsList.as_view(), name='inventory_root_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', InventoryVariableData.as_view(), name='inventory_variable_data'),
|
re_path(r'^(?P<pk>[0-9]+)/variable_data/$', InventoryVariableData.as_view(), name='inventory_variable_data'),
|
||||||
url(r'^(?P<pk>[0-9]+)/script/$', InventoryScriptView.as_view(), name='inventory_script_view'),
|
re_path(r'^(?P<pk>[0-9]+)/script/$', InventoryScriptView.as_view(), name='inventory_script_view'),
|
||||||
url(r'^(?P<pk>[0-9]+)/tree/$', InventoryTreeView.as_view(), name='inventory_tree_view'),
|
re_path(r'^(?P<pk>[0-9]+)/tree/$', InventoryTreeView.as_view(), name='inventory_tree_view'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', InventoryInventorySourcesList.as_view(), name='inventory_inventory_sources_list'),
|
re_path(r'^(?P<pk>[0-9]+)/inventory_sources/$', InventoryInventorySourcesList.as_view(), name='inventory_inventory_sources_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/update_inventory_sources/$', InventoryInventorySourcesUpdate.as_view(), name='inventory_inventory_sources_update'),
|
re_path(r'^(?P<pk>[0-9]+)/update_inventory_sources/$', InventoryInventorySourcesUpdate.as_view(), name='inventory_inventory_sources_update'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', InventoryActivityStreamList.as_view(), name='inventory_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', InventoryActivityStreamList.as_view(), name='inventory_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_templates/$', InventoryJobTemplateList.as_view(), name='inventory_job_template_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_templates/$', InventoryJobTemplateList.as_view(), name='inventory_job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', InventoryAdHocCommandsList.as_view(), name='inventory_ad_hoc_commands_list'),
|
re_path(r'^(?P<pk>[0-9]+)/ad_hoc_commands/$', InventoryAdHocCommandsList.as_view(), name='inventory_ad_hoc_commands_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', InventoryAccessList.as_view(), name='inventory_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', InventoryAccessList.as_view(), name='inventory_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', InventoryObjectRolesList.as_view(), name='inventory_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', InventoryObjectRolesList.as_view(), name='inventory_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/instance_groups/$', InventoryInstanceGroupsList.as_view(), name='inventory_instance_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', InventoryInstanceGroupsList.as_view(), name='inventory_instance_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', InventoryCopy.as_view(), name='inventory_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', InventoryLabelList.as_view(), name='inventory_label_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', InventoryCopy.as_view(), name='inventory_copy'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
InventorySourceList,
|
InventorySourceList,
|
||||||
@@ -20,26 +20,26 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', InventorySourceList.as_view(), name='inventory_source_list'),
|
re_path(r'^$', InventorySourceList.as_view(), name='inventory_source_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', InventorySourceDetail.as_view(), name='inventory_source_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', InventorySourceDetail.as_view(), name='inventory_source_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/update/$', InventorySourceUpdateView.as_view(), name='inventory_source_update_view'),
|
re_path(r'^(?P<pk>[0-9]+)/update/$', InventorySourceUpdateView.as_view(), name='inventory_source_update_view'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventory_updates/$', InventorySourceUpdatesList.as_view(), name='inventory_source_updates_list'),
|
re_path(r'^(?P<pk>[0-9]+)/inventory_updates/$', InventorySourceUpdatesList.as_view(), name='inventory_source_updates_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', InventorySourceActivityStreamList.as_view(), name='inventory_source_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', InventorySourceActivityStreamList.as_view(), name='inventory_source_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', InventorySourceSchedulesList.as_view(), name='inventory_source_schedules_list'),
|
re_path(r'^(?P<pk>[0-9]+)/schedules/$', InventorySourceSchedulesList.as_view(), name='inventory_source_schedules_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', InventorySourceCredentialsList.as_view(), name='inventory_source_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', InventorySourceCredentialsList.as_view(), name='inventory_source_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/groups/$', InventorySourceGroupsList.as_view(), name='inventory_source_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/groups/$', InventorySourceGroupsList.as_view(), name='inventory_source_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/hosts/$', InventorySourceHostsList.as_view(), name='inventory_source_hosts_list'),
|
re_path(r'^(?P<pk>[0-9]+)/hosts/$', InventorySourceHostsList.as_view(), name='inventory_source_hosts_list'),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
InventorySourceNotificationTemplatesStartedList.as_view(),
|
InventorySourceNotificationTemplatesStartedList.as_view(),
|
||||||
name='inventory_source_notification_templates_started_list',
|
name='inventory_source_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
||||||
InventorySourceNotificationTemplatesErrorList.as_view(),
|
InventorySourceNotificationTemplatesErrorList.as_view(),
|
||||||
name='inventory_source_notification_templates_error_list',
|
name='inventory_source_notification_templates_error_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
InventorySourceNotificationTemplatesSuccessList.as_view(),
|
InventorySourceNotificationTemplatesSuccessList.as_view(),
|
||||||
name='inventory_source_notification_templates_success_list',
|
name='inventory_source_notification_templates_success_list',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
InventoryUpdateList,
|
InventoryUpdateList,
|
||||||
@@ -15,13 +15,13 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', InventoryUpdateList.as_view(), name='inventory_update_list'),
|
re_path(r'^$', InventoryUpdateList.as_view(), name='inventory_update_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', InventoryUpdateDetail.as_view(), name='inventory_update_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', InventoryUpdateDetail.as_view(), name='inventory_update_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', InventoryUpdateCancel.as_view(), name='inventory_update_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', InventoryUpdateCancel.as_view(), name='inventory_update_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/stdout/$', InventoryUpdateStdout.as_view(), name='inventory_update_stdout'),
|
re_path(r'^(?P<pk>[0-9]+)/stdout/$', InventoryUpdateStdout.as_view(), name='inventory_update_stdout'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', InventoryUpdateNotificationsList.as_view(), name='inventory_update_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', InventoryUpdateNotificationsList.as_view(), name='inventory_update_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', InventoryUpdateCredentialsList.as_view(), name='inventory_update_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', InventoryUpdateCredentialsList.as_view(), name='inventory_update_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/events/$', InventoryUpdateEventsList.as_view(), name='inventory_update_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/events/$', InventoryUpdateEventsList.as_view(), name='inventory_update_events_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
JobList,
|
JobList,
|
||||||
@@ -10,6 +10,7 @@ from awx.api.views import (
|
|||||||
JobRelaunch,
|
JobRelaunch,
|
||||||
JobCreateSchedule,
|
JobCreateSchedule,
|
||||||
JobJobHostSummariesList,
|
JobJobHostSummariesList,
|
||||||
|
JobJobEventsChildrenSummary,
|
||||||
JobJobEventsList,
|
JobJobEventsList,
|
||||||
JobActivityStreamList,
|
JobActivityStreamList,
|
||||||
JobStdout,
|
JobStdout,
|
||||||
@@ -20,18 +21,19 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', JobList.as_view(), name='job_list'),
|
re_path(r'^$', JobList.as_view(), name='job_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', JobDetail.as_view(), name='job_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', JobDetail.as_view(), name='job_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', JobCancel.as_view(), name='job_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', JobCancel.as_view(), name='job_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/relaunch/$', JobRelaunch.as_view(), name='job_relaunch'),
|
re_path(r'^(?P<pk>[0-9]+)/relaunch/$', JobRelaunch.as_view(), name='job_relaunch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/create_schedule/$', JobCreateSchedule.as_view(), name='job_create_schedule'),
|
re_path(r'^(?P<pk>[0-9]+)/create_schedule/$', JobCreateSchedule.as_view(), name='job_create_schedule'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', JobJobHostSummariesList.as_view(), name='job_job_host_summaries_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_host_summaries/$', JobJobHostSummariesList.as_view(), name='job_job_host_summaries_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_events/$', JobJobEventsList.as_view(), name='job_job_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_events/$', JobJobEventsList.as_view(), name='job_job_events_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', JobActivityStreamList.as_view(), name='job_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_events/children_summary/$', JobJobEventsChildrenSummary.as_view(), name='job_job_events_children_summary'),
|
||||||
url(r'^(?P<pk>[0-9]+)/stdout/$', JobStdout.as_view(), name='job_stdout'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', JobActivityStreamList.as_view(), name='job_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', JobNotificationsList.as_view(), name='job_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/stdout/$', JobStdout.as_view(), name='job_stdout'),
|
||||||
url(r'^(?P<pk>[0-9]+)/labels/$', JobLabelList.as_view(), name='job_label_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', JobNotificationsList.as_view(), name='job_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', JobHostSummaryDetail.as_view(), name='job_host_summary_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', JobLabelList.as_view(), name='job_label_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/$', JobHostSummaryDetail.as_view(), name='job_host_summary_detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import JobEventDetail, JobEventChildrenList
|
from awx.api.views import JobEventDetail, JobEventChildrenList
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^(?P<pk>[0-9]+)/$', JobEventDetail.as_view(), name='job_event_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', JobEventDetail.as_view(), name='job_event_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/children/$', JobEventChildrenList.as_view(), name='job_event_children_list'),
|
re_path(r'^(?P<pk>[0-9]+)/children/$', JobEventChildrenList.as_view(), name='job_event_children_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import JobHostSummaryDetail
|
from awx.api.views import JobHostSummaryDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [url(r'^(?P<pk>[0-9]+)/$', JobHostSummaryDetail.as_view(), name='job_host_summary_detail')]
|
urls = [re_path(r'^(?P<pk>[0-9]+)/$', JobHostSummaryDetail.as_view(), name='job_host_summary_detail')]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
JobTemplateList,
|
JobTemplateList,
|
||||||
@@ -25,36 +25,36 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', JobTemplateList.as_view(), name='job_template_list'),
|
re_path(r'^$', JobTemplateList.as_view(), name='job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', JobTemplateDetail.as_view(), name='job_template_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', JobTemplateDetail.as_view(), name='job_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/launch/$', JobTemplateLaunch.as_view(), name='job_template_launch'),
|
re_path(r'^(?P<pk>[0-9]+)/launch/$', JobTemplateLaunch.as_view(), name='job_template_launch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', JobTemplateJobsList.as_view(), name='job_template_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', JobTemplateJobsList.as_view(), name='job_template_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/slice_workflow_jobs/$', JobTemplateSliceWorkflowJobsList.as_view(), name='job_template_slice_workflow_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/slice_workflow_jobs/$', JobTemplateSliceWorkflowJobsList.as_view(), name='job_template_slice_workflow_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/callback/$', JobTemplateCallback.as_view(), name='job_template_callback'),
|
re_path(r'^(?P<pk>[0-9]+)/callback/$', JobTemplateCallback.as_view(), name='job_template_callback'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', JobTemplateSchedulesList.as_view(), name='job_template_schedules_list'),
|
re_path(r'^(?P<pk>[0-9]+)/schedules/$', JobTemplateSchedulesList.as_view(), name='job_template_schedules_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/survey_spec/$', JobTemplateSurveySpec.as_view(), name='job_template_survey_spec'),
|
re_path(r'^(?P<pk>[0-9]+)/survey_spec/$', JobTemplateSurveySpec.as_view(), name='job_template_survey_spec'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', JobTemplateActivityStreamList.as_view(), name='job_template_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', JobTemplateActivityStreamList.as_view(), name='job_template_activity_stream_list'),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
JobTemplateNotificationTemplatesStartedList.as_view(),
|
JobTemplateNotificationTemplatesStartedList.as_view(),
|
||||||
name='job_template_notification_templates_started_list',
|
name='job_template_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
||||||
JobTemplateNotificationTemplatesErrorList.as_view(),
|
JobTemplateNotificationTemplatesErrorList.as_view(),
|
||||||
name='job_template_notification_templates_error_list',
|
name='job_template_notification_templates_error_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
JobTemplateNotificationTemplatesSuccessList.as_view(),
|
JobTemplateNotificationTemplatesSuccessList.as_view(),
|
||||||
name='job_template_notification_templates_success_list',
|
name='job_template_notification_templates_success_list',
|
||||||
),
|
),
|
||||||
url(r'^(?P<pk>[0-9]+)/instance_groups/$', JobTemplateInstanceGroupsList.as_view(), name='job_template_instance_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', JobTemplateInstanceGroupsList.as_view(), name='job_template_instance_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', JobTemplateAccessList.as_view(), name='job_template_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', JobTemplateAccessList.as_view(), name='job_template_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', JobTemplateObjectRolesList.as_view(), name='job_template_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', JobTemplateObjectRolesList.as_view(), name='job_template_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/labels/$', JobTemplateLabelList.as_view(), name='job_template_label_list'),
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', JobTemplateLabelList.as_view(), name='job_template_label_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', JobTemplateCopy.as_view(), name='job_template_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', JobTemplateCopy.as_view(), name='job_template_copy'),
|
||||||
url(r'^(?P<pk>[0-9]+)/', include('awx.api.urls.webhooks'), {'model_kwarg': 'job_templates'}),
|
re_path(r'^(?P<pk>[0-9]+)/', include('awx.api.urls.webhooks'), {'model_kwarg': 'job_templates'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import LabelList, LabelDetail
|
from awx.api.views import LabelList, LabelDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [url(r'^$', LabelList.as_view(), name='label_list'), url(r'^(?P<pk>[0-9]+)/$', LabelDetail.as_view(), name='label_detail')]
|
urls = [re_path(r'^$', LabelList.as_view(), name='label_list'), re_path(r'^(?P<pk>[0-9]+)/$', LabelDetail.as_view(), name='label_detail')]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import NotificationList, NotificationDetail
|
from awx.api.views import NotificationList, NotificationDetail
|
||||||
|
|
||||||
|
|
||||||
urls = [url(r'^$', NotificationList.as_view(), name='notification_list'), url(r'^(?P<pk>[0-9]+)/$', NotificationDetail.as_view(), name='notification_detail')]
|
urls = [
|
||||||
|
re_path(r'^$', NotificationList.as_view(), name='notification_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/$', NotificationDetail.as_view(), name='notification_detail'),
|
||||||
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
NotificationTemplateList,
|
NotificationTemplateList,
|
||||||
@@ -13,11 +13,11 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', NotificationTemplateList.as_view(), name='notification_template_list'),
|
re_path(r'^$', NotificationTemplateList.as_view(), name='notification_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', NotificationTemplateDetail.as_view(), name='notification_template_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', NotificationTemplateDetail.as_view(), name='notification_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/test/$', NotificationTemplateTest.as_view(), name='notification_template_test'),
|
re_path(r'^(?P<pk>[0-9]+)/test/$', NotificationTemplateTest.as_view(), name='notification_template_test'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', NotificationTemplateNotificationList.as_view(), name='notification_template_notification_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', NotificationTemplateNotificationList.as_view(), name='notification_template_notification_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', NotificationTemplateCopy.as_view(), name='notification_template_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', NotificationTemplateCopy.as_view(), name='notification_template_copy'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
OAuth2ApplicationList,
|
OAuth2ApplicationList,
|
||||||
@@ -15,13 +15,13 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
re_path(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
||||||
url(r'^applications/(?P<pk>[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'),
|
re_path(r'^applications/(?P<pk>[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'),
|
||||||
url(r'^applications/(?P<pk>[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='o_auth2_application_token_list'),
|
re_path(r'^applications/(?P<pk>[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='o_auth2_application_token_list'),
|
||||||
url(r'^applications/(?P<pk>[0-9]+)/activity_stream/$', OAuth2ApplicationActivityStreamList.as_view(), name='o_auth2_application_activity_stream_list'),
|
re_path(r'^applications/(?P<pk>[0-9]+)/activity_stream/$', OAuth2ApplicationActivityStreamList.as_view(), name='o_auth2_application_activity_stream_list'),
|
||||||
url(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
|
re_path(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
|
||||||
url(r'^tokens/(?P<pk>[0-9]+)/$', OAuth2TokenDetail.as_view(), name='o_auth2_token_detail'),
|
re_path(r'^tokens/(?P<pk>[0-9]+)/$', OAuth2TokenDetail.as_view(), name='o_auth2_token_detail'),
|
||||||
url(r'^tokens/(?P<pk>[0-9]+)/activity_stream/$', OAuth2TokenActivityStreamList.as_view(), name='o_auth2_token_activity_stream_list'),
|
re_path(r'^tokens/(?P<pk>[0-9]+)/activity_stream/$', OAuth2TokenActivityStreamList.as_view(), name='o_auth2_token_activity_stream_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from oauthlib import oauth2
|
from oauthlib import oauth2
|
||||||
from oauth2_provider import views
|
from oauth2_provider import views
|
||||||
@@ -35,10 +35,10 @@ class TokenView(views.TokenView):
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ApiOAuthAuthorizationRootView.as_view(), name='oauth_authorization_root_view'),
|
re_path(r'^$', ApiOAuthAuthorizationRootView.as_view(), name='oauth_authorization_root_view'),
|
||||||
url(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"),
|
re_path(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"),
|
||||||
url(r"^token/$", TokenView.as_view(), name="token"),
|
re_path(r"^token/$", TokenView.as_view(), name="token"),
|
||||||
url(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"),
|
re_path(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
OrganizationList,
|
OrganizationList,
|
||||||
@@ -30,44 +30,44 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', OrganizationList.as_view(), name='organization_list'),
|
re_path(r'^$', OrganizationList.as_view(), name='organization_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', OrganizationDetail.as_view(), name='organization_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', OrganizationDetail.as_view(), name='organization_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/users/$', OrganizationUsersList.as_view(), name='organization_users_list'),
|
re_path(r'^(?P<pk>[0-9]+)/users/$', OrganizationUsersList.as_view(), name='organization_users_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/admins/$', OrganizationAdminsList.as_view(), name='organization_admins_list'),
|
re_path(r'^(?P<pk>[0-9]+)/admins/$', OrganizationAdminsList.as_view(), name='organization_admins_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventories/$', OrganizationInventoriesList.as_view(), name='organization_inventories_list'),
|
re_path(r'^(?P<pk>[0-9]+)/inventories/$', OrganizationInventoriesList.as_view(), name='organization_inventories_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/execution_environments/$', OrganizationExecutionEnvironmentsList.as_view(), name='organization_execution_environments_list'),
|
re_path(r'^(?P<pk>[0-9]+)/execution_environments/$', OrganizationExecutionEnvironmentsList.as_view(), name='organization_execution_environments_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/projects/$', OrganizationProjectsList.as_view(), name='organization_projects_list'),
|
re_path(r'^(?P<pk>[0-9]+)/projects/$', OrganizationProjectsList.as_view(), name='organization_projects_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/job_templates/$', OrganizationJobTemplatesList.as_view(), name='organization_job_templates_list'),
|
re_path(r'^(?P<pk>[0-9]+)/job_templates/$', OrganizationJobTemplatesList.as_view(), name='organization_job_templates_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/workflow_job_templates/$', OrganizationWorkflowJobTemplatesList.as_view(), name='organization_workflow_job_templates_list'),
|
re_path(r'^(?P<pk>[0-9]+)/workflow_job_templates/$', OrganizationWorkflowJobTemplatesList.as_view(), name='organization_workflow_job_templates_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/teams/$', OrganizationTeamsList.as_view(), name='organization_teams_list'),
|
re_path(r'^(?P<pk>[0-9]+)/teams/$', OrganizationTeamsList.as_view(), name='organization_teams_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', OrganizationCredentialList.as_view(), name='organization_credential_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', OrganizationCredentialList.as_view(), name='organization_credential_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', OrganizationActivityStreamList.as_view(), name='organization_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', OrganizationActivityStreamList.as_view(), name='organization_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notification_templates/$', OrganizationNotificationTemplatesList.as_view(), name='organization_notification_templates_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notification_templates/$', OrganizationNotificationTemplatesList.as_view(), name='organization_notification_templates_list'),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
OrganizationNotificationTemplatesStartedList.as_view(),
|
OrganizationNotificationTemplatesStartedList.as_view(),
|
||||||
name='organization_notification_templates_started_list',
|
name='organization_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
||||||
OrganizationNotificationTemplatesErrorList.as_view(),
|
OrganizationNotificationTemplatesErrorList.as_view(),
|
||||||
name='organization_notification_templates_error_list',
|
name='organization_notification_templates_error_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
OrganizationNotificationTemplatesSuccessList.as_view(),
|
OrganizationNotificationTemplatesSuccessList.as_view(),
|
||||||
name='organization_notification_templates_success_list',
|
name='organization_notification_templates_success_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_approvals/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_approvals/$',
|
||||||
OrganizationNotificationTemplatesApprovalList.as_view(),
|
OrganizationNotificationTemplatesApprovalList.as_view(),
|
||||||
name='organization_notification_templates_approvals_list',
|
name='organization_notification_templates_approvals_list',
|
||||||
),
|
),
|
||||||
url(r'^(?P<pk>[0-9]+)/instance_groups/$', OrganizationInstanceGroupsList.as_view(), name='organization_instance_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', OrganizationInstanceGroupsList.as_view(), name='organization_instance_groups_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/galaxy_credentials/$', OrganizationGalaxyCredentialsList.as_view(), name='organization_galaxy_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/galaxy_credentials/$', OrganizationGalaxyCredentialsList.as_view(), name='organization_galaxy_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', OrganizationObjectRolesList.as_view(), name='organization_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', OrganizationObjectRolesList.as_view(), name='organization_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', OrganizationAccessList.as_view(), name='organization_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', OrganizationAccessList.as_view(), name='organization_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/applications/$', OrganizationApplicationList.as_view(), name='organization_applications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/applications/$', OrganizationApplicationList.as_view(), name='organization_applications_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
ProjectList,
|
ProjectList,
|
||||||
@@ -24,30 +24,32 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ProjectList.as_view(), name='project_list'),
|
re_path(r'^$', ProjectList.as_view(), name='project_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', ProjectDetail.as_view(), name='project_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ProjectDetail.as_view(), name='project_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/playbooks/$', ProjectPlaybooks.as_view(), name='project_playbooks'),
|
re_path(r'^(?P<pk>[0-9]+)/playbooks/$', ProjectPlaybooks.as_view(), name='project_playbooks'),
|
||||||
url(r'^(?P<pk>[0-9]+)/inventories/$', ProjectInventories.as_view(), name='project_inventories'),
|
re_path(r'^(?P<pk>[0-9]+)/inventories/$', ProjectInventories.as_view(), name='project_inventories'),
|
||||||
url(r'^(?P<pk>[0-9]+)/scm_inventory_sources/$', ProjectScmInventorySources.as_view(), name='project_scm_inventory_sources'),
|
re_path(r'^(?P<pk>[0-9]+)/scm_inventory_sources/$', ProjectScmInventorySources.as_view(), name='project_scm_inventory_sources'),
|
||||||
url(r'^(?P<pk>[0-9]+)/teams/$', ProjectTeamsList.as_view(), name='project_teams_list'),
|
re_path(r'^(?P<pk>[0-9]+)/teams/$', ProjectTeamsList.as_view(), name='project_teams_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/update/$', ProjectUpdateView.as_view(), name='project_update_view'),
|
re_path(r'^(?P<pk>[0-9]+)/update/$', ProjectUpdateView.as_view(), name='project_update_view'),
|
||||||
url(r'^(?P<pk>[0-9]+)/project_updates/$', ProjectUpdatesList.as_view(), name='project_updates_list'),
|
re_path(r'^(?P<pk>[0-9]+)/project_updates/$', ProjectUpdatesList.as_view(), name='project_updates_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', ProjectActivityStreamList.as_view(), name='project_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', ProjectActivityStreamList.as_view(), name='project_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', ProjectSchedulesList.as_view(), name='project_schedules_list'),
|
re_path(r'^(?P<pk>[0-9]+)/schedules/$', ProjectSchedulesList.as_view(), name='project_schedules_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', ProjectNotificationTemplatesErrorList.as_view(), name='project_notification_templates_error_list'),
|
re_path(
|
||||||
url(
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$', ProjectNotificationTemplatesErrorList.as_view(), name='project_notification_templates_error_list'
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
ProjectNotificationTemplatesSuccessList.as_view(),
|
ProjectNotificationTemplatesSuccessList.as_view(),
|
||||||
name='project_notification_templates_success_list',
|
name='project_notification_templates_success_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
ProjectNotificationTemplatesStartedList.as_view(),
|
ProjectNotificationTemplatesStartedList.as_view(),
|
||||||
name='project_notification_templates_started_list',
|
name='project_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', ProjectObjectRolesList.as_view(), name='project_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', ProjectObjectRolesList.as_view(), name='project_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', ProjectAccessList.as_view(), name='project_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', ProjectAccessList.as_view(), name='project_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', ProjectCopy.as_view(), name='project_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', ProjectCopy.as_view(), name='project_copy'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
ProjectUpdateList,
|
ProjectUpdateList,
|
||||||
@@ -15,13 +15,13 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ProjectUpdateList.as_view(), name='project_update_list'),
|
re_path(r'^$', ProjectUpdateList.as_view(), name='project_update_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', ProjectUpdateDetail.as_view(), name='project_update_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ProjectUpdateDetail.as_view(), name='project_update_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', ProjectUpdateCancel.as_view(), name='project_update_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', ProjectUpdateCancel.as_view(), name='project_update_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/stdout/$', ProjectUpdateStdout.as_view(), name='project_update_stdout'),
|
re_path(r'^(?P<pk>[0-9]+)/stdout/$', ProjectUpdateStdout.as_view(), name='project_update_stdout'),
|
||||||
url(r'^(?P<pk>[0-9]+)/scm_inventory_updates/$', ProjectUpdateScmInventoryUpdates.as_view(), name='project_update_scm_inventory_updates'),
|
re_path(r'^(?P<pk>[0-9]+)/scm_inventory_updates/$', ProjectUpdateScmInventoryUpdates.as_view(), name='project_update_scm_inventory_updates'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', ProjectUpdateNotificationsList.as_view(), name='project_update_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', ProjectUpdateNotificationsList.as_view(), name='project_update_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/events/$', ProjectUpdateEventsList.as_view(), name='project_update_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/events/$', ProjectUpdateEventsList.as_view(), name='project_update_events_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import RoleList, RoleDetail, RoleUsersList, RoleTeamsList, RoleParentsList, RoleChildrenList
|
from awx.api.views import RoleList, RoleDetail, RoleUsersList, RoleTeamsList, RoleParentsList, RoleChildrenList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', RoleList.as_view(), name='role_list'),
|
re_path(r'^$', RoleList.as_view(), name='role_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', RoleDetail.as_view(), name='role_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', RoleDetail.as_view(), name='role_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/users/$', RoleUsersList.as_view(), name='role_users_list'),
|
re_path(r'^(?P<pk>[0-9]+)/users/$', RoleUsersList.as_view(), name='role_users_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/teams/$', RoleTeamsList.as_view(), name='role_teams_list'),
|
re_path(r'^(?P<pk>[0-9]+)/teams/$', RoleTeamsList.as_view(), name='role_teams_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/parents/$', RoleParentsList.as_view(), name='role_parents_list'),
|
re_path(r'^(?P<pk>[0-9]+)/parents/$', RoleParentsList.as_view(), name='role_parents_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/children/$', RoleChildrenList.as_view(), name='role_children_list'),
|
re_path(r'^(?P<pk>[0-9]+)/children/$', RoleChildrenList.as_view(), name='role_children_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import ScheduleList, ScheduleDetail, ScheduleUnifiedJobsList, ScheduleCredentialsList
|
from awx.api.views import ScheduleList, ScheduleDetail, ScheduleUnifiedJobsList, ScheduleCredentialsList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', ScheduleList.as_view(), name='schedule_list'),
|
re_path(r'^$', ScheduleList.as_view(), name='schedule_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', ScheduleDetail.as_view(), name='schedule_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ScheduleDetail.as_view(), name='schedule_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', ScheduleUnifiedJobsList.as_view(), name='schedule_unified_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', ScheduleUnifiedJobsList.as_view(), name='schedule_unified_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', ScheduleCredentialsList.as_view(), name='schedule_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', ScheduleCredentialsList.as_view(), name='schedule_credentials_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import SystemJobList, SystemJobDetail, SystemJobCancel, SystemJobNotificationsList, SystemJobEventsList
|
from awx.api.views import SystemJobList, SystemJobDetail, SystemJobCancel, SystemJobNotificationsList, SystemJobEventsList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', SystemJobList.as_view(), name='system_job_list'),
|
re_path(r'^$', SystemJobList.as_view(), name='system_job_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', SystemJobDetail.as_view(), name='system_job_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', SystemJobDetail.as_view(), name='system_job_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', SystemJobCancel.as_view(), name='system_job_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', SystemJobCancel.as_view(), name='system_job_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', SystemJobNotificationsList.as_view(), name='system_job_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', SystemJobNotificationsList.as_view(), name='system_job_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/events/$', SystemJobEventsList.as_view(), name='system_job_events_list'),
|
re_path(r'^(?P<pk>[0-9]+)/events/$', SystemJobEventsList.as_view(), name='system_job_events_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
SystemJobTemplateList,
|
SystemJobTemplateList,
|
||||||
@@ -16,22 +16,22 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', SystemJobTemplateList.as_view(), name='system_job_template_list'),
|
re_path(r'^$', SystemJobTemplateList.as_view(), name='system_job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', SystemJobTemplateDetail.as_view(), name='system_job_template_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', SystemJobTemplateDetail.as_view(), name='system_job_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/launch/$', SystemJobTemplateLaunch.as_view(), name='system_job_template_launch'),
|
re_path(r'^(?P<pk>[0-9]+)/launch/$', SystemJobTemplateLaunch.as_view(), name='system_job_template_launch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', SystemJobTemplateJobsList.as_view(), name='system_job_template_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', SystemJobTemplateJobsList.as_view(), name='system_job_template_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', SystemJobTemplateSchedulesList.as_view(), name='system_job_template_schedules_list'),
|
re_path(r'^(?P<pk>[0-9]+)/schedules/$', SystemJobTemplateSchedulesList.as_view(), name='system_job_template_schedules_list'),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
SystemJobTemplateNotificationTemplatesStartedList.as_view(),
|
SystemJobTemplateNotificationTemplatesStartedList.as_view(),
|
||||||
name='system_job_template_notification_templates_started_list',
|
name='system_job_template_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
||||||
SystemJobTemplateNotificationTemplatesErrorList.as_view(),
|
SystemJobTemplateNotificationTemplatesErrorList.as_view(),
|
||||||
name='system_job_template_notification_templates_error_list',
|
name='system_job_template_notification_templates_error_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
SystemJobTemplateNotificationTemplatesSuccessList.as_view(),
|
SystemJobTemplateNotificationTemplatesSuccessList.as_view(),
|
||||||
name='system_job_template_notification_templates_success_list',
|
name='system_job_template_notification_templates_success_list',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
TeamList,
|
TeamList,
|
||||||
@@ -17,15 +17,15 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', TeamList.as_view(), name='team_list'),
|
re_path(r'^$', TeamList.as_view(), name='team_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', TeamDetail.as_view(), name='team_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', TeamDetail.as_view(), name='team_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/projects/$', TeamProjectsList.as_view(), name='team_projects_list'),
|
re_path(r'^(?P<pk>[0-9]+)/projects/$', TeamProjectsList.as_view(), name='team_projects_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/users/$', TeamUsersList.as_view(), name='team_users_list'),
|
re_path(r'^(?P<pk>[0-9]+)/users/$', TeamUsersList.as_view(), name='team_users_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', TeamCredentialsList.as_view(), name='team_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', TeamCredentialsList.as_view(), name='team_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/roles/$', TeamRolesList.as_view(), name='team_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/roles/$', TeamRolesList.as_view(), name='team_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', TeamObjectRolesList.as_view(), name='team_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', TeamObjectRolesList.as_view(), name='team_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', TeamActivityStreamList.as_view(), name='team_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', TeamActivityStreamList.as_view(), name='team_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', TeamAccessList.as_view(), name='team_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', TeamAccessList.as_view(), name='team_access_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, re_path
|
||||||
|
|
||||||
from awx.api.generics import LoggedLoginView, LoggedLogoutView
|
from awx.api.generics import LoggedLoginView, LoggedLogoutView
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
@@ -28,6 +28,7 @@ from awx.api.views import (
|
|||||||
OAuth2TokenList,
|
OAuth2TokenList,
|
||||||
ApplicationOAuth2TokenList,
|
ApplicationOAuth2TokenList,
|
||||||
OAuth2ApplicationDetail,
|
OAuth2ApplicationDetail,
|
||||||
|
MeshVisualizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from awx.api.views.metrics import MetricsView
|
from awx.api.views.metrics import MetricsView
|
||||||
@@ -73,77 +74,78 @@ from .workflow_approval import urls as workflow_approval_urls
|
|||||||
|
|
||||||
|
|
||||||
v2_urls = [
|
v2_urls = [
|
||||||
url(r'^$', ApiV2RootView.as_view(), name='api_v2_root_view'),
|
re_path(r'^$', ApiV2RootView.as_view(), name='api_v2_root_view'),
|
||||||
url(r'^credential_types/', include(credential_type_urls)),
|
re_path(r'^credential_types/', include(credential_type_urls)),
|
||||||
url(r'^credential_input_sources/', include(credential_input_source_urls)),
|
re_path(r'^credential_input_sources/', include(credential_input_source_urls)),
|
||||||
url(r'^hosts/(?P<pk>[0-9]+)/ansible_facts/$', HostAnsibleFactsDetail.as_view(), name='host_ansible_facts_detail'),
|
re_path(r'^hosts/(?P<pk>[0-9]+)/ansible_facts/$', HostAnsibleFactsDetail.as_view(), name='host_ansible_facts_detail'),
|
||||||
url(r'^jobs/(?P<pk>[0-9]+)/credentials/$', JobCredentialsList.as_view(), name='job_credentials_list'),
|
re_path(r'^jobs/(?P<pk>[0-9]+)/credentials/$', JobCredentialsList.as_view(), name='job_credentials_list'),
|
||||||
url(r'^job_templates/(?P<pk>[0-9]+)/credentials/$', JobTemplateCredentialsList.as_view(), name='job_template_credentials_list'),
|
re_path(r'^job_templates/(?P<pk>[0-9]+)/credentials/$', JobTemplateCredentialsList.as_view(), name='job_template_credentials_list'),
|
||||||
url(r'^schedules/preview/$', SchedulePreview.as_view(), name='schedule_rrule'),
|
re_path(r'^schedules/preview/$', SchedulePreview.as_view(), name='schedule_rrule'),
|
||||||
url(r'^schedules/zoneinfo/$', ScheduleZoneInfo.as_view(), name='schedule_zoneinfo'),
|
re_path(r'^schedules/zoneinfo/$', ScheduleZoneInfo.as_view(), name='schedule_zoneinfo'),
|
||||||
url(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
re_path(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
||||||
url(r'^applications/(?P<pk>[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'),
|
re_path(r'^applications/(?P<pk>[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'),
|
||||||
url(r'^applications/(?P<pk>[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='application_o_auth2_token_list'),
|
re_path(r'^applications/(?P<pk>[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='application_o_auth2_token_list'),
|
||||||
url(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
|
re_path(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
|
||||||
url(r'^', include(oauth2_urls)),
|
re_path(r'^', include(oauth2_urls)),
|
||||||
url(r'^metrics/$', MetricsView.as_view(), name='metrics_view'),
|
re_path(r'^metrics/$', MetricsView.as_view(), name='metrics_view'),
|
||||||
url(r'^ping/$', ApiV2PingView.as_view(), name='api_v2_ping_view'),
|
re_path(r'^ping/$', ApiV2PingView.as_view(), name='api_v2_ping_view'),
|
||||||
url(r'^config/$', ApiV2ConfigView.as_view(), name='api_v2_config_view'),
|
re_path(r'^config/$', ApiV2ConfigView.as_view(), name='api_v2_config_view'),
|
||||||
url(r'^config/subscriptions/$', ApiV2SubscriptionView.as_view(), name='api_v2_subscription_view'),
|
re_path(r'^config/subscriptions/$', ApiV2SubscriptionView.as_view(), name='api_v2_subscription_view'),
|
||||||
url(r'^config/attach/$', ApiV2AttachView.as_view(), name='api_v2_attach_view'),
|
re_path(r'^config/attach/$', ApiV2AttachView.as_view(), name='api_v2_attach_view'),
|
||||||
url(r'^auth/$', AuthView.as_view()),
|
re_path(r'^auth/$', AuthView.as_view()),
|
||||||
url(r'^me/$', UserMeList.as_view(), name='user_me_list'),
|
re_path(r'^me/$', UserMeList.as_view(), name='user_me_list'),
|
||||||
url(r'^dashboard/$', DashboardView.as_view(), name='dashboard_view'),
|
re_path(r'^dashboard/$', DashboardView.as_view(), name='dashboard_view'),
|
||||||
url(r'^dashboard/graphs/jobs/$', DashboardJobsGraphView.as_view(), name='dashboard_jobs_graph_view'),
|
re_path(r'^dashboard/graphs/jobs/$', DashboardJobsGraphView.as_view(), name='dashboard_jobs_graph_view'),
|
||||||
url(r'^settings/', include('awx.conf.urls')),
|
re_path(r'^mesh_visualizer/', MeshVisualizer.as_view(), name='mesh_visualizer_view'),
|
||||||
url(r'^instances/', include(instance_urls)),
|
re_path(r'^settings/', include('awx.conf.urls')),
|
||||||
url(r'^instance_groups/', include(instance_group_urls)),
|
re_path(r'^instances/', include(instance_urls)),
|
||||||
url(r'^schedules/', include(schedule_urls)),
|
re_path(r'^instance_groups/', include(instance_group_urls)),
|
||||||
url(r'^organizations/', include(organization_urls)),
|
re_path(r'^schedules/', include(schedule_urls)),
|
||||||
url(r'^users/', include(user_urls)),
|
re_path(r'^organizations/', include(organization_urls)),
|
||||||
url(r'^execution_environments/', include(execution_environment_urls)),
|
re_path(r'^users/', include(user_urls)),
|
||||||
url(r'^projects/', include(project_urls)),
|
re_path(r'^execution_environments/', include(execution_environment_urls)),
|
||||||
url(r'^project_updates/', include(project_update_urls)),
|
re_path(r'^projects/', include(project_urls)),
|
||||||
url(r'^teams/', include(team_urls)),
|
re_path(r'^project_updates/', include(project_update_urls)),
|
||||||
url(r'^inventories/', include(inventory_urls)),
|
re_path(r'^teams/', include(team_urls)),
|
||||||
url(r'^hosts/', include(host_urls)),
|
re_path(r'^inventories/', include(inventory_urls)),
|
||||||
url(r'^groups/', include(group_urls)),
|
re_path(r'^hosts/', include(host_urls)),
|
||||||
url(r'^inventory_sources/', include(inventory_source_urls)),
|
re_path(r'^groups/', include(group_urls)),
|
||||||
url(r'^inventory_updates/', include(inventory_update_urls)),
|
re_path(r'^inventory_sources/', include(inventory_source_urls)),
|
||||||
url(r'^credentials/', include(credential_urls)),
|
re_path(r'^inventory_updates/', include(inventory_update_urls)),
|
||||||
url(r'^roles/', include(role_urls)),
|
re_path(r'^credentials/', include(credential_urls)),
|
||||||
url(r'^job_templates/', include(job_template_urls)),
|
re_path(r'^roles/', include(role_urls)),
|
||||||
url(r'^jobs/', include(job_urls)),
|
re_path(r'^job_templates/', include(job_template_urls)),
|
||||||
url(r'^job_host_summaries/', include(job_host_summary_urls)),
|
re_path(r'^jobs/', include(job_urls)),
|
||||||
url(r'^job_events/', include(job_event_urls)),
|
re_path(r'^job_host_summaries/', include(job_host_summary_urls)),
|
||||||
url(r'^ad_hoc_commands/', include(ad_hoc_command_urls)),
|
re_path(r'^job_events/', include(job_event_urls)),
|
||||||
url(r'^ad_hoc_command_events/', include(ad_hoc_command_event_urls)),
|
re_path(r'^ad_hoc_commands/', include(ad_hoc_command_urls)),
|
||||||
url(r'^system_job_templates/', include(system_job_template_urls)),
|
re_path(r'^ad_hoc_command_events/', include(ad_hoc_command_event_urls)),
|
||||||
url(r'^system_jobs/', include(system_job_urls)),
|
re_path(r'^system_job_templates/', include(system_job_template_urls)),
|
||||||
url(r'^notification_templates/', include(notification_template_urls)),
|
re_path(r'^system_jobs/', include(system_job_urls)),
|
||||||
url(r'^notifications/', include(notification_urls)),
|
re_path(r'^notification_templates/', include(notification_template_urls)),
|
||||||
url(r'^workflow_job_templates/', include(workflow_job_template_urls)),
|
re_path(r'^notifications/', include(notification_urls)),
|
||||||
url(r'^workflow_jobs/', include(workflow_job_urls)),
|
re_path(r'^workflow_job_templates/', include(workflow_job_template_urls)),
|
||||||
url(r'^labels/', include(label_urls)),
|
re_path(r'^workflow_jobs/', include(workflow_job_urls)),
|
||||||
url(r'^workflow_job_template_nodes/', include(workflow_job_template_node_urls)),
|
re_path(r'^labels/', include(label_urls)),
|
||||||
url(r'^workflow_job_nodes/', include(workflow_job_node_urls)),
|
re_path(r'^workflow_job_template_nodes/', include(workflow_job_template_node_urls)),
|
||||||
url(r'^unified_job_templates/$', UnifiedJobTemplateList.as_view(), name='unified_job_template_list'),
|
re_path(r'^workflow_job_nodes/', include(workflow_job_node_urls)),
|
||||||
url(r'^unified_jobs/$', UnifiedJobList.as_view(), name='unified_job_list'),
|
re_path(r'^unified_job_templates/$', UnifiedJobTemplateList.as_view(), name='unified_job_template_list'),
|
||||||
url(r'^activity_stream/', include(activity_stream_urls)),
|
re_path(r'^unified_jobs/$', UnifiedJobList.as_view(), name='unified_job_list'),
|
||||||
url(r'^workflow_approval_templates/', include(workflow_approval_template_urls)),
|
re_path(r'^activity_stream/', include(activity_stream_urls)),
|
||||||
url(r'^workflow_approvals/', include(workflow_approval_urls)),
|
re_path(r'^workflow_approval_templates/', include(workflow_approval_template_urls)),
|
||||||
|
re_path(r'^workflow_approvals/', include(workflow_approval_urls)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', ApiRootView.as_view(), name='api_root_view'),
|
re_path(r'^$', ApiRootView.as_view(), name='api_root_view'),
|
||||||
url(r'^(?P<version>(v2))/', include(v2_urls)),
|
re_path(r'^(?P<version>(v2))/', include(v2_urls)),
|
||||||
url(r'^login/$', LoggedLoginView.as_view(template_name='rest_framework/login.html', extra_context={'inside_login_context': True}), name='login'),
|
re_path(r'^login/$', LoggedLoginView.as_view(template_name='rest_framework/login.html', extra_context={'inside_login_context': True}), name='login'),
|
||||||
url(r'^logout/$', LoggedLogoutView.as_view(next_page='/api/', redirect_field_name='next'), name='logout'),
|
re_path(r'^logout/$', LoggedLogoutView.as_view(next_page='/api/', redirect_field_name='next'), name='logout'),
|
||||||
url(r'^o/', include(oauth2_root_urls)),
|
re_path(r'^o/', include(oauth2_root_urls)),
|
||||||
]
|
]
|
||||||
if settings.SETTINGS_MODULE == 'awx.settings.development':
|
if settings.SETTINGS_MODULE == 'awx.settings.development':
|
||||||
from awx.api.swagger import SwaggerSchemaView
|
from awx.api.swagger import SwaggerSchemaView
|
||||||
|
|
||||||
urlpatterns += [url(r'^swagger/$', SwaggerSchemaView.as_view(), name='swagger_view')]
|
urlpatterns += [re_path(r'^swagger/$', SwaggerSchemaView.as_view(), name='swagger_view')]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
UserList,
|
UserList,
|
||||||
@@ -21,20 +21,20 @@ from awx.api.views import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', UserList.as_view(), name='user_list'),
|
re_path(r'^$', UserList.as_view(), name='user_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', UserDetail.as_view(), name='user_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', UserDetail.as_view(), name='user_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/teams/$', UserTeamsList.as_view(), name='user_teams_list'),
|
re_path(r'^(?P<pk>[0-9]+)/teams/$', UserTeamsList.as_view(), name='user_teams_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/organizations/$', UserOrganizationsList.as_view(), name='user_organizations_list'),
|
re_path(r'^(?P<pk>[0-9]+)/organizations/$', UserOrganizationsList.as_view(), name='user_organizations_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/admin_of_organizations/$', UserAdminOfOrganizationsList.as_view(), name='user_admin_of_organizations_list'),
|
re_path(r'^(?P<pk>[0-9]+)/admin_of_organizations/$', UserAdminOfOrganizationsList.as_view(), name='user_admin_of_organizations_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/projects/$', UserProjectsList.as_view(), name='user_projects_list'),
|
re_path(r'^(?P<pk>[0-9]+)/projects/$', UserProjectsList.as_view(), name='user_projects_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', UserCredentialsList.as_view(), name='user_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', UserCredentialsList.as_view(), name='user_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/roles/$', UserRolesList.as_view(), name='user_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/roles/$', UserRolesList.as_view(), name='user_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', UserActivityStreamList.as_view(), name='user_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', UserActivityStreamList.as_view(), name='user_activity_stream_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', UserAccessList.as_view(), name='user_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', UserAccessList.as_view(), name='user_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
re_path(r'^(?P<pk>[0-9]+)/applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/tokens/$', OAuth2UserTokenList.as_view(), name='o_auth2_token_list'),
|
re_path(r'^(?P<pk>[0-9]+)/tokens/$', OAuth2UserTokenList.as_view(), name='o_auth2_token_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/authorized_tokens/$', UserAuthorizedTokenList.as_view(), name='user_authorized_token_list'),
|
re_path(r'^(?P<pk>[0-9]+)/authorized_tokens/$', UserAuthorizedTokenList.as_view(), name='user_authorized_token_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/personal_tokens/$', UserPersonalTokenList.as_view(), name='user_personal_token_list'),
|
re_path(r'^(?P<pk>[0-9]+)/personal_tokens/$', UserPersonalTokenList.as_view(), name='user_personal_token_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver
|
from awx.api.views import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^webhook_key/$', WebhookKeyView.as_view(), name='webhook_key'),
|
re_path(r'^webhook_key/$', WebhookKeyView.as_view(), name='webhook_key'),
|
||||||
url(r'^github/$', GithubWebhookReceiver.as_view(), name='webhook_receiver_github'),
|
re_path(r'^github/$', GithubWebhookReceiver.as_view(), name='webhook_receiver_github'),
|
||||||
url(r'^gitlab/$', GitlabWebhookReceiver.as_view(), name='webhook_receiver_gitlab'),
|
re_path(r'^gitlab/$', GitlabWebhookReceiver.as_view(), name='webhook_receiver_gitlab'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import WorkflowApprovalList, WorkflowApprovalDetail, WorkflowApprovalApprove, WorkflowApprovalDeny
|
from awx.api.views import WorkflowApprovalList, WorkflowApprovalDetail, WorkflowApprovalApprove, WorkflowApprovalDeny
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', WorkflowApprovalList.as_view(), name='workflow_approval_list'),
|
re_path(r'^$', WorkflowApprovalList.as_view(), name='workflow_approval_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalDetail.as_view(), name='workflow_approval_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalDetail.as_view(), name='workflow_approval_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/approve/$', WorkflowApprovalApprove.as_view(), name='workflow_approval_approve'),
|
re_path(r'^(?P<pk>[0-9]+)/approve/$', WorkflowApprovalApprove.as_view(), name='workflow_approval_approve'),
|
||||||
url(r'^(?P<pk>[0-9]+)/deny/$', WorkflowApprovalDeny.as_view(), name='workflow_approval_deny'),
|
re_path(r'^(?P<pk>[0-9]+)/deny/$', WorkflowApprovalDeny.as_view(), name='workflow_approval_deny'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import WorkflowApprovalTemplateDetail, WorkflowApprovalTemplateJobsList
|
from awx.api.views import WorkflowApprovalTemplateDetail, WorkflowApprovalTemplateJobsList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalTemplateDetail.as_view(), name='workflow_approval_template_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalTemplateDetail.as_view(), name='workflow_approval_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/approvals/$', WorkflowApprovalTemplateJobsList.as_view(), name='workflow_approval_template_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/approvals/$', WorkflowApprovalTemplateJobsList.as_view(), name='workflow_approval_template_jobs_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
WorkflowJobList,
|
WorkflowJobList,
|
||||||
@@ -16,14 +16,14 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', WorkflowJobList.as_view(), name='workflow_job_list'),
|
re_path(r'^$', WorkflowJobList.as_view(), name='workflow_job_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowJobDetail.as_view(), name='workflow_job_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowJobDetail.as_view(), name='workflow_job_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/workflow_nodes/$', WorkflowJobWorkflowNodesList.as_view(), name='workflow_job_workflow_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/workflow_nodes/$', WorkflowJobWorkflowNodesList.as_view(), name='workflow_job_workflow_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobLabelList.as_view(), name='workflow_job_label_list'),
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobLabelList.as_view(), name='workflow_job_label_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/cancel/$', WorkflowJobCancel.as_view(), name='workflow_job_cancel'),
|
re_path(r'^(?P<pk>[0-9]+)/cancel/$', WorkflowJobCancel.as_view(), name='workflow_job_cancel'),
|
||||||
url(r'^(?P<pk>[0-9]+)/relaunch/$', WorkflowJobRelaunch.as_view(), name='workflow_job_relaunch'),
|
re_path(r'^(?P<pk>[0-9]+)/relaunch/$', WorkflowJobRelaunch.as_view(), name='workflow_job_relaunch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/notifications/$', WorkflowJobNotificationsList.as_view(), name='workflow_job_notifications_list'),
|
re_path(r'^(?P<pk>[0-9]+)/notifications/$', WorkflowJobNotificationsList.as_view(), name='workflow_job_notifications_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', WorkflowJobActivityStreamList.as_view(), name='workflow_job_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', WorkflowJobActivityStreamList.as_view(), name='workflow_job_activity_stream_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
WorkflowJobNodeList,
|
WorkflowJobNodeList,
|
||||||
@@ -14,12 +14,12 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', WorkflowJobNodeList.as_view(), name='workflow_job_node_list'),
|
re_path(r'^$', WorkflowJobNodeList.as_view(), name='workflow_job_node_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowJobNodeDetail.as_view(), name='workflow_job_node_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowJobNodeDetail.as_view(), name='workflow_job_node_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/success_nodes/$', WorkflowJobNodeSuccessNodesList.as_view(), name='workflow_job_node_success_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/success_nodes/$', WorkflowJobNodeSuccessNodesList.as_view(), name='workflow_job_node_success_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobNodeFailureNodesList.as_view(), name='workflow_job_node_failure_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobNodeFailureNodesList.as_view(), name='workflow_job_node_failure_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobNodeAlwaysNodesList.as_view(), name='workflow_job_node_always_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobNodeAlwaysNodesList.as_view(), name='workflow_job_node_always_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobNodeCredentialsList.as_view(), name='workflow_job_node_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobNodeCredentialsList.as_view(), name='workflow_job_node_credentials_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
WorkflowJobTemplateList,
|
WorkflowJobTemplateList,
|
||||||
@@ -24,39 +24,39 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', WorkflowJobTemplateList.as_view(), name='workflow_job_template_list'),
|
re_path(r'^$', WorkflowJobTemplateList.as_view(), name='workflow_job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowJobTemplateDetail.as_view(), name='workflow_job_template_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowJobTemplateDetail.as_view(), name='workflow_job_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/workflow_jobs/$', WorkflowJobTemplateJobsList.as_view(), name='workflow_job_template_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/workflow_jobs/$', WorkflowJobTemplateJobsList.as_view(), name='workflow_job_template_jobs_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/launch/$', WorkflowJobTemplateLaunch.as_view(), name='workflow_job_template_launch'),
|
re_path(r'^(?P<pk>[0-9]+)/launch/$', WorkflowJobTemplateLaunch.as_view(), name='workflow_job_template_launch'),
|
||||||
url(r'^(?P<pk>[0-9]+)/copy/$', WorkflowJobTemplateCopy.as_view(), name='workflow_job_template_copy'),
|
re_path(r'^(?P<pk>[0-9]+)/copy/$', WorkflowJobTemplateCopy.as_view(), name='workflow_job_template_copy'),
|
||||||
url(r'^(?P<pk>[0-9]+)/schedules/$', WorkflowJobTemplateSchedulesList.as_view(), name='workflow_job_template_schedules_list'),
|
re_path(r'^(?P<pk>[0-9]+)/schedules/$', WorkflowJobTemplateSchedulesList.as_view(), name='workflow_job_template_schedules_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/survey_spec/$', WorkflowJobTemplateSurveySpec.as_view(), name='workflow_job_template_survey_spec'),
|
re_path(r'^(?P<pk>[0-9]+)/survey_spec/$', WorkflowJobTemplateSurveySpec.as_view(), name='workflow_job_template_survey_spec'),
|
||||||
url(r'^(?P<pk>[0-9]+)/workflow_nodes/$', WorkflowJobTemplateWorkflowNodesList.as_view(), name='workflow_job_template_workflow_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/workflow_nodes/$', WorkflowJobTemplateWorkflowNodesList.as_view(), name='workflow_job_template_workflow_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', WorkflowJobTemplateActivityStreamList.as_view(), name='workflow_job_template_activity_stream_list'),
|
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', WorkflowJobTemplateActivityStreamList.as_view(), name='workflow_job_template_activity_stream_list'),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_started/$',
|
||||||
WorkflowJobTemplateNotificationTemplatesStartedList.as_view(),
|
WorkflowJobTemplateNotificationTemplatesStartedList.as_view(),
|
||||||
name='workflow_job_template_notification_templates_started_list',
|
name='workflow_job_template_notification_templates_started_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_error/$',
|
||||||
WorkflowJobTemplateNotificationTemplatesErrorList.as_view(),
|
WorkflowJobTemplateNotificationTemplatesErrorList.as_view(),
|
||||||
name='workflow_job_template_notification_templates_error_list',
|
name='workflow_job_template_notification_templates_error_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_success/$',
|
||||||
WorkflowJobTemplateNotificationTemplatesSuccessList.as_view(),
|
WorkflowJobTemplateNotificationTemplatesSuccessList.as_view(),
|
||||||
name='workflow_job_template_notification_templates_success_list',
|
name='workflow_job_template_notification_templates_success_list',
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r'^(?P<pk>[0-9]+)/notification_templates_approvals/$',
|
r'^(?P<pk>[0-9]+)/notification_templates_approvals/$',
|
||||||
WorkflowJobTemplateNotificationTemplatesApprovalList.as_view(),
|
WorkflowJobTemplateNotificationTemplatesApprovalList.as_view(),
|
||||||
name='workflow_job_template_notification_templates_approvals_list',
|
name='workflow_job_template_notification_templates_approvals_list',
|
||||||
),
|
),
|
||||||
url(r'^(?P<pk>[0-9]+)/access_list/$', WorkflowJobTemplateAccessList.as_view(), name='workflow_job_template_access_list'),
|
re_path(r'^(?P<pk>[0-9]+)/access_list/$', WorkflowJobTemplateAccessList.as_view(), name='workflow_job_template_access_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/object_roles/$', WorkflowJobTemplateObjectRolesList.as_view(), name='workflow_job_template_object_roles_list'),
|
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', WorkflowJobTemplateObjectRolesList.as_view(), name='workflow_job_template_object_roles_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobTemplateLabelList.as_view(), name='workflow_job_template_label_list'),
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobTemplateLabelList.as_view(), name='workflow_job_template_label_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/', include('awx.api.urls.webhooks'), {'model_kwarg': 'workflow_job_templates'}),
|
re_path(r'^(?P<pk>[0-9]+)/', include('awx.api.urls.webhooks'), {'model_kwarg': 'workflow_job_templates'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2017 Ansible, Inc.
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import (
|
from awx.api.views import (
|
||||||
WorkflowJobTemplateNodeList,
|
WorkflowJobTemplateNodeList,
|
||||||
@@ -15,13 +15,13 @@ from awx.api.views import (
|
|||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
url(r'^$', WorkflowJobTemplateNodeList.as_view(), name='workflow_job_template_node_list'),
|
re_path(r'^$', WorkflowJobTemplateNodeList.as_view(), name='workflow_job_template_node_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowJobTemplateNodeDetail.as_view(), name='workflow_job_template_node_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', WorkflowJobTemplateNodeDetail.as_view(), name='workflow_job_template_node_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/success_nodes/$', WorkflowJobTemplateNodeSuccessNodesList.as_view(), name='workflow_job_template_node_success_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/success_nodes/$', WorkflowJobTemplateNodeSuccessNodesList.as_view(), name='workflow_job_template_node_success_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'),
|
re_path(r'^(?P<pk>[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
@@ -62,7 +62,7 @@ import pytz
|
|||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.tasks import send_notifications, update_inventory_computed_fields
|
from awx.main.tasks.system import send_notifications, update_inventory_computed_fields
|
||||||
from awx.main.access import get_user_queryset, HostAccess
|
from awx.main.access import get_user_queryset, HostAccess
|
||||||
from awx.api.generics import (
|
from awx.api.generics import (
|
||||||
APIView,
|
APIView,
|
||||||
@@ -105,17 +105,16 @@ from awx.api.permissions import (
|
|||||||
ProjectUpdatePermission,
|
ProjectUpdatePermission,
|
||||||
InventoryInventorySourcesUpdatePermission,
|
InventoryInventorySourcesUpdatePermission,
|
||||||
UserPermission,
|
UserPermission,
|
||||||
InstanceGroupTowerPermission,
|
|
||||||
VariableDataPermission,
|
VariableDataPermission,
|
||||||
WorkflowApprovalPermission,
|
WorkflowApprovalPermission,
|
||||||
|
IsSystemAdminOrAuditor,
|
||||||
)
|
)
|
||||||
from awx.api import renderers
|
from awx.api import renderers
|
||||||
from awx.api import serializers
|
from awx.api import serializers
|
||||||
from awx.api.metadata import RoleMetadata
|
from awx.api.metadata import RoleMetadata
|
||||||
from awx.main.constants import ACTIVE_STATES
|
from awx.main.constants import ACTIVE_STATES, SURVEY_TYPE_MAPPING
|
||||||
from awx.main.scheduler.dag_workflow import WorkflowDAG
|
from awx.main.scheduler.dag_workflow import WorkflowDAG
|
||||||
from awx.api.views.mixin import (
|
from awx.api.views.mixin import (
|
||||||
ControlledByScmMixin,
|
|
||||||
InstanceGroupMembershipMixin,
|
InstanceGroupMembershipMixin,
|
||||||
OrganizationCountsMixin,
|
OrganizationCountsMixin,
|
||||||
RelatedJobsPreventDeleteMixin,
|
RelatedJobsPreventDeleteMixin,
|
||||||
@@ -156,8 +155,10 @@ from awx.api.views.inventory import ( # noqa
|
|||||||
InventoryAccessList,
|
InventoryAccessList,
|
||||||
InventoryObjectRolesList,
|
InventoryObjectRolesList,
|
||||||
InventoryJobTemplateList,
|
InventoryJobTemplateList,
|
||||||
|
InventoryLabelList,
|
||||||
InventoryCopy,
|
InventoryCopy,
|
||||||
)
|
)
|
||||||
|
from awx.api.views.mesh_visualizer import MeshVisualizer # noqa
|
||||||
from awx.api.views.root import ( # noqa
|
from awx.api.views.root import ( # noqa
|
||||||
ApiRootView,
|
ApiRootView,
|
||||||
ApiOAuthAuthorizationRootView,
|
ApiOAuthAuthorizationRootView,
|
||||||
@@ -170,6 +171,7 @@ from awx.api.views.root import ( # noqa
|
|||||||
)
|
)
|
||||||
from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver # noqa
|
from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver # noqa
|
||||||
from awx.api.pagination import UnifiedJobEventPagination
|
from awx.api.pagination import UnifiedJobEventPagination
|
||||||
|
from awx.main.utils import set_environ
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.views')
|
logger = logging.getLogger('awx.api.views')
|
||||||
@@ -362,6 +364,7 @@ class InstanceList(ListAPIView):
|
|||||||
model = models.Instance
|
model = models.Instance
|
||||||
serializer_class = serializers.InstanceSerializer
|
serializer_class = serializers.InstanceSerializer
|
||||||
search_fields = ('hostname',)
|
search_fields = ('hostname',)
|
||||||
|
ordering = ('id',)
|
||||||
|
|
||||||
|
|
||||||
class InstanceDetail(RetrieveUpdateAPIView):
|
class InstanceDetail(RetrieveUpdateAPIView):
|
||||||
@@ -374,8 +377,8 @@ class InstanceDetail(RetrieveUpdateAPIView):
|
|||||||
r = super(InstanceDetail, self).update(request, *args, **kwargs)
|
r = super(InstanceDetail, self).update(request, *args, **kwargs)
|
||||||
if status.is_success(r.status_code):
|
if status.is_success(r.status_code):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
obj.refresh_capacity()
|
obj.set_capacity_value()
|
||||||
obj.save()
|
obj.save(update_fields=['capacity'])
|
||||||
r.data = serializers.InstanceSerializer(obj, context=self.get_serializer_context()).to_representation(obj)
|
r.data = serializers.InstanceSerializer(obj, context=self.get_serializer_context()).to_representation(obj)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -402,6 +405,75 @@ class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAtta
|
|||||||
parent_model = models.Instance
|
parent_model = models.Instance
|
||||||
relationship = 'rampart_groups'
|
relationship = 'rampart_groups'
|
||||||
|
|
||||||
|
def is_valid_relation(self, parent, sub, created=False):
|
||||||
|
if parent.node_type == 'control':
|
||||||
|
return {'msg': _(f"Cannot change instance group membership of control-only node: {parent.hostname}.")}
|
||||||
|
if parent.node_type == 'hop':
|
||||||
|
return {'msg': _(f"Cannot change instance group membership of hop node : {parent.hostname}.")}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_valid_removal(self, parent, sub):
|
||||||
|
res = self.is_valid_relation(parent, sub)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
if sub.name == settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME and parent.node_type == 'hybrid':
|
||||||
|
return {'msg': _(f"Cannot disassociate hybrid instance {parent.hostname} from {sub.name}.")}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceHealthCheck(GenericAPIView):
|
||||||
|
|
||||||
|
name = _('Instance Health Check')
|
||||||
|
model = models.Instance
|
||||||
|
serializer_class = serializers.InstanceHealthCheckSerializer
|
||||||
|
permission_classes = (IsSystemAdminOrAuditor,)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# FIXME: For now, we don't have a good way of checking the health of a hop node.
|
||||||
|
return super().get_queryset().exclude(node_type='hop')
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object()
|
||||||
|
data = self.get_serializer(data=request.data).to_representation(obj)
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object()
|
||||||
|
|
||||||
|
if obj.node_type == 'execution':
|
||||||
|
from awx.main.tasks.system import execution_node_health_check
|
||||||
|
|
||||||
|
runner_data = execution_node_health_check(obj.hostname)
|
||||||
|
obj.refresh_from_db()
|
||||||
|
data = self.get_serializer(data=request.data).to_representation(obj)
|
||||||
|
# Add in some extra unsaved fields
|
||||||
|
for extra_field in ('transmit_timing', 'run_timing'):
|
||||||
|
if extra_field in runner_data:
|
||||||
|
data[extra_field] = runner_data[extra_field]
|
||||||
|
else:
|
||||||
|
from awx.main.tasks.system import cluster_node_health_check
|
||||||
|
|
||||||
|
if settings.CLUSTER_HOST_ID == obj.hostname:
|
||||||
|
cluster_node_health_check(obj.hostname)
|
||||||
|
else:
|
||||||
|
cluster_node_health_check.apply_async([obj.hostname], queue=obj.hostname)
|
||||||
|
start_time = time.time()
|
||||||
|
prior_check_time = obj.last_health_check
|
||||||
|
while time.time() - start_time < 50.0:
|
||||||
|
obj.refresh_from_db(fields=['last_health_check'])
|
||||||
|
if obj.last_health_check != prior_check_time:
|
||||||
|
break
|
||||||
|
if time.time() - start_time < 1.0:
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
time.sleep(1.0)
|
||||||
|
else:
|
||||||
|
obj.mark_offline(errors=_('Health check initiated by user determined this instance to be unresponsive'))
|
||||||
|
obj.refresh_from_db()
|
||||||
|
data = self.get_serializer(data=request.data).to_representation(obj)
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class InstanceGroupList(ListCreateAPIView):
|
class InstanceGroupList(ListCreateAPIView):
|
||||||
|
|
||||||
@@ -416,7 +488,6 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP
|
|||||||
name = _("Instance Group Detail")
|
name = _("Instance Group Detail")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
serializer_class = serializers.InstanceGroupSerializer
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
permission_classes = (InstanceGroupTowerPermission,)
|
|
||||||
|
|
||||||
def update_raw_data(self, data):
|
def update_raw_data(self, data):
|
||||||
if self.get_object().is_container_group:
|
if self.get_object().is_container_group:
|
||||||
@@ -444,12 +515,28 @@ class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetac
|
|||||||
relationship = "instances"
|
relationship = "instances"
|
||||||
search_fields = ('hostname',)
|
search_fields = ('hostname',)
|
||||||
|
|
||||||
|
def is_valid_relation(self, parent, sub, created=False):
|
||||||
|
if sub.node_type == 'control':
|
||||||
|
return {'msg': _(f"Cannot change instance group membership of control-only node: {sub.hostname}.")}
|
||||||
|
if sub.node_type == 'hop':
|
||||||
|
return {'msg': _(f"Cannot change instance group membership of hop node : {sub.hostname}.")}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_valid_removal(self, parent, sub):
|
||||||
|
res = self.is_valid_relation(parent, sub)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
if sub.node_type == 'hybrid' and parent.name == settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME:
|
||||||
|
return {'msg': _(f"Cannot disassociate hybrid node {sub.hostname} from {parent.name}.")}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ScheduleList(ListCreateAPIView):
|
class ScheduleList(ListCreateAPIView):
|
||||||
|
|
||||||
name = _("Schedules")
|
name = _("Schedules")
|
||||||
model = models.Schedule
|
model = models.Schedule
|
||||||
serializer_class = serializers.ScheduleSerializer
|
serializer_class = serializers.ScheduleSerializer
|
||||||
|
ordering = ('id',)
|
||||||
|
|
||||||
|
|
||||||
class ScheduleDetail(RetrieveUpdateDestroyAPIView):
|
class ScheduleDetail(RetrieveUpdateDestroyAPIView):
|
||||||
@@ -490,8 +577,7 @@ class ScheduleZoneInfo(APIView):
|
|||||||
swagger_topic = 'System Configuration'
|
swagger_topic = 'System Configuration'
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
zones = [{'name': zone} for zone in models.Schedule.get_zoneinfo()]
|
return Response({'zones': models.Schedule.get_zoneinfo(), 'links': models.Schedule.get_zoneinfo_links()})
|
||||||
return Response(zones)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchConfigCredentialsBase(SubListAttachDetachAPIView):
|
class LaunchConfigCredentialsBase(SubListAttachDetachAPIView):
|
||||||
@@ -1486,8 +1572,9 @@ class CredentialExternalTest(SubDetailAPIView):
|
|||||||
backend_kwargs[field_name] = value
|
backend_kwargs[field_name] = value
|
||||||
backend_kwargs.update(request.data.get('metadata', {}))
|
backend_kwargs.update(request.data.get('metadata', {}))
|
||||||
try:
|
try:
|
||||||
obj.credential_type.plugin.backend(**backend_kwargs)
|
with set_environ(**settings.AWX_TASK_ENV):
|
||||||
return Response({}, status=status.HTTP_202_ACCEPTED)
|
obj.credential_type.plugin.backend(**backend_kwargs)
|
||||||
|
return Response({}, status=status.HTTP_202_ACCEPTED)
|
||||||
except requests.exceptions.HTTPError as exc:
|
except requests.exceptions.HTTPError as exc:
|
||||||
message = 'HTTP {}'.format(exc.response.status_code)
|
message = 'HTTP {}'.format(exc.response.status_code)
|
||||||
return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -1587,7 +1674,7 @@ class HostList(HostRelatedSearchMixin, ListCreateAPIView):
|
|||||||
return Response(dict(error=_(str(e))), status=status.HTTP_400_BAD_REQUEST)
|
return Response(dict(error=_(str(e))), status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class HostDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
|
class HostDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
always_allow_superuser = False
|
always_allow_superuser = False
|
||||||
model = models.Host
|
model = models.Host
|
||||||
@@ -1621,7 +1708,7 @@ class InventoryHostsList(HostRelatedSearchMixin, SubListCreateAttachDetachAPIVie
|
|||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class HostGroupsList(ControlledByScmMixin, SubListCreateAttachDetachAPIView):
|
class HostGroupsList(SubListCreateAttachDetachAPIView):
|
||||||
'''the list of groups a host is directly a member of'''
|
'''the list of groups a host is directly a member of'''
|
||||||
|
|
||||||
model = models.Group
|
model = models.Group
|
||||||
@@ -1737,7 +1824,7 @@ class EnforceParentRelationshipMixin(object):
|
|||||||
return super(EnforceParentRelationshipMixin, self).create(request, *args, **kwargs)
|
return super(EnforceParentRelationshipMixin, self).create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GroupChildrenList(ControlledByScmMixin, EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
|
class GroupChildrenList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
model = models.Group
|
model = models.Group
|
||||||
serializer_class = serializers.GroupSerializer
|
serializer_class = serializers.GroupSerializer
|
||||||
@@ -1783,7 +1870,7 @@ class GroupPotentialChildrenList(SubListAPIView):
|
|||||||
return qs.exclude(pk__in=except_pks)
|
return qs.exclude(pk__in=except_pks)
|
||||||
|
|
||||||
|
|
||||||
class GroupHostsList(HostRelatedSearchMixin, ControlledByScmMixin, SubListCreateAttachDetachAPIView):
|
class GroupHostsList(HostRelatedSearchMixin, SubListCreateAttachDetachAPIView):
|
||||||
'''the list of hosts directly below a group'''
|
'''the list of hosts directly below a group'''
|
||||||
|
|
||||||
model = models.Host
|
model = models.Host
|
||||||
@@ -1847,7 +1934,7 @@ class GroupActivityStreamList(SubListAPIView):
|
|||||||
return qs.filter(Q(group=parent) | Q(host__in=parent.hosts.all()))
|
return qs.filter(Q(group=parent) | Q(host__in=parent.hosts.all()))
|
||||||
|
|
||||||
|
|
||||||
class GroupDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
|
class GroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
model = models.Group
|
model = models.Group
|
||||||
serializer_class = serializers.GroupSerializer
|
serializer_class = serializers.GroupSerializer
|
||||||
@@ -2397,8 +2484,6 @@ class JobTemplateSurveySpec(GenericAPIView):
|
|||||||
obj_permission_type = 'admin'
|
obj_permission_type = 'admin'
|
||||||
serializer_class = serializers.EmptySerializer
|
serializer_class = serializers.EmptySerializer
|
||||||
|
|
||||||
ALLOWED_TYPES = {'text': str, 'textarea': str, 'password': str, 'multiplechoice': str, 'multiselect': str, 'integer': int, 'float': float}
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
return Response(obj.display_survey_spec())
|
return Response(obj.display_survey_spec())
|
||||||
@@ -2469,17 +2554,17 @@ class JobTemplateSurveySpec(GenericAPIView):
|
|||||||
# Type-specific validation
|
# Type-specific validation
|
||||||
# validate question type <-> default type
|
# validate question type <-> default type
|
||||||
qtype = survey_item["type"]
|
qtype = survey_item["type"]
|
||||||
if qtype not in JobTemplateSurveySpec.ALLOWED_TYPES:
|
if qtype not in SURVEY_TYPE_MAPPING:
|
||||||
return Response(
|
return Response(
|
||||||
dict(
|
dict(
|
||||||
error=_("'{survey_item[type]}' in survey question {idx} is not one of '{allowed_types}' allowed question types.").format(
|
error=_("'{survey_item[type]}' in survey question {idx} is not one of '{allowed_types}' allowed question types.").format(
|
||||||
allowed_types=', '.join(JobTemplateSurveySpec.ALLOWED_TYPES.keys()), **context
|
allowed_types=', '.join(SURVEY_TYPE_MAPPING.keys()), **context
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
if 'default' in survey_item and survey_item['default'] != '':
|
if 'default' in survey_item and survey_item['default'] != '':
|
||||||
if not isinstance(survey_item['default'], JobTemplateSurveySpec.ALLOWED_TYPES[qtype]):
|
if not isinstance(survey_item['default'], SURVEY_TYPE_MAPPING[qtype]):
|
||||||
type_label = 'string'
|
type_label = 'string'
|
||||||
if qtype in ['integer', 'float']:
|
if qtype in ['integer', 'float']:
|
||||||
type_label = qtype
|
type_label = qtype
|
||||||
@@ -3757,6 +3842,112 @@ class JobJobEventsList(BaseJobEventsList):
|
|||||||
return job.get_event_queryset().select_related('host').order_by('start_line')
|
return job.get_event_queryset().select_related('host').order_by('start_line')
|
||||||
|
|
||||||
|
|
||||||
|
class JobJobEventsChildrenSummary(APIView):
|
||||||
|
|
||||||
|
renderer_classes = [JSONRenderer]
|
||||||
|
meta_events = ('debug', 'verbose', 'warning', 'error', 'system_warning', 'deprecated')
|
||||||
|
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
resp = dict(children_summary={}, meta_event_nested_uuid={}, event_processing_finished=False, is_tree=True)
|
||||||
|
job = get_object_or_404(models.Job, pk=kwargs['pk'])
|
||||||
|
if not job.event_processing_finished:
|
||||||
|
return Response(resp)
|
||||||
|
else:
|
||||||
|
resp["event_processing_finished"] = True
|
||||||
|
|
||||||
|
events = list(job.get_event_queryset().values('counter', 'uuid', 'parent_uuid', 'event').order_by('counter'))
|
||||||
|
if len(events) == 0:
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
|
# key is counter, value is number of total children (including children of children, etc.)
|
||||||
|
map_counter_children_tally = {i['counter']: {"rowNumber": 0, "numChildren": 0} for i in events}
|
||||||
|
# key is uuid, value is counter
|
||||||
|
map_uuid_counter = {i['uuid']: i['counter'] for i in events}
|
||||||
|
# key is uuid, value is parent uuid. Used as a quick lookup
|
||||||
|
map_uuid_puuid = {i['uuid']: i['parent_uuid'] for i in events}
|
||||||
|
# key is counter of meta events (i.e. verbose), value is uuid of the assigned parent
|
||||||
|
map_meta_counter_nested_uuid = {}
|
||||||
|
|
||||||
|
# collapsable tree view in the UI only makes sense for tree-like
|
||||||
|
# hierarchy. If ansible is ran with a strategy like free or host_pinned, then
|
||||||
|
# events can be out of sequential order, and no longer follow a tree structure
|
||||||
|
# E1
|
||||||
|
# E2
|
||||||
|
# E3
|
||||||
|
# E4 <- parent is E3
|
||||||
|
# E5 <- parent is E1
|
||||||
|
# in the above, there is no clear way to collapse E1, because E5 comes after
|
||||||
|
# E3, which occurs after E1. Thus the tree view should be disabled.
|
||||||
|
|
||||||
|
# mark the last seen uuid at a given level (0-3)
|
||||||
|
# if a parent uuid is not in this list, then we know the events are not tree-like
|
||||||
|
# and return a response with is_tree: False
|
||||||
|
level_current_uuid = [None, None, None, None]
|
||||||
|
|
||||||
|
prev_non_meta_event = events[0]
|
||||||
|
for i, e in enumerate(events):
|
||||||
|
if not e['event'] in JobJobEventsChildrenSummary.meta_events:
|
||||||
|
prev_non_meta_event = e
|
||||||
|
if not e['uuid']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not e['event'] in JobJobEventsChildrenSummary.meta_events:
|
||||||
|
level = models.JobEvent.LEVEL_FOR_EVENT[e['event']]
|
||||||
|
level_current_uuid[level] = e['uuid']
|
||||||
|
# if setting level 1, for example, set levels 2 and 3 back to None
|
||||||
|
for u in range(level + 1, len(level_current_uuid)):
|
||||||
|
level_current_uuid[u] = None
|
||||||
|
|
||||||
|
puuid = e['parent_uuid']
|
||||||
|
if puuid and puuid not in level_current_uuid:
|
||||||
|
# improper tree detected, so bail out early
|
||||||
|
resp['is_tree'] = False
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
|
# if event is verbose (or debug, etc), we need to "assign" it a
|
||||||
|
# parent. This code looks at the event level of the previous
|
||||||
|
# non-verbose event, and the level of the next (by looking ahead)
|
||||||
|
# non-verbose event. The verbose event is assigned the same parent
|
||||||
|
# uuid of the higher level event.
|
||||||
|
# e.g.
|
||||||
|
# E1
|
||||||
|
# E2
|
||||||
|
# verbose
|
||||||
|
# verbose <- we are on this event currently
|
||||||
|
# E4
|
||||||
|
# We'll compare E2 and E4, and the verbose event
|
||||||
|
# will be assigned the parent uuid of E4 (higher event level)
|
||||||
|
if e['event'] in JobJobEventsChildrenSummary.meta_events:
|
||||||
|
event_level_before = models.JobEvent.LEVEL_FOR_EVENT[prev_non_meta_event['event']]
|
||||||
|
# find next non meta event
|
||||||
|
z = i
|
||||||
|
next_non_meta_event = events[-1]
|
||||||
|
while z < len(events):
|
||||||
|
if events[z]['event'] not in JobJobEventsChildrenSummary.meta_events:
|
||||||
|
next_non_meta_event = events[z]
|
||||||
|
break
|
||||||
|
z += 1
|
||||||
|
event_level_after = models.JobEvent.LEVEL_FOR_EVENT[next_non_meta_event['event']]
|
||||||
|
if event_level_after and event_level_after > event_level_before:
|
||||||
|
puuid = next_non_meta_event['parent_uuid']
|
||||||
|
else:
|
||||||
|
puuid = prev_non_meta_event['parent_uuid']
|
||||||
|
if puuid:
|
||||||
|
map_meta_counter_nested_uuid[e['counter']] = puuid
|
||||||
|
map_counter_children_tally[e['counter']]['rowNumber'] = i
|
||||||
|
if not puuid:
|
||||||
|
continue
|
||||||
|
# now traverse up the parent, grandparent, etc. events and tally those
|
||||||
|
while puuid:
|
||||||
|
map_counter_children_tally[map_uuid_counter[puuid]]['numChildren'] += 1
|
||||||
|
puuid = map_uuid_puuid.get(puuid, None)
|
||||||
|
|
||||||
|
# create new dictionary, dropping events with 0 children
|
||||||
|
resp["children_summary"] = {k: v for k, v in map_counter_children_tally.items() if v['numChildren'] != 0}
|
||||||
|
resp["meta_event_nested_uuid"] = map_meta_counter_nested_uuid
|
||||||
|
return Response(resp)
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandList(ListCreateAPIView):
|
class AdHocCommandList(ListCreateAPIView):
|
||||||
|
|
||||||
model = models.AdHocCommand
|
model = models.AdHocCommand
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logging
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
@@ -16,17 +16,21 @@ from rest_framework.response import Response
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import (
|
from awx.main.models import ActivityStream, Inventory, JobTemplate, Role, User, InstanceGroup, InventoryUpdateEvent, InventoryUpdate
|
||||||
ActivityStream,
|
|
||||||
Inventory,
|
from awx.main.models.label import Label
|
||||||
JobTemplate,
|
|
||||||
Role,
|
from awx.api.generics import (
|
||||||
User,
|
ListCreateAPIView,
|
||||||
InstanceGroup,
|
RetrieveUpdateDestroyAPIView,
|
||||||
InventoryUpdateEvent,
|
SubListAPIView,
|
||||||
InventoryUpdate,
|
SubListAttachDetachAPIView,
|
||||||
|
ResourceAccessList,
|
||||||
|
CopyAPIView,
|
||||||
|
DeleteLastUnattachLabelMixin,
|
||||||
|
SubListCreateAttachDetachAPIView,
|
||||||
)
|
)
|
||||||
from awx.api.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView, SubListAPIView, SubListAttachDetachAPIView, ResourceAccessList, CopyAPIView
|
|
||||||
|
|
||||||
from awx.api.serializers import (
|
from awx.api.serializers import (
|
||||||
InventorySerializer,
|
InventorySerializer,
|
||||||
@@ -35,8 +39,9 @@ from awx.api.serializers import (
|
|||||||
InstanceGroupSerializer,
|
InstanceGroupSerializer,
|
||||||
InventoryUpdateEventSerializer,
|
InventoryUpdateEventSerializer,
|
||||||
JobTemplateSerializer,
|
JobTemplateSerializer,
|
||||||
|
LabelSerializer,
|
||||||
)
|
)
|
||||||
from awx.api.views.mixin import RelatedJobsPreventDeleteMixin, ControlledByScmMixin
|
from awx.api.views.mixin import RelatedJobsPreventDeleteMixin
|
||||||
|
|
||||||
from awx.api.pagination import UnifiedJobEventPagination
|
from awx.api.pagination import UnifiedJobEventPagination
|
||||||
|
|
||||||
@@ -70,7 +75,7 @@ class InventoryList(ListCreateAPIView):
|
|||||||
serializer_class = InventorySerializer
|
serializer_class = InventorySerializer
|
||||||
|
|
||||||
|
|
||||||
class InventoryDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
|
class InventoryDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
model = Inventory
|
model = Inventory
|
||||||
serializer_class = InventorySerializer
|
serializer_class = InventorySerializer
|
||||||
@@ -152,6 +157,30 @@ class InventoryJobTemplateList(SubListAPIView):
|
|||||||
return qs.filter(inventory=parent)
|
return qs.filter(inventory=parent)
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView, SubListAPIView):
|
||||||
|
|
||||||
|
model = Label
|
||||||
|
serializer_class = LabelSerializer
|
||||||
|
parent_model = Inventory
|
||||||
|
relationship = 'labels'
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If a label already exists in the database, attach it instead of erroring out
|
||||||
|
# that it already exists
|
||||||
|
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
||||||
|
existing = Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
||||||
|
if existing.exists():
|
||||||
|
existing = existing[0]
|
||||||
|
request.data['id'] = existing.id
|
||||||
|
del request.data['name']
|
||||||
|
del request.data['organization']
|
||||||
|
if Label.objects.filter(inventory_labels=self.kwargs['pk']).count() > 100:
|
||||||
|
return Response(
|
||||||
|
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
return super(InventoryLabelList, self).post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class InventoryCopy(CopyAPIView):
|
class InventoryCopy(CopyAPIView):
|
||||||
|
|
||||||
model = Inventory
|
model = Inventory
|
||||||
|
|||||||
25
awx/api/views/mesh_visualizer.py
Normal file
25
awx/api/views/mesh_visualizer.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright (c) 2018 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from awx.api.generics import APIView, Response
|
||||||
|
from awx.api.permissions import IsSystemAdminOrAuditor
|
||||||
|
from awx.api.serializers import InstanceLinkSerializer, InstanceNodeSerializer
|
||||||
|
from awx.main.models import InstanceLink, Instance
|
||||||
|
|
||||||
|
|
||||||
|
class MeshVisualizer(APIView):
|
||||||
|
|
||||||
|
name = _("Mesh Visualizer")
|
||||||
|
permission_classes = (IsSystemAdminOrAuditor,)
|
||||||
|
swagger_topic = "System Configuration"
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'nodes': InstanceNodeSerializer(Instance.objects.all(), many=True).data,
|
||||||
|
'links': InstanceLinkSerializer(InstanceLink.objects.select_related('target', 'source'), many=True).data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(data)
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|||||||
@@ -8,15 +8,14 @@ from django.db.models import Count
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework.permissions import SAFE_METHODS
|
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from awx.main.constants import ACTIVE_STATES
|
from awx.main.constants import ACTIVE_STATES
|
||||||
from awx.main.utils import get_object_or_400, parse_yaml_or_json
|
from awx.main.utils import get_object_or_400
|
||||||
from awx.main.models.ha import Instance, InstanceGroup
|
from awx.main.models.ha import Instance, InstanceGroup
|
||||||
from awx.main.models.organization import Team
|
from awx.main.models.organization import Team
|
||||||
from awx.main.models.projects import Project
|
from awx.main.models.projects import Project
|
||||||
@@ -70,36 +69,25 @@ class InstanceGroupMembershipMixin(object):
|
|||||||
|
|
||||||
def attach(self, request, *args, **kwargs):
|
def attach(self, request, *args, **kwargs):
|
||||||
response = super(InstanceGroupMembershipMixin, self).attach(request, *args, **kwargs)
|
response = super(InstanceGroupMembershipMixin, self).attach(request, *args, **kwargs)
|
||||||
sub_id, res = self.attach_validate(request)
|
|
||||||
if status.is_success(response.status_code):
|
if status.is_success(response.status_code):
|
||||||
|
sub_id = request.data.get('id', None)
|
||||||
if self.parent_model is Instance:
|
if self.parent_model is Instance:
|
||||||
ig_obj = get_object_or_400(self.model, pk=sub_id)
|
inst_name = self.get_parent_object().hostname
|
||||||
inst_name = ig_obj.hostname
|
|
||||||
else:
|
else:
|
||||||
inst_name = get_object_or_400(self.model, pk=sub_id).hostname
|
inst_name = get_object_or_400(self.model, pk=sub_id).hostname
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
ig_qs = InstanceGroup.objects.select_for_update()
|
instance_groups_queryset = InstanceGroup.objects.select_for_update()
|
||||||
if self.parent_model is Instance:
|
if self.parent_model is Instance:
|
||||||
ig_obj = get_object_or_400(ig_qs, pk=sub_id)
|
ig_obj = get_object_or_400(instance_groups_queryset, pk=sub_id)
|
||||||
else:
|
else:
|
||||||
# similar to get_parent_object, but selected for update
|
# similar to get_parent_object, but selected for update
|
||||||
parent_filter = {self.lookup_field: self.kwargs.get(self.lookup_field, None)}
|
parent_filter = {self.lookup_field: self.kwargs.get(self.lookup_field, None)}
|
||||||
ig_obj = get_object_or_404(ig_qs, **parent_filter)
|
ig_obj = get_object_or_404(instance_groups_queryset, **parent_filter)
|
||||||
if inst_name not in ig_obj.policy_instance_list:
|
if inst_name not in ig_obj.policy_instance_list:
|
||||||
ig_obj.policy_instance_list.append(inst_name)
|
ig_obj.policy_instance_list.append(inst_name)
|
||||||
ig_obj.save(update_fields=['policy_instance_list'])
|
ig_obj.save(update_fields=['policy_instance_list'])
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def unattach_validate(self, request):
|
|
||||||
(sub_id, res) = super(InstanceGroupMembershipMixin, self).unattach_validate(request)
|
|
||||||
if res:
|
|
||||||
return (sub_id, res)
|
|
||||||
sub = get_object_or_400(self.model, pk=sub_id)
|
|
||||||
attach_errors = self.is_valid_relation(None, sub)
|
|
||||||
if attach_errors:
|
|
||||||
return (sub_id, Response(attach_errors, status=status.HTTP_400_BAD_REQUEST))
|
|
||||||
return (sub_id, res)
|
|
||||||
|
|
||||||
def unattach(self, request, *args, **kwargs):
|
def unattach(self, request, *args, **kwargs):
|
||||||
response = super(InstanceGroupMembershipMixin, self).unattach(request, *args, **kwargs)
|
response = super(InstanceGroupMembershipMixin, self).unattach(request, *args, **kwargs)
|
||||||
if status.is_success(response.status_code):
|
if status.is_success(response.status_code):
|
||||||
@@ -109,13 +97,13 @@ class InstanceGroupMembershipMixin(object):
|
|||||||
else:
|
else:
|
||||||
inst_name = get_object_or_400(self.model, pk=sub_id).hostname
|
inst_name = get_object_or_400(self.model, pk=sub_id).hostname
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
ig_qs = InstanceGroup.objects.select_for_update()
|
instance_groups_queryset = InstanceGroup.objects.select_for_update()
|
||||||
if self.parent_model is Instance:
|
if self.parent_model is Instance:
|
||||||
ig_obj = get_object_or_400(ig_qs, pk=sub_id)
|
ig_obj = get_object_or_400(instance_groups_queryset, pk=sub_id)
|
||||||
else:
|
else:
|
||||||
# similar to get_parent_object, but selected for update
|
# similar to get_parent_object, but selected for update
|
||||||
parent_filter = {self.lookup_field: self.kwargs.get(self.lookup_field, None)}
|
parent_filter = {self.lookup_field: self.kwargs.get(self.lookup_field, None)}
|
||||||
ig_obj = get_object_or_404(ig_qs, **parent_filter)
|
ig_obj = get_object_or_404(instance_groups_queryset, **parent_filter)
|
||||||
if inst_name in ig_obj.policy_instance_list:
|
if inst_name in ig_obj.policy_instance_list:
|
||||||
ig_obj.policy_instance_list.pop(ig_obj.policy_instance_list.index(inst_name))
|
ig_obj.policy_instance_list.pop(ig_obj.policy_instance_list.index(inst_name))
|
||||||
ig_obj.save(update_fields=['policy_instance_list'])
|
ig_obj.save(update_fields=['policy_instance_list'])
|
||||||
@@ -197,35 +185,6 @@ class OrganizationCountsMixin(object):
|
|||||||
return full_context
|
return full_context
|
||||||
|
|
||||||
|
|
||||||
class ControlledByScmMixin(object):
|
|
||||||
"""
|
|
||||||
Special method to reset SCM inventory commit hash
|
|
||||||
if anything that it manages changes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _reset_inv_src_rev(self, obj):
|
|
||||||
if self.request.method in SAFE_METHODS or not obj:
|
|
||||||
return
|
|
||||||
project_following_sources = obj.inventory_sources.filter(update_on_project_update=True, source='scm')
|
|
||||||
if project_following_sources:
|
|
||||||
# Allow inventory changes unrelated to variables
|
|
||||||
if self.model == Inventory and (
|
|
||||||
not self.request or not self.request.data or parse_yaml_or_json(self.request.data.get('variables', '')) == parse_yaml_or_json(obj.variables)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
project_following_sources.update(scm_last_revision='')
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
obj = super(ControlledByScmMixin, self).get_object()
|
|
||||||
self._reset_inv_src_rev(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def get_parent_object(self):
|
|
||||||
obj = super(ControlledByScmMixin, self).get_parent_object()
|
|
||||||
self._reset_inv_src_rev(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class NoTruncateMixin(object):
|
class NoTruncateMixin(object):
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import logging
|
|||||||
# Django
|
# Django
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import operator
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_str
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@@ -123,6 +123,7 @@ class ApiVersionRootView(APIView):
|
|||||||
data['workflow_approvals'] = reverse('api:workflow_approval_list', request=request)
|
data['workflow_approvals'] = reverse('api:workflow_approval_list', request=request)
|
||||||
data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request)
|
data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request)
|
||||||
data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request)
|
data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request)
|
||||||
|
data['mesh_visualizer'] = reverse('api:mesh_visualizer_view', request=request)
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
@@ -149,16 +150,24 @@ class ApiV2PingView(APIView):
|
|||||||
response = {'ha': is_ha_environment(), 'version': get_awx_version(), 'active_node': settings.CLUSTER_HOST_ID, 'install_uuid': settings.INSTALL_UUID}
|
response = {'ha': is_ha_environment(), 'version': get_awx_version(), 'active_node': settings.CLUSTER_HOST_ID, 'install_uuid': settings.INSTALL_UUID}
|
||||||
|
|
||||||
response['instances'] = []
|
response['instances'] = []
|
||||||
for instance in Instance.objects.all():
|
for instance in Instance.objects.exclude(node_type='hop'):
|
||||||
response['instances'].append(
|
response['instances'].append(
|
||||||
dict(node=instance.hostname, uuid=instance.uuid, heartbeat=instance.modified, capacity=instance.capacity, version=instance.version)
|
dict(
|
||||||
|
node=instance.hostname,
|
||||||
|
node_type=instance.node_type,
|
||||||
|
uuid=instance.uuid,
|
||||||
|
heartbeat=instance.last_seen,
|
||||||
|
capacity=instance.capacity,
|
||||||
|
version=instance.version,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
sorted(response['instances'], key=operator.itemgetter('node'))
|
response['instances'] = sorted(response['instances'], key=operator.itemgetter('node'))
|
||||||
response['instance_groups'] = []
|
response['instance_groups'] = []
|
||||||
for instance_group in InstanceGroup.objects.prefetch_related('instances'):
|
for instance_group in InstanceGroup.objects.prefetch_related('instances'):
|
||||||
response['instance_groups'].append(
|
response['instance_groups'].append(
|
||||||
dict(name=instance_group.name, capacity=instance_group.capacity, instances=[x.hostname for x in instance_group.instances.all()])
|
dict(name=instance_group.name, capacity=instance_group.capacity, instances=[x.hostname for x in instance_group.instances.all()])
|
||||||
)
|
)
|
||||||
|
response['instance_groups'] = sorted(response['instance_groups'], key=lambda x: x['name'].lower())
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
||||||
|
|
||||||
@@ -196,7 +205,7 @@ class ApiV2SubscriptionView(APIView):
|
|||||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||||
msg = exc.args[0]
|
msg = exc.args[0]
|
||||||
else:
|
else:
|
||||||
logger.exception(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
return Response(validated)
|
return Response(validated)
|
||||||
@@ -237,7 +246,7 @@ class ApiV2AttachView(APIView):
|
|||||||
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
elif isinstance(exc, (ValueError, OSError)) and exc.args:
|
||||||
msg = exc.args[0]
|
msg = exc.args[0]
|
||||||
else:
|
else:
|
||||||
logger.exception(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
for sub in validated:
|
for sub in validated:
|
||||||
if sub['pool_id'] == pool_id:
|
if sub['pool_id'] == pool_id:
|
||||||
@@ -313,7 +322,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
try:
|
try:
|
||||||
data_actual = json.dumps(request.data)
|
data_actual = json.dumps(request.data)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.info(smart_text(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username))
|
logger.info(smart_str(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
license_data = json.loads(data_actual)
|
license_data = json.loads(data_actual)
|
||||||
@@ -337,7 +346,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
try:
|
try:
|
||||||
license_data_validated = get_licenser().license_from_manifest(license_data)
|
license_data_validated = get_licenser().license_from_manifest(license_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
logger.warning(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
else:
|
else:
|
||||||
license_data_validated = get_licenser().validate()
|
license_data_validated = get_licenser().validate()
|
||||||
@@ -348,7 +357,7 @@ class ApiV2ConfigView(APIView):
|
|||||||
settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host())
|
settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host())
|
||||||
return Response(license_data_validated)
|
return Response(license_data_validated)
|
||||||
|
|
||||||
logger.warning(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
logger.warning(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -16,7 +16,7 @@ from awx.api import serializers
|
|||||||
from awx.api.generics import APIView, GenericAPIView
|
from awx.api.generics import APIView, GenericAPIView
|
||||||
from awx.api.permissions import WebhookKeyPermission
|
from awx.api.permissions import WebhookKeyPermission
|
||||||
from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate
|
from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate
|
||||||
|
from awx.main.constants import JOB_VARIABLE_PREFIXES
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.views.webhooks')
|
logger = logging.getLogger('awx.api.views.webhooks')
|
||||||
|
|
||||||
@@ -136,15 +136,16 @@ class WebhookReceiverBase(APIView):
|
|||||||
'webhook_credential': obj.webhook_credential,
|
'webhook_credential': obj.webhook_credential,
|
||||||
'webhook_guid': event_guid,
|
'webhook_guid': event_guid,
|
||||||
},
|
},
|
||||||
'extra_vars': {
|
'extra_vars': {},
|
||||||
'tower_webhook_event_type': event_type,
|
|
||||||
'tower_webhook_event_guid': event_guid,
|
|
||||||
'tower_webhook_event_ref': event_ref,
|
|
||||||
'tower_webhook_status_api': status_api,
|
|
||||||
'tower_webhook_payload': request.data,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
|
kwargs['extra_vars']['{}_webhook_event_type'.format(name)] = event_type
|
||||||
|
kwargs['extra_vars']['{}_webhook_event_guid'.format(name)] = event_guid
|
||||||
|
kwargs['extra_vars']['{}_webhook_event_ref'.format(name)] = event_ref
|
||||||
|
kwargs['extra_vars']['{}_webhook_status_api'.format(name)] = status_api
|
||||||
|
kwargs['extra_vars']['{}_webhook_payload'.format(name)] = request.data
|
||||||
|
|
||||||
new_job = obj.create_unified_job(**kwargs)
|
new_job = obj.create_unified_job(**kwargs)
|
||||||
new_job.signal_start()
|
new_job.signal_start()
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ class GitlabWebhookReceiver(WebhookReceiverBase):
|
|||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
def get_event_status_api(self):
|
def get_event_status_api(self):
|
||||||
if self.get_event_type() != 'Merge Request Hook':
|
if self.get_event_type() not in self.ref_keys.keys():
|
||||||
return
|
return
|
||||||
project = self.request.data.get('project', {})
|
project = self.request.data.get('project', {})
|
||||||
repo_url = project.get('web_url')
|
repo_url = project.get('web_url')
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ from django.utils.module_loading import autodiscover_modules
|
|||||||
# AWX
|
# AWX
|
||||||
from .registry import settings_registry
|
from .registry import settings_registry
|
||||||
|
|
||||||
default_app_config = 'awx.conf.apps.ConfConfig'
|
|
||||||
|
|
||||||
|
|
||||||
def register(setting, **kwargs):
|
def register(setting, **kwargs):
|
||||||
settings_registry.register(setting, **kwargs)
|
settings_registry.register(setting, **kwargs)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
# from django.core import checks
|
# from django.core import checks
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ConfConfig(AppConfig):
|
class ConfConfig(AppConfig):
|
||||||
@@ -12,6 +14,9 @@ class ConfConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
self.module.autodiscover()
|
self.module.autodiscover()
|
||||||
from .settings import SettingsWrapper
|
|
||||||
|
|
||||||
SettingsWrapper.initialize()
|
if not set(sys.argv) & {'migrate', 'check_migrations'}:
|
||||||
|
|
||||||
|
from .settings import SettingsWrapper
|
||||||
|
|
||||||
|
SettingsWrapper.initialize()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.conf import fields, register
|
from awx.conf import fields, register
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.core.validators import URLValidator, _lazy_re_compile
|
from django.core.validators import URLValidator, _lazy_re_compile
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField # noqa
|
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField # noqa
|
||||||
from rest_framework.serializers import PrimaryKeyRelatedField # noqa
|
from rest_framework.serializers import PrimaryKeyRelatedField # noqa
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.constants import CONTAINER_VOLUMES_MOUNT_TYPES, MAX_ISOLATED_PATH_COLON_DELIMITER
|
||||||
|
|
||||||
logger = logging.getLogger('awx.conf.fields')
|
logger = logging.getLogger('awx.conf.fields')
|
||||||
|
|
||||||
# Use DRF fields to convert/validate settings:
|
# Use DRF fields to convert/validate settings:
|
||||||
@@ -62,11 +65,11 @@ class StringListBooleanField(ListField):
|
|||||||
try:
|
try:
|
||||||
if isinstance(value, (list, tuple)):
|
if isinstance(value, (list, tuple)):
|
||||||
return super(StringListBooleanField, self).to_representation(value)
|
return super(StringListBooleanField, self).to_representation(value)
|
||||||
elif value in NullBooleanField.TRUE_VALUES:
|
elif value in BooleanField.TRUE_VALUES:
|
||||||
return True
|
return True
|
||||||
elif value in NullBooleanField.FALSE_VALUES:
|
elif value in BooleanField.FALSE_VALUES:
|
||||||
return False
|
return False
|
||||||
elif value in NullBooleanField.NULL_VALUES:
|
elif value in BooleanField.NULL_VALUES:
|
||||||
return None
|
return None
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
return self.child.to_representation(value)
|
return self.child.to_representation(value)
|
||||||
@@ -79,11 +82,11 @@ class StringListBooleanField(ListField):
|
|||||||
try:
|
try:
|
||||||
if isinstance(data, (list, tuple)):
|
if isinstance(data, (list, tuple)):
|
||||||
return super(StringListBooleanField, self).to_internal_value(data)
|
return super(StringListBooleanField, self).to_internal_value(data)
|
||||||
elif data in NullBooleanField.TRUE_VALUES:
|
elif data in BooleanField.TRUE_VALUES:
|
||||||
return True
|
return True
|
||||||
elif data in NullBooleanField.FALSE_VALUES:
|
elif data in BooleanField.FALSE_VALUES:
|
||||||
return False
|
return False
|
||||||
elif data in NullBooleanField.NULL_VALUES:
|
elif data in BooleanField.NULL_VALUES:
|
||||||
return None
|
return None
|
||||||
elif isinstance(data, str):
|
elif isinstance(data, str):
|
||||||
return self.child.run_validation(data)
|
return self.child.run_validation(data)
|
||||||
@@ -109,6 +112,49 @@ class StringListPathField(StringListField):
|
|||||||
self.fail('type_error', input_type=type(paths))
|
self.fail('type_error', input_type=type(paths))
|
||||||
|
|
||||||
|
|
||||||
|
class StringListIsolatedPathField(StringListField):
|
||||||
|
# Valid formats
|
||||||
|
# '/etc/pki/ca-trust'
|
||||||
|
# '/etc/pki/ca-trust:/etc/pki/ca-trust'
|
||||||
|
# '/etc/pki/ca-trust:/etc/pki/ca-trust:O'
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'type_error': _('Expected list of strings but got {input_type} instead.'),
|
||||||
|
'path_error': _('{path} is not a valid path choice. You must provide an absolute path.'),
|
||||||
|
'mount_error': _('{scontext} is not a valid mount option. Allowed types are {mount_types}'),
|
||||||
|
'syntax_error': _('Invalid syntax. A string HOST-DIR[:CONTAINER-DIR[:OPTIONS]] is expected but got {path}.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_internal_value(self, paths):
|
||||||
|
|
||||||
|
if isinstance(paths, (list, tuple)):
|
||||||
|
for p in paths:
|
||||||
|
if not isinstance(p, str):
|
||||||
|
self.fail('type_error', input_type=type(p))
|
||||||
|
if not p.startswith('/'):
|
||||||
|
self.fail('path_error', path=p)
|
||||||
|
|
||||||
|
if p.count(':'):
|
||||||
|
if p.count(':') > MAX_ISOLATED_PATH_COLON_DELIMITER:
|
||||||
|
self.fail('syntax_error', path=p)
|
||||||
|
try:
|
||||||
|
src, dest, scontext = p.split(':')
|
||||||
|
except ValueError:
|
||||||
|
scontext = 'z'
|
||||||
|
src, dest = p.split(':')
|
||||||
|
finally:
|
||||||
|
for sp in [src, dest]:
|
||||||
|
if not len(sp):
|
||||||
|
self.fail('syntax_error', path=sp)
|
||||||
|
if not sp.startswith('/'):
|
||||||
|
self.fail('path_error', path=sp)
|
||||||
|
if scontext not in CONTAINER_VOLUMES_MOUNT_TYPES:
|
||||||
|
self.fail('mount_error', scontext=scontext, mount_types=CONTAINER_VOLUMES_MOUNT_TYPES)
|
||||||
|
return super(StringListIsolatedPathField, self).to_internal_value(sorted(paths))
|
||||||
|
else:
|
||||||
|
self.fail('type_error', input_type=type(paths))
|
||||||
|
|
||||||
|
|
||||||
class URLField(CharField):
|
class URLField(CharField):
|
||||||
# these lines set up a custom regex that allow numbers in the
|
# these lines set up a custom regex that allow numbers in the
|
||||||
# top-level domain
|
# top-level domain
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import jsonfield.fields
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ class Migration(migrations.Migration):
|
|||||||
('created', models.DateTimeField(default=None, editable=False)),
|
('created', models.DateTimeField(default=None, editable=False)),
|
||||||
('modified', models.DateTimeField(default=None, editable=False)),
|
('modified', models.DateTimeField(default=None, editable=False)),
|
||||||
('key', models.CharField(max_length=255)),
|
('key', models.CharField(max_length=255)),
|
||||||
('value', jsonfield.fields.JSONField(null=True)),
|
('value', awx.main.fields.JSONBlob(null=True)),
|
||||||
(
|
(
|
||||||
'user',
|
'user',
|
||||||
models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True),
|
models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
import awx.main.fields
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
@@ -9,4 +10,4 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [('conf', '0002_v310_copy_tower_settings')]
|
dependencies = [('conf', '0002_v310_copy_tower_settings')]
|
||||||
|
|
||||||
operations = [migrations.AlterField(model_name='setting', name='value', field=awx.main.fields.JSONField(null=True))]
|
operations = [migrations.AlterField(model_name='setting', name='value', field=awx.main.fields.JSONBlob(null=True))]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user