ofs | hex dump | ascii |
---|
0000 | ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff db 00 84 00 06 04 05 06 05 04 06 | ......JFIF...................... |
0020 | 06 05 06 07 07 06 08 0a 10 0a 0a 09 09 0a 14 0e 0f 0c 10 17 14 18 18 17 14 16 16 1a 1d 25 1f 1a | .............................%.. |
0040 | 1b 23 1c 16 16 20 2c 20 23 26 27 29 2a 29 19 1f 2d 30 2d 28 30 25 28 29 28 01 07 07 07 0a 08 0a | .#....,.#&')*)..-0-(0%()(....... |
0060 | 13 0a 0a 13 28 1a 16 1a 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 | ....(...(((((((((((((((((((((((( |
0080 | 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 ff c0 00 11 08 03 | ((((((((((((((((((((((((((...... |
00a0 | 66 05 88 03 01 11 00 02 11 01 03 11 01 ff c4 01 a2 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 | f............................... |
00c0 | 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7d 01 | ..............................}. |
00e0 | 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 a1 08 23 42 b1 c1 15 52 d1 f0 24 | .......!1A..Qa."q.2....#B...R..$ |
0100 | 33 62 72 82 09 0a 16 17 18 19 1a 25 26 27 28 29 2a 34 35 36 37 38 39 3a 43 44 45 46 47 48 49 4a | 3br........%&'()*456789:CDEFGHIJ |
0120 | 53 54 55 56 57 58 59 5a 63 64 65 66 67 68 69 6a 73 74 75 76 77 78 79 7a 83 84 85 86 87 88 89 8a | STUVWXYZcdefghijstuvwxyz........ |
0140 | 92 93 94 95 96 97 98 99 9a a2 a3 a4 a5 a6 a7 a8 a9 aa b2 b3 b4 b5 b6 b7 b8 b9 ba c2 c3 c4 c5 c6 | ................................ |
0160 | c7 c8 c9 ca d2 d3 d4 d5 d6 d7 d8 d9 da e1 e2 e3 e4 e5 e6 e7 e8 e9 ea f1 f2 f3 f4 f5 f6 f7 f8 f9 | ................................ |
0180 | fa 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 11 00 | ................................ |
01a0 | 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 | ..............w.......!1..AQ.aq. |
01c0 | 22 32 81 08 14 42 91 a1 b1 c1 09 23 33 52 f0 15 62 72 d1 0a 16 24 34 e1 25 f1 17 18 19 1a 26 27 | "2...B.....#3R..br...$4.%.....&' |
01e0 | 28 29 2a 35 36 37 38 39 3a 43 44 45 46 47 48 49 4a 53 54 55 56 57 58 59 5a 63 64 65 66 67 68 69 | ()*56789:CDEFGHIJSTUVWXYZcdefghi |
0200 | 6a 73 74 75 76 77 78 79 7a 82 83 84 85 86 87 88 89 8a 92 93 94 95 96 97 98 99 9a a2 a3 a4 a5 a6 | jstuvwxyz....................... |
0220 | a7 a8 a9 aa b2 b3 b4 b5 b6 b7 b8 b9 ba c2 c3 c4 c5 c6 c7 c8 c9 ca d2 d3 d4 d5 d6 d7 d8 d9 da e2 | ................................ |
0240 | e3 e4 e5 e6 e7 e8 e9 ea f2 f3 f4 f5 f6 f7 f8 f9 fa ff da 00 0c 03 01 00 02 11 03 11 00 3f 00 fa | .............................?.. |
0260 | a6 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 | ....(......(......(......(...... |
0280 | 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 | (......(......(......(......(... |
02a0 | 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 | ...(......(......(......(......( |
02c0 | 00 a0 02 96 e0 14 58 06 83 49 b8 ad c2 f7 02 45 4d e1 dc 76 0c fd 29 de 3e 42 d4 33 f4 a2 f1 f2 | ......X..I.....EM..v..).>B.3.... |
02e0 | 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f 1f | .C?J/...3.....C?J/...3.....C?J/. |
0300 | 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 | ..3.....C?J/...3.....C?J/...3... |
0320 | f2 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f 1f 20 d4 33 f4 a2 f1 f2 0d 43 3f 4a 2f | ..C?J/...3.....C?J/...3.....C?J/ |
0340 | 1f 20 d4 33 f4 a2 f1 f2 0d 40 7b 50 b9 57 c2 16 14 1a a5 77 b8 ae 2d 31 85 00 14 00 50 01 40 05 | ...3.....@{P.W.....w..-1....P.@. |
0360 | 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 | ...P.@....P.@....P.@....P.@....P |
0380 | 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 | .@....P.@....P.@....P.@....P.@.. |
03a0 | 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 | ..P.@....P.@....P.@....P.@....P. |
03c0 | 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 | @....P.@....P.@....P.@....P.@... |
03e0 | 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 | .P.@....P.@....P.@....P.@....P.@ |
0400 | 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 | ....P.@....P.@....P.@....P.@.... |
0420 | 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 | P.@....P.@....P.@....P.@....P.@. |
0440 | 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 | ...P.@....P.@....P.@....P.@....P |
0460 | 01 40 05 24 04 73 4a b0 c6 cf 23 05 45 19 24 d6 75 2a 72 81 cc dc 6a b7 77 d2 98 ac 41 8e 21 fc | .@.$.sJ...#.E.$.u*r...j.w...A.!. |
0480 | 5d ff 00 13 da be 43 19 9e d4 9a b2 56 3a 15 2b 10 ff 00 64 49 2f 33 dc e5 be 99 af 02 55 aa d5 | ].....C.....V:.+...dI/3......U.. |
04a0 | 77 e7 b1 4a 36 0f ec 5f fa 78 ff 00 c8 7f fd 7a 5c f3 ee cb d0 3f b1 7f e9 e3 ff 00 21 ff 00 f5 | w..J6.._.x.....z\....?......!... |
04c0 | e8 e7 9f 76 1a 07 f6 2f fd 3c 7f e4 3f fe bd 1c f3 ee c3 40 fe c5 ff 00 a7 8f fc 87 ff 00 d7 a3 | ...v.../.<..?......@............ |
04e0 | 9e 7d d8 68 1f d8 bf f4 f1 ff 00 90 ff 00 fa f4 73 cf bb 0d 03 fb 17 fe 9e 3f f2 1f ff 00 5e 8e | .}.h............s........?....^. |
0500 | 79 f7 61 a0 7f 62 ff 00 d3 c7 fe 43 ff 00 eb d1 cf 3e ec 34 0f ec 5f fa 78 ff 00 c8 7f fd 7a 39 | y.a..b.....C.....>.4.._.x.....z9 |
0520 | e7 dd 86 81 fd 8b ff 00 4f 1f f9 0f ff 00 af 47 3c fb b0 d0 3f b1 7f e9 e3 ff 00 21 ff 00 f5 e8 | ........O......G<...?......!.... |
0540 | e7 9f 76 1a 07 f6 2f fd 3c 7f e4 3f fe bd 1c f3 ee c3 40 fe c5 ff 00 a7 8f fc 87 ff 00 d7 a3 9e | ..v.../.<..?......@............. |
0560 | 7d d8 68 1f d8 bf f4 f1 ff 00 90 ff 00 fa f4 73 cf bb 0d 03 fb 17 fe 9e 3f f2 1f ff 00 5e 8e 79 | }.h............s........?....^.y |
0580 | f7 61 a0 7f 62 ff 00 d3 c7 fe 43 ff 00 eb d1 cf 3e ec 34 0f ec 5f fa 78 ff 00 c8 7f fd 7a 39 e7 | .a..b.....C.....>.4.._.x.....z9. |
05a0 | dd 86 81 fd 8b ff 00 4f 1f f9 0f ff 00 af 47 3c fb b0 d0 3f b1 88 e5 6e 39 ff 00 73 ff 00 af 47 | .......O......G<...?...n9..s...G |
05c0 | 3c fb b0 d0 72 5c 6a 1a 63 06 2d e7 43 e8 4e 47 ff 00 5a bd 5c 1e 6f 52 84 ad 51 91 2a 68 e8 b4 | <...r\j.c.-.C.NG..Z.\.oR..Q.*h.. |
05e0 | fb d8 ef a0 12 45 ff 00 02 53 d4 1a fb 2c 26 32 38 a8 dd 1c d2 8d 8b 95 da 20 a0 02 80 0a 00 28 | .....E...S...,&28..............( |
0600 | 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 | ......(......(......(......(.... |
0620 | 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 | ..(......(......(......(......(. |
0640 | a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a | .....(......(......(......(..... |
0660 | 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 | .(......(......(......(......(.. |
0680 | 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 | ....(......(......(......(...... |
06a0 | 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 | (......(......(......(......(... |
06c0 | 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 | ...(......(......(......(......( |
06e0 | 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 | ......(......(......(......(.... |
0700 | 0a 00 28 01 0f 5a 95 a3 03 9a f1 2c ed 2c f0 d9 c6 7a e0 9f af 6a f9 3c fa ba b5 3a 7d 99 ac 11 | ..(..Z.....,.,...z...j.<...:}... |
0720 | 6a d2 05 82 25 8d 07 03 bf ad 7c c5 3e 6a 8e c7 4b 2d 08 c7 7a f4 a9 e0 54 96 a4 39 0b b1 7d 2b | j...%.....|.>j..K-..z...T..9..}+ |
0740 | 4f aa 51 15 d8 6c 5f 4a 3e a9 44 2e c3 62 fa 51 f5 4a 21 76 1b 17 d2 8f aa 51 0b b0 d8 be 94 7d | O.Q..l_J>.D..b.Q.J!v.....Q.....} |
0760 | 52 88 5d 86 c5 f4 a3 ea 94 42 ec 36 2f a5 1f 54 a2 17 61 b1 7d 28 fa a5 10 bb 0d 8b e9 47 d5 28 | R.]......B.6/..T..a.}(.......G.( |
0780 | 85 d8 6c 5f 4a 3e a9 44 2e c3 62 fa 51 f5 4a 21 76 1b 17 d2 8f aa 51 0b b0 d8 be 94 7d 52 88 5d | ..l_J>.D..b.Q.J!v.....Q.....}R.] |
07a0 | 86 c5 f4 a3 ea 94 42 ec 36 2f a5 1f 54 a2 17 61 b1 7d 28 fa a5 10 bb 0d 8b e9 47 d5 28 85 d8 9b | ......B.6/..T..a.}(.......G.(... |
07c0 | 05 1f 55 a2 3b 91 c9 1f 50 46 41 af 3a ad 07 4e 5c c5 26 62 db 31 d3 75 95 00 fe e2 53 8f c0 ff | ..U.;...PFA.:..N\.&b.1.u....S... |
07e0 | 00 81 ae fc 9b 16 a8 57 e6 66 73 47 60 39 15 fa 09 cc 14 00 50 01 40 05 00 14 00 50 01 40 05 00 | .......W.fsG`9......P.@....P.@.. |
0800 | 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 | ..P.@....P.@....P.@....P.@....P. |
0820 | 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 | @....P.@....P.@....P.@....P.@... |
0840 | 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 | .P.@....P.@....P.@....P.@....P.@ |
0860 | 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 | ....P.@....P.@....P.@....P.@.... |
0880 | 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 | P.@....P.@....P.@....P.@....P.@. |
08a0 | 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 | ...P.@....P.@....P.@....P.@....P |
08c0 | 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 | .@....P.@....P.@....P.@....P.@.. |
08e0 | 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 14 00 50 01 40 05 00 34 f4 a9 fb | ..P.@....P.@....P.@....P.@..4... |
0900 | 40 8e 52 73 bf c4 c7 3d bf f8 8a f8 0c e5 b9 63 aa 43 b2 3a a9 a3 6a 2c 13 58 61 1c 5b 1b 25 35 | @.Rs...=.......c.C.:..j,.Xa.[.%5 |
0920 | e9 ca e9 68 48 9c 51 fb b0 0e 28 fd d8 07 14 7e ec 03 8a 3f 76 01 c5 1f bb 00 e2 8f dd 80 71 47 | ...hH.Q...(....~...?v.........qG |
0940 | ee c0 38 a3 f7 60 1c 51 fb b0 0e 28 fd d8 07 14 7e ec 03 8a 3f 76 01 c5 1f bb 00 e2 8f dd 80 71 | ..8..`.Q...(....~...?v.........q |
0960 | 47 ee c0 38 a3 f7 60 1c 51 fb b0 0e 28 fd d8 0c 97 ee 57 1e 3b f8 57 1c 0c 1d 7f e5 36 ec 3a f3 | G..8..`.Q...(.....W.;.W.....6.:. |
0980 | fd 2b cb 84 5c 69 f3 a2 99 d6 42 73 18 af d5 4e 32 4a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a | .+..\i....Bs...N2J.(......(..... |
09a0 | 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 | .(......(......(......(......(.. |
09c0 | 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 | ....(......(......(......(...... |
09e0 | 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 | (......(......(......(......(... |
0a00 | 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 | ...(......(......(......(......( |
0a20 | 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 | ......(......(......(......(.... |
0a40 | 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 | ..(......(......(......(......(. |
0a60 | a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a | .....(......(......(......(..... |
0a80 | 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 10 f4 a4 c6 72 | .(......(......(......(........r |
0aa0 | 73 ff 00 c8 ca df 5f fd 92 bf 3f ce ff 00 de aa 9d 14 cd a8 7b d6 38 0d 8a 91 2d 7a 4a 5a 90 82 | s....._...?.........{.8...-zJZ.. |
0ac0 | 90 c2 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 0a 00 28 00 a0 02 80 19 | .....(......(......(......(..... |
0ae0 | 27 dc 35 86 29 fe e0 6b 73 9f f1 07 4b 7f f8 17 f4 af 1a 1f 01 6c eb 2d ff 00 d5 8a fd 58 e2 25 | '.5.)..ks...K........l.-.....X.% |
0b00 | .highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */#!/usr/bin/env python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
import traceback
import yaml
required_params = ['EndpointMap', 'ServiceNetMap', 'DefaultPasswords',
'RoleName', 'RoleParameters', 'ServiceData']
# NOTE(bnemec): The duplication in this list is intentional. For the
# transition to generated environments we have two copies of these files,
# so they need to be listed twice. Once the deprecated version can be removed
# the duplicate entries can be as well.
envs_containing_endpoint_map = ['tls-endpoints-public-dns.yaml',
'tls-endpoints-public-ip.yaml',
'tls-everywhere-endpoints-dns.yaml',
'tls-endpoints-public-dns.yaml',
'tls-endpoints-public-ip.yaml',
'tls-everywhere-endpoints-dns.yaml']
ENDPOINT_MAP_FILE = 'endpoint_map.yaml'
OPTIONAL_SECTIONS = ['service_workflow_tasks']
REQUIRED_DOCKER_SECTIONS = ['service_name', 'docker_config', 'puppet_config',
'config_settings', 'step_config']
OPTIONAL_DOCKER_SECTIONS = ['docker_puppet_tasks', 'upgrade_tasks',
'service_config_settings', 'host_prep_tasks',
'metadata_settings', 'kolla_config']
REQUIRED_DOCKER_PUPPET_CONFIG_SECTIONS = ['config_volume', 'step_config',
'config_image']
OPTIONAL_DOCKER_PUPPET_CONFIG_SECTIONS = [ 'puppet_tags', 'volumes' ]
# Mapping of parameter names to a list of the fields we should _not_ enforce
# consistency across files on. This should only contain parameters whose
# definition we cannot change for backwards compatibility reasons. New
# parameters to the templates should not be added to this list.
PARAMETER_DEFINITION_EXCLUSIONS = {'ManagementNetCidr': ['default'],
'ManagementAllocationPools': ['default'],
'ExternalNetCidr': ['default'],
'ExternalAllocationPools': ['default'],
'StorageNetCidr': ['default'],
'StorageAllocationPools': ['default'],
'StorageMgmtNetCidr': ['default',
# FIXME
'description'],
'StorageMgmtAllocationPools': ['default'],
'TenantNetCidr': ['default'],
'TenantAllocationPools': ['default'],
'InternalApiNetCidr': ['default'],
'UpdateIdentifier': ['description'],
# TODO(bnemec): Address these existing
# inconsistencies.
'NeutronMetadataProxySharedSecret': [
'description', 'hidden'],
'ServiceNetMap': ['description', 'default'],
'EC2MetadataIp': ['default'],
'network': ['default'],
'ControlPlaneIP': ['default',
'description'],
'ControlPlaneIp': ['default',
'description'],
'NeutronBigswitchLLDPEnabled': ['default'],
'NeutronEnableL2Pop': ['description'],
'NeutronWorkers': ['description'],
'TenantIpSubnet': ['description'],
'ExternalNetName': ['description'],
'ControlPlaneDefaultRoute': ['default'],
'StorageMgmtNetName': ['description'],
'ServerMetadata': ['description'],
'InternalApiIpUri': ['description'],
'UpgradeLevelNovaCompute': ['default'],
'StorageMgmtIpUri': ['description'],
'server': ['description'],
'servers': ['description'],
'FixedIPs': ['description'],
'ExternalIpSubnet': ['description'],
'NeutronBridgeMappings': ['description'],
'ExtraConfig': ['description'],
'InternalApiIpSubnet': ['description'],
'DefaultPasswords': ['description',
'default'],
'BondInterfaceOvsOptions': ['description',
'default',
'constraints'],
'KeyName': ['constraints'],
'TenantNetName': ['description'],
'StorageIpSubnet': ['description'],
'OVNSouthboundServerPort': ['description'],
'ExternalInterfaceDefaultRoute':
['description', 'default'],
'ExternalIpUri': ['description'],
'IPPool': ['description'],
'ControlPlaneNetwork': ['description'],
'SSLCertificate': ['description',
'default',
'hidden'],
'HostCpusList': ['default', 'constraints'],
'InternalApiAllocationPools': ['default'],
'NodeIndex': ['description'],
'name': ['description', 'default'],
'StorageNetName': ['description'],
'ManagementNetName': ['description'],
'NeutronPublicInterface': ['description'],
'RoleParameters': ['description'],
'ManagementInterfaceDefaultRoute':
['default'],
'image': ['description', 'default'],
'NeutronBigswitchAgentEnabled': ['default'],
'EndpointMap': ['description', 'default'],
'DockerManilaConfigImage': ['description',
'default'],
'NetworkName': ['default', 'description'],
'StorageIpUri': ['description'],
'InternalApiNetName': ['description'],
'NeutronTunnelTypes': ['description'],
'replacement_policy': ['default'],
'StorageMgmtIpSubnet': ['description'],
'CloudDomain': ['description', 'default'],
'key_name': ['default', 'description'],
'EnableLoadBalancer': ['description'],
'ControllerExtraConfig': ['description'],
'NovaComputeExtraConfig': ['description'],
'controllerExtraConfig': ['description'],
'DockerSwiftConfigImage': ['default'],
}
PREFERRED_CAMEL_CASE = {
'ec2api': 'Ec2Api',
'haproxy': 'HAProxy',
}
def exit_usage():
print('Usage %s <yaml file or directory>' % sys.argv[0])
sys.exit(1)
def to_camel_case(string):
return PREFERRED_CAMEL_CASE.get(string, ''.join(s.capitalize() or '_' for
s in string.split('_')))
def get_base_endpoint_map(filename):
try:
tpl = yaml.load(open(filename).read())
return tpl['parameters']['EndpointMap']['default']
except Exception:
print(traceback.format_exc())
return None
def get_endpoint_map_from_env(filename):
try:
tpl = yaml.load(open(filename).read())
return {
'file': filename,
'map': tpl['parameter_defaults']['EndpointMap']
}
except Exception:
print(traceback.format_exc())
return None
def validate_endpoint_map(base_map, env_map):
return sorted(base_map.keys()) == sorted(env_map.keys())
def validate_hci_compute_services_default(env_filename, env_tpl):
env_services_list = env_tpl['parameter_defaults']['ComputeServices']
env_services_list.remove('OS::TripleO::Services::CephOSD')
roles_filename = os.path.join(os.path.dirname(env_filename),
'../roles/Compute.yaml')
roles_tpl = yaml.load(open(roles_filename).read())
for role in roles_tpl:
if role['name'] == 'Compute':
roles_services_list = role['ServicesDefault']
if sorted(env_services_list) != sorted(roles_services_list):
print('ERROR: ComputeServices in %s is different from '
'ServicesDefault in roles/Compute.yaml' % env_filename)
return 1
return 0
def validate_hci_computehci_role(hci_role_filename, hci_role_tpl):
compute_role_filename = os.path.join(os.path.dirname(hci_role_filename),
'./Compute.yaml')
compute_role_tpl = yaml.load(open(compute_role_filename).read())
compute_role_services = compute_role_tpl[0]['ServicesDefault']
for role in hci_role_tpl:
if role['name'] == 'ComputeHCI':
hci_role_services = role['ServicesDefault']
hci_role_services.remove('OS::TripleO::Services::CephOSD')
if sorted(hci_role_services) != sorted(compute_role_services):
print('ERROR: ServicesDefault in %s is different from'
'ServicesDefault in roles/Compute.yaml' % hci_role_filename)
return 1
return 0
def search(item, check_item, check_key):
if check_item(item):
return True
elif isinstance(item, list):
for i in item:
if search(i, check_item, check_key):
return True
elif isinstance(item, dict):
for k in item.keys():
if check_key(k, item[k]):
return True
elif search(item[k], check_item, check_key):
return True
return False
def validate_mysql_connection(settings):
no_op = lambda *args: False
error_status = [0]
def mysql_protocol(items):
return items == ['EndpointMap', 'MysqlInternal', 'protocol']
def client_bind_address(item):
return 'read_default_file' in item and \
'read_default_group' in item
def validate_mysql_uri(key, items):
# Only consider a connection if it targets mysql
if key.endswith('connection') and \
search(items, mysql_protocol, no_op):
# Assume the "bind_address" option is one of
# the token that made up the uri
if not search(items, client_bind_address, no_op):
error_status[0] = 1
return False
search(settings, no_op, validate_mysql_uri)
return error_status[0]
def validate_docker_service_mysql_usage(filename, tpl):
no_op = lambda *args: False
included_res = []
def match_included_res(item):
is_config_setting = isinstance(item, list) and len(item) > 1 and \
item[1:] == ['role_data', 'config_settings']
if is_config_setting:
included_res.append(item[0])
return is_config_setting
def match_use_mysql_protocol(items):
return items == ['EndpointMap', 'MysqlInternal', 'protocol']
all_content = []
def read_all(incfile, inctpl):
# search for included content
content = inctpl['outputs']['role_data']['value'].get('config_settings',{})
all_content.append(content)
included_res[:] = []
if search(content, match_included_res, no_op):
files = [inctpl['resources'][x]['type'] for x in included_res]
# parse included content
for r, f in zip(included_res, files):
# disregard class names, only consider file names
if 'OS::' in f:
continue
newfile = os.path.normpath(os.path.dirname(incfile)+'/'+f)
newtmp = yaml.load(open(newfile).read())
read_all(newfile, newtmp)
read_all(filename, tpl)
if search(all_content, match_use_mysql_protocol, no_op):
# ensure this service includes the mysqlclient service
resources = tpl['resources']
mysqlclient = [x for x in resources
if resources[x]['type'].endswith('mysql-client.yaml')]
if len(mysqlclient) == 0:
print("ERROR: containerized service %s uses mysql but "
"resource mysql-client.yaml is not used"
% filename)
return 1
# and that mysql::client puppet module is included in puppet-config
match_mysqlclient = \
lambda x: x == [mysqlclient[0], 'role_data', 'step_config']
role_data = tpl['outputs']['role_data']
puppet_config = role_data['value']['puppet_config']['step_config']
if not search(puppet_config, match_mysqlclient, no_op):
print("ERROR: containerized service %s uses mysql but "
"puppet_config section does not include "
"::tripleo::profile::base::database::mysql::client"
% filename)
return 1
return 0
def validate_docker_service(filename, tpl):
if 'outputs' in tpl and 'role_data' in tpl['outputs']:
if 'value' not in tpl['outputs']['role_data']:
print('ERROR: invalid role_data for filename: %s'
% filename)
return 1
role_data = tpl['outputs']['role_data']['value']
for section_name in REQUIRED_DOCKER_SECTIONS:
if section_name not in role_data:
print('ERROR: %s is required in role_data for %s.'
% (section_name, filename))
return 1
for section_name in role_data.keys():
if section_name in REQUIRED_DOCKER_SECTIONS:
continue
else:
if section_name in OPTIONAL_DOCKER_SECTIONS:
continue
elif section_name in OPTIONAL_SECTIONS:
continue
else:
print('ERROR: %s is extra in role_data for %s.'
% (section_name, filename))
return 1
if 'puppet_config' in role_data:
if validate_docker_service_mysql_usage(filename, tpl):
print('ERROR: could not validate use of mysql service for %s.'
% filename)
return 1
puppet_config = role_data['puppet_config']
for key in puppet_config:
if key in REQUIRED_DOCKER_PUPPET_CONFIG_SECTIONS:
continue
else:
if key in OPTIONAL_DOCKER_PUPPET_CONFIG_SECTIONS:
continue
else:
print('ERROR: %s should not be in puppet_config section.'
% key)
return 1
for key in REQUIRED_DOCKER_PUPPET_CONFIG_SECTIONS:
if key not in puppet_config:
print('ERROR: %s is required in puppet_config for %s.'
% (key, filename))
return 1
config_volume = puppet_config.get('config_volume')
expected_config_image_parameter = "Docker%sConfigImage" % to_camel_case(config_volume)
if config_volume and not expected_config_image_parameter in tpl.get('parameters', []):
print('ERROR: Missing %s heat parameter for %s config_volume.'
% (expected_config_image_parameter, config_volume))
return 1
if 'docker_config' in role_data:
docker_config = role_data['docker_config']
for _, step in docker_config.items():
if not isinstance(step, dict):
# NOTE(mandre) this skips everything that is not a dict
# so we may ignore some containers definitions if they
# are in a map_merge for example
continue
for _, container in step.items():
if not isinstance(container, dict):
continue
command = container.get('command', '')
if isinstance(command, list):
command = ' '.join(map(str, command))
if 'bootstrap_host_exec' in command \
and container.get('user') != 'root':
print('ERROR: bootstrap_host_exec needs to run as the root user.')
return 1
if 'parameters' in tpl:
for param in required_params:
if param not in tpl['parameters']:
print('ERROR: parameter %s is required for %s.'
% (param, filename))
return 1
return 0
def validate_service(filename, tpl):
if 'outputs' in tpl and 'role_data' in tpl['outputs']:
if 'value' not in tpl['outputs']['role_data']:
print('ERROR: invalid role_data for filename: %s'
% filename)
return 1
role_data = tpl['outputs']['role_data']['value']
if 'service_name' not in role_data:
print('ERROR: service_name is required in role_data for %s.'
% filename)
return 1
# service_name must match the filename, but with an underscore
if (role_data['service_name'] !=
os.path.basename(filename).split('.')[0].replace("-", "_")):
print('ERROR: service_name should match file name for service: %s.'
% filename)
return 1
# if service connects to mysql, the uri should use option
# bind_address to avoid issues with VIP failover
if 'config_settings' in role_data and \
validate_mysql_connection(role_data['config_settings']):
print('ERROR: mysql connection uri should use option bind_address')
return 1
if 'parameters' in tpl:
for param in required_params:
if param not in tpl['parameters']:
print('ERROR: parameter %s is required for %s.'
% (param, filename))
return 1
return 0
def validate(filename, param_map):
"""Validate a Heat template
:param filename: The path to the file to validate
:param param_map: A dict which will be populated with the details of the
parameters in the template. The dict will have the
following structure:
{'ParameterName': [
{'filename': ./file1.yaml,
'data': {'description': '',
'type': string,
'default': '',
...}
},
{'filename': ./file2.yaml,
'data': {'description': '',
'type': string,
'default': '',
...}
},
...
]}
"""
print('Validating %s' % filename)
retval = 0
try:
tpl = yaml.load(open(filename).read())
# The template alias version should be used instead a date, this validation
# will be applied to all templates not just for those in the services folder.
if 'heat_template_version' in tpl and not str(tpl['heat_template_version']).isalpha():
print('ERROR: heat_template_version needs to be the release alias not a date: %s'
% filename)
return 1
# qdr aliases rabbitmq service to provide alternative messaging backend
if (filename.startswith('./puppet/services/') and
filename not in ['./puppet/services/qdr.yaml']):
retval = validate_service(filename, tpl)
if filename.startswith('./docker/services/'):
retval = validate_docker_service(filename, tpl)
if filename.endswith('hyperconverged-ceph.yaml'):
retval = validate_hci_compute_services_default(filename, tpl)
if filename.startswith('./roles/ComputeHCI.yaml'):
retval = validate_hci_computehci_role(filename, tpl)
except Exception:
print(traceback.format_exc())
return 1
# yaml is OK, now walk the parameters and output a warning for unused ones
if 'heat_template_version' in tpl:
for p, data in tpl.get('parameters', {}).items():
definition = {'data': data, 'filename': filename}
param_map.setdefault(p, []).append(definition)
if p in required_params:
continue
str_p = '\'%s\'' % p
in_resources = str_p in str(tpl.get('resources', {}))
in_outputs = str_p in str(tpl.get('outputs', {}))
if not in_resources and not in_outputs:
print('Warning: parameter %s in template %s '
'appears to be unused' % (p, filename))
return retval
if len(sys.argv) < 2:
exit_usage()
path_args = sys.argv[1:]
exit_val = 0
failed_files = []
base_endpoint_map = None
env_endpoint_maps = list()
param_map = {}
for base_path in path_args:
if os.path.isdir(base_path):
for subdir, dirs, files in os.walk(base_path):
if '.tox' in dirs:
dirs.remove('.tox')
for f in files:
if f.endswith('.yaml') and not f.endswith('.j2.yaml'):
file_path = os.path.join(subdir, f)
failed = validate(file_path, param_map)
if failed:
failed_files.append(file_path)
exit_val |= failed
if f == ENDPOINT_MAP_FILE:
base_endpoint_map = get_base_endpoint_map(file_path)
if f in envs_containing_endpoint_map:
env_endpoint_map = get_endpoint_map_from_env(file_path)
if env_endpoint_map:
env_endpoint_maps.append(env_endpoint_map)
elif os.path.isfile(base_path) and base_path.endswith('.yaml'):
failed = validate(base_path, param_map)
if failed:
failed_files.append(base_path)
exit_val |= failed
else:
print('Unexpected argument %s' % base_path)
exit_usage()
if base_endpoint_map and \
len(env_endpoint_maps) == len(envs_containing_endpoint_map):
for env_endpoint_map in env_endpoint_maps:
matches = validate_endpoint_map(base_endpoint_map,
env_endpoint_map['map'])
if not matches:
print("ERROR: %s needs to be updated to match changes in base "
"endpoint map" % env_endpoint_map['file'])
failed_files.append(env_endpoint_map['file'])
exit_val |= 1
else:
print("%s matches base endpoint map" % env_endpoint_map['file'])
else:
print("ERROR: Did not find expected number of environments containing the "
"EndpointMap parameter. If you meant to add or remove one of these "
"environments then you also need to update this tool.")
if not base_endpoint_map:
failed_files.append(ENDPOINT_MAP_FILE)
if len(env_endpoint_maps) != len(envs_containing_endpoint_map):
matched_files = set(os.path.basename(matched_env_file['file'])
for matched_env_file in env_endpoint_maps)
failed_files.extend(set(envs_containing_endpoint_map) - matched_files)
exit_val |= 1
# Validate that duplicate parameters defined in multiple files all have the
# same definition.
mismatch_count = 0
for p, defs in param_map.items():
# Nothing to validate if the parameter is only defined once
if len(defs) == 1:
continue
check_data = [d['data'] for d in defs]
# Override excluded fields so they don't affect the result
exclusions = PARAMETER_DEFINITION_EXCLUSIONS.get(p, [])
ex_dict = {}
for field in exclusions:
ex_dict[field] = 'IGNORED'
for d in check_data:
d.update(ex_dict)
# If all items in the list are not == the first, then the check fails
if check_data.count(check_data[0]) != len(check_data):
mismatch_count += 1
exit_val |= 1
failed_files.extend([d['filename'] for d in defs])
print('Mismatched parameter definitions found for "%s"' % p)
print('Definitions found:')
for d in defs:
print(' %s:\n %s' % (d['filename'], d['data']))
print('Mismatched parameter definitions: %d' % mismatch_count)
if failed_files:
print('Validation failed on:')
for f in failed_files:
print(f)
else:
print('Validation successful!')
sys.exit(exit_val)
|