mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 08:32:16 +01:00
Compare commits
1081 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe1b0020b8 | ||
|
|
b47b9f45d8 | ||
|
|
8c0ad887f2 | ||
|
|
45b9f32351 | ||
|
|
ace379a92c | ||
|
|
4998870723 | ||
|
|
64c28dd45e | ||
|
|
68e0f88d5b | ||
|
|
cc97cb7e59 | ||
|
|
200334197f | ||
|
|
ce9c48b2b0 | ||
|
|
05465b3a74 | ||
|
|
1c0ff24c4b | ||
|
|
8f711b0f99 | ||
|
|
430f95f79f | ||
|
|
259d80bb68 | ||
|
|
7a914fce65 | ||
|
|
a2a8c42457 | ||
|
|
4b192b1498 | ||
|
|
c204cadfc5 | ||
|
|
35f1a990a6 | ||
|
|
7729bd0590 | ||
|
|
2980ca97a3 | ||
|
|
bb95b9b7f3 | ||
|
|
62d98fef79 | ||
|
|
db51f9026d | ||
|
|
3187980ead | ||
|
|
eff9444294 | ||
|
|
999dd2db40 | ||
|
|
f44d59d514 | ||
|
|
092b526ab5 | ||
|
|
0b5eaff8d9 | ||
|
|
46e190970f | ||
|
|
55d19522a8 | ||
|
|
924576d3d4 | ||
|
|
405d8d1c2b | ||
|
|
1b1e100107 | ||
|
|
c3b4702424 | ||
|
|
866ae53436 | ||
|
|
53e6a0ecf8 | ||
|
|
eab3d2da12 | ||
|
|
3f01ca18d3 | ||
|
|
405f1069c0 | ||
|
|
f1d1215c7c | ||
|
|
9bf12e7cb9 | ||
|
|
b87a6d4dfb | ||
|
|
a80191a288 | ||
|
|
e4a43434dc | ||
|
|
4811a00543 | ||
|
|
cfb48d9af4 | ||
|
|
8b65315a09 | ||
|
|
20522fd1ac | ||
|
|
e28e2ebf0f | ||
|
|
15050a311b | ||
|
|
c99eb471f9 | ||
|
|
53c2f97008 | ||
|
|
61cadd0e6a | ||
|
|
813926cd47 | ||
|
|
f447e31ae2 | ||
|
|
00c6ed0387 | ||
|
|
cdf861bcfd | ||
|
|
5279d73928 | ||
|
|
e6d2a5640b | ||
|
|
4387a4f122 | ||
|
|
bf0d0a4f40 | ||
|
|
f8809b3943 | ||
|
|
9e5b05ad3c | ||
|
|
452a500955 | ||
|
|
1ab0117943 | ||
|
|
5be574081c | ||
|
|
6430bc6a5c | ||
|
|
e0eff40f74 | ||
|
|
385cbe6590 | ||
|
|
043b1588de | ||
|
|
e5d861def3 | ||
|
|
72bc6f895a | ||
|
|
7549a69f71 | ||
|
|
ebf0548d19 | ||
|
|
d3a65a587c | ||
|
|
8f7c1c98ad | ||
|
|
40d75882a2 | ||
|
|
bb91259829 | ||
|
|
0b33be17a4 | ||
|
|
3a78b89c97 | ||
|
|
c72be765b0 | ||
|
|
4e9f63004d | ||
|
|
08088c15f4 | ||
|
|
8625a4e899 | ||
|
|
06b2b2e10c | ||
|
|
bd6d92be9b | ||
|
|
ed22f1e5e6 | ||
|
|
8e3acc8d0a | ||
|
|
51afa5ba85 | ||
|
|
d67500ee05 | ||
|
|
0bec14c6ac | ||
|
|
3b0eb06991 | ||
|
|
410c1dfd7f | ||
|
|
b98378311f | ||
|
|
e6306e7c2f | ||
|
|
0663e5f92b | ||
|
|
21e300fcec | ||
|
|
7fb4c89832 | ||
|
|
1e26fdd35b | ||
|
|
19db80bc2e | ||
|
|
4ac493250c | ||
|
|
ec9d960538 | ||
|
|
5fa6603fa4 | ||
|
|
d81517bf26 | ||
|
|
9af0f2896c | ||
|
|
f619ce09d6 | ||
|
|
58c41dd01f | ||
|
|
ec3e415bf0 | ||
|
|
230cb8da2c | ||
|
|
59f96358c4 | ||
|
|
874e78c6cd | ||
|
|
caea81501d | ||
|
|
1578253fb1 | ||
|
|
fec6437c5d | ||
|
|
edff24913a | ||
|
|
5120b49639 | ||
|
|
0e13cc3ed8 | ||
|
|
cb49e8e4d1 | ||
|
|
3113e034f6 | ||
|
|
85291fdec3 | ||
|
|
57e1b7efda | ||
|
|
be78a71bb7 | ||
|
|
2baa59cb3a | ||
|
|
aea7c5952a | ||
|
|
cbde321d3b | ||
|
|
aa5de988cf | ||
|
|
fd485a0d3e | ||
|
|
aea06a98b7 | ||
|
|
82401bdfdd | ||
|
|
12c25a2d21 | ||
|
|
241d5077c9 | ||
|
|
37757315d0 | ||
|
|
1195c37508 | ||
|
|
3d9b2deb59 | ||
|
|
32d948554f | ||
|
|
b7a6b7aba2 | ||
|
|
05e15a1d06 | ||
|
|
f57ecb6ef2 | ||
|
|
45318497d2 | ||
|
|
b762143d75 | ||
|
|
ba1a730d3a | ||
|
|
463bb2f91c | ||
|
|
e14cab0c8b | ||
|
|
3220ca4140 | ||
|
|
92de0cf6fe | ||
|
|
d111591c8b | ||
|
|
935686b9e3 | ||
|
|
b1ad21c7d3 | ||
|
|
4f800c8a76 | ||
|
|
5c498219ed | ||
|
|
0b211c39eb | ||
|
|
77b9776d9c | ||
|
|
04e596b616 | ||
|
|
9fba76fe5c | ||
|
|
8b9858aa66 | ||
|
|
2f7218b09c | ||
|
|
0a05091241 | ||
|
|
507d0cb2e2 | ||
|
|
fb61948244 | ||
|
|
15fdc99ec0 | ||
|
|
dd07c73c28 | ||
|
|
43354cafb1 | ||
|
|
b2c3187690 | ||
|
|
4a788ce9d8 | ||
|
|
f9ad8bd2d5 | ||
|
|
4429aea8a4 | ||
|
|
7a60e82d03 | ||
|
|
3544e9c355 | ||
|
|
ca4f0141c6 | ||
|
|
fcaa26a84d | ||
|
|
ea103d3070 | ||
|
|
286efbe548 | ||
|
|
a0ee800002 | ||
|
|
f4d3fba306 | ||
|
|
d5a58cfcb5 | ||
|
|
0e6bb27ecf | ||
|
|
83fdc01df1 | ||
|
|
c7ccabc6cb | ||
|
|
3eefe33e64 | ||
|
|
c44dc7f0ab | ||
|
|
1639d82348 | ||
|
|
b31e520731 | ||
|
|
e85d4143c0 | ||
|
|
da20f04e25 | ||
|
|
9a49ea426e | ||
|
|
c0c4c21682 | ||
|
|
c7424dfcb3 | ||
|
|
e33ca6af12 | ||
|
|
9dc6e308b6 | ||
|
|
4fe2ff5553 | ||
|
|
e5ccb82db8 | ||
|
|
63ae987010 | ||
|
|
efa0d3fd75 | ||
|
|
fd5930bc56 | ||
|
|
a057c6253b | ||
|
|
ed5d1fa891 | ||
|
|
76d36d18be | ||
|
|
a78c433a60 | ||
|
|
aff7530cb0 | ||
|
|
e7e7267fc2 | ||
|
|
9aaab2de3b | ||
|
|
8f0121a92d | ||
|
|
beb1e386c2 | ||
|
|
5b84a5e0e9 | ||
|
|
a9613651a5 | ||
|
|
d6ebc7f230 | ||
|
|
84ca118524 | ||
|
|
0ad6623e24 | ||
|
|
ba944f9dc2 | ||
|
|
99307befdd | ||
|
|
638c4e6b23 | ||
|
|
18bb7ef00d | ||
|
|
3dd9ce1fcd | ||
|
|
fd4814194c | ||
|
|
4d526fe419 | ||
|
|
50d1b187bb | ||
|
|
f54f78d186 | ||
|
|
97ec4949d7 | ||
|
|
f08b9d389b | ||
|
|
4fc94cae24 | ||
|
|
8cab8230e6 | ||
|
|
b7afa49c9f | ||
|
|
e0b5ccb41d | ||
|
|
fb63ffb919 | ||
|
|
cb98b0c937 | ||
|
|
bee56cfd99 | ||
|
|
9e130614ce | ||
|
|
2f487d2e83 | ||
|
|
1ff4b3a4fc | ||
|
|
e86d38c52c | ||
|
|
d371ae8ceb | ||
|
|
a9d10b2bf3 | ||
|
|
d6ffd193d3 | ||
|
|
2635213947 | ||
|
|
d496cbfb53 | ||
|
|
db8f0ad9ee | ||
|
|
8d795a4317 | ||
|
|
1a9ae456dd | ||
|
|
4e9d7a3f8c | ||
|
|
087967befb | ||
|
|
02ad6c7834 | ||
|
|
fc11f557fa | ||
|
|
f299f3ed20 | ||
|
|
94c831cd05 | ||
|
|
53b4c70e4b | ||
|
|
6ff5109b00 | ||
|
|
339afe614c | ||
|
|
911fdd3067 | ||
|
|
588e27e6d1 | ||
|
|
3a71032d62 | ||
|
|
59a091e7dc | ||
|
|
0e250a99ed | ||
|
|
f73b72753f | ||
|
|
00a85b0d90 | ||
|
|
e79d7e6fb5 | ||
|
|
ef745dc378 | ||
|
|
ac5dfb7ff0 | ||
|
|
51b0841171 | ||
|
|
57264da21b | ||
|
|
16f740b318 | ||
|
|
2ba70141cc | ||
|
|
e945329d91 | ||
|
|
ffeb4703fd | ||
|
|
fcf41187fa | ||
|
|
f0fc704efc | ||
|
|
aa58615650 | ||
|
|
b0a84bcbf7 | ||
|
|
cf2d413a28 | ||
|
|
9226b78b0c | ||
|
|
a635fd8a7b | ||
|
|
6f714277f7 | ||
|
|
4aff4982b0 | ||
|
|
8634989497 | ||
|
|
a50e60b2a0 | ||
|
|
8875f4dfc4 | ||
|
|
61c8217ed5 | ||
|
|
f75912033e | ||
|
|
1d8630810b | ||
|
|
ce155fb43e | ||
|
|
67ebe17fcb | ||
|
|
f13a6c0a3b | ||
|
|
e1870429b9 | ||
|
|
5aa1cd7f40 | ||
|
|
de1b887d29 | ||
|
|
e662f5ce0b | ||
|
|
790b69a32e | ||
|
|
9aaab97d6a | ||
|
|
66317197b6 | ||
|
|
91757335ac | ||
|
|
be3145aae9 | ||
|
|
79425af848 | ||
|
|
45b47a1071 | ||
|
|
4b30567c39 | ||
|
|
1ed33a7400 | ||
|
|
ca1d11006c | ||
|
|
71f1f3dd22 | ||
|
|
64add9a542 | ||
|
|
a7c02438e4 | ||
|
|
910af7a44c | ||
|
|
05f9ac3745 | ||
|
|
5c43eefe46 | ||
|
|
b63f4c53d2 | ||
|
|
3d652bc829 | ||
|
|
c78ef8771d | ||
|
|
9a38f16219 | ||
|
|
e5363205a7 | ||
|
|
088b3274f2 | ||
|
|
fabe323e34 | ||
|
|
a731bf034e | ||
|
|
3cbf772acf | ||
|
|
9701f88ae3 | ||
|
|
7ca1d118a1 | ||
|
|
a7dfa21cbf | ||
|
|
ab81444773 | ||
|
|
02c4343dfd | ||
|
|
1bfe86738f | ||
|
|
7fe0e05050 | ||
|
|
10588efe25 | ||
|
|
bf4607d368 | ||
|
|
45f04ae8d4 | ||
|
|
674f1de183 | ||
|
|
368c4fa1a7 | ||
|
|
c737579b7f | ||
|
|
0b7c51a323 | ||
|
|
e7c77b7bf3 | ||
|
|
6c727b7e51 | ||
|
|
a9754d6a1c | ||
|
|
218052d5dd | ||
|
|
f112cb748f | ||
|
|
67a65c66b6 | ||
|
|
cd6add14a0 | ||
|
|
33f9f1ddf4 | ||
|
|
2131de77b0 | ||
|
|
0febb60a3f | ||
|
|
820b914291 | ||
|
|
536d3ee70d | ||
|
|
2544d82486 | ||
|
|
ea6854fadc | ||
|
|
147e3b1173 | ||
|
|
8252142e65 | ||
|
|
6e2270632e | ||
|
|
7d14546d3b | ||
|
|
b31024c362 | ||
|
|
85eaef05ad | ||
|
|
16e4c10862 | ||
|
|
904e0f61a8 | ||
|
|
70953aa66e | ||
|
|
d41de5d270 | ||
|
|
d8603f4f34 | ||
|
|
b13a8094ea | ||
|
|
28e07bad54 | ||
|
|
81cc483166 | ||
|
|
8d7cc55c28 | ||
|
|
083e0e307a | ||
|
|
80f3f89a89 | ||
|
|
3dc8c18849 | ||
|
|
0a6a97cf07 | ||
|
|
7676d7a239 | ||
|
|
679715f896 | ||
|
|
d5aa880336 | ||
|
|
08daa86766 | ||
|
|
f0c1952dfe | ||
|
|
198d9115cc | ||
|
|
6b0c01bd3e | ||
|
|
a1848d3c75 | ||
|
|
a1596f7c62 | ||
|
|
740e701ad3 | ||
|
|
71d571a25c | ||
|
|
2293300182 | ||
|
|
85c7f2806f | ||
|
|
27ecb4fe49 | ||
|
|
7afeefe84f | ||
|
|
651922194f | ||
|
|
1d1b4fb04d | ||
|
|
b418f1eb05 | ||
|
|
a9b44ee39e | ||
|
|
cb48355898 | ||
|
|
6ce47e3a20 | ||
|
|
09e511bc49 | ||
|
|
66ade27383 | ||
|
|
d96058d589 | ||
|
|
8631a15933 | ||
|
|
c0bf81ad7c | ||
|
|
8fd1fa203e | ||
|
|
51b907bcf5 | ||
|
|
ecb5d974c5 | ||
|
|
19c91aff19 | ||
|
|
8e0cc7dd2f | ||
|
|
6478528ebc | ||
|
|
3e3b24f611 | ||
|
|
6dc7d70a1f | ||
|
|
a6f4c5651c | ||
|
|
291a4d39fe | ||
|
|
e639a1dd73 | ||
|
|
e3a961623b | ||
|
|
1ca27fdba2 | ||
|
|
f72428056a | ||
|
|
bdb3f11ac7 | ||
|
|
2f69af1a52 | ||
|
|
984cf77ee0 | ||
|
|
1762596fce | ||
|
|
910eb9b770 | ||
|
|
16807bd57f | ||
|
|
8bc29ad92d | ||
|
|
51a913a5e4 | ||
|
|
c30da805b3 | ||
|
|
42ee593708 | ||
|
|
1b49b35365 | ||
|
|
333ab7066c | ||
|
|
66dbdc09b5 | ||
|
|
79f396f6ff | ||
|
|
62ba4877ac | ||
|
|
e65a5e6d22 | ||
|
|
711b43070d | ||
|
|
b2f957d1d8 | ||
|
|
249745f256 | ||
|
|
8ff9d3bcf1 | ||
|
|
99c328c302 | ||
|
|
8a9f0a245e | ||
|
|
9daecc127a | ||
|
|
119bba4f04 | ||
|
|
bf205c579d | ||
|
|
aa57b17254 | ||
|
|
7df786c5c6 | ||
|
|
b315464e98 | ||
|
|
1252ea076b | ||
|
|
d970e446a8 | ||
|
|
fe40f3727b | ||
|
|
10a290b065 | ||
|
|
0abb11b42a | ||
|
|
1eb6eb729a | ||
|
|
e371a4a3c3 | ||
|
|
f2b6bd3d4b | ||
|
|
d21f87b543 | ||
|
|
b8cdee4f45 | ||
|
|
b06d7445d9 | ||
|
|
2aa9cceb32 | ||
|
|
ae2a2a8a07 | ||
|
|
286555411b | ||
|
|
3c42aec180 | ||
|
|
a203ae00c6 | ||
|
|
68784616a4 | ||
|
|
5cd8c6a5da | ||
|
|
a3647fea8a | ||
|
|
300e73f07d | ||
|
|
03ced23f05 | ||
|
|
da8eeb5d8f | ||
|
|
16cf02896f | ||
|
|
c9f10d248e | ||
|
|
3297e41289 | ||
|
|
4e1b347797 | ||
|
|
4602332dbb | ||
|
|
e1f69ee533 | ||
|
|
d90e431b5b | ||
|
|
45a9eca0ce | ||
|
|
af202f4871 | ||
|
|
7b32fa35f3 | ||
|
|
0c40b230dc | ||
|
|
2baba67bfc | ||
|
|
b1e69a9059 | ||
|
|
f87362ec1b | ||
|
|
84dc57373a | ||
|
|
b3f4a02658 | ||
|
|
50304dbefe | ||
|
|
bf01af0bf3 | ||
|
|
0e3fe3463b | ||
|
|
3956763825 | ||
|
|
d6fe895f6e | ||
|
|
697fa08d8b | ||
|
|
669a35afef | ||
|
|
b1d94a3845 | ||
|
|
06869cf491 | ||
|
|
ed67d8514c | ||
|
|
07ec2f3d7b | ||
|
|
73d02e85c4 | ||
|
|
c1cf1eb37a | ||
|
|
7aecc94049 | ||
|
|
b55fbfeb8e | ||
|
|
20b8991fa4 | ||
|
|
e50de4201d | ||
|
|
ba2b876ee4 | ||
|
|
9b1399c9d5 | ||
|
|
020a2f00c2 | ||
|
|
8713946f35 | ||
|
|
bed8c25e40 | ||
|
|
7c51a09068 | ||
|
|
0f0a6ce042 | ||
|
|
a890968ee4 | ||
|
|
2267c5be00 | ||
|
|
e853cfb98a | ||
|
|
f89a0db471 | ||
|
|
1d66b43da1 | ||
|
|
75c4a4f253 | ||
|
|
6a9c95b3b1 | ||
|
|
c11a2d0081 | ||
|
|
43a13ff2c5 | ||
|
|
8454d163eb | ||
|
|
4a11f6a39f | ||
|
|
c63235cd3d | ||
|
|
f4ce196a34 | ||
|
|
e644b27201 | ||
|
|
d2b14582ee | ||
|
|
ca9194fd96 | ||
|
|
773d3ab13c | ||
|
|
a02a4a07a5 | ||
|
|
0a8625227f | ||
|
|
dc613aa2bf | ||
|
|
2008ac5663 | ||
|
|
da326f1925 | ||
|
|
71911cb1a7 | ||
|
|
31e7582216 | ||
|
|
91f63047e0 | ||
|
|
c9aa4938f4 | ||
|
|
b9e6a38037 | ||
|
|
eb054146d2 | ||
|
|
a780373cfc | ||
|
|
5ad90ccdb8 | ||
|
|
ef97d81ca8 | ||
|
|
c9f7b642c5 | ||
|
|
770962be3f | ||
|
|
d6c857f4a8 | ||
|
|
7b7ffb427d | ||
|
|
b80318d1b6 | ||
|
|
3997d69e49 | ||
|
|
4af41b33c0 | ||
|
|
0f8bbead66 | ||
|
|
804fcff5b6 | ||
|
|
d18e6920fc | ||
|
|
c95174ff11 | ||
|
|
c70329c22b | ||
|
|
34f69263a2 | ||
|
|
bf5056f8fe | ||
|
|
c47f5b06ba | ||
|
|
63055e3df2 | ||
|
|
8ee555140b | ||
|
|
859f78e986 | ||
|
|
366eebb68c | ||
|
|
25ce0b9aee | ||
|
|
376a7db545 | ||
|
|
f906ed741d | ||
|
|
ad152e62e5 | ||
|
|
7f3a61ef43 | ||
|
|
a7ba8c8213 | ||
|
|
b1f3d6d4c0 | ||
|
|
5707ae027f | ||
|
|
770208024d | ||
|
|
975a9f5344 | ||
|
|
a45008411e | ||
|
|
9091ffd1b3 | ||
|
|
089f161d1e | ||
|
|
e6d7131701 | ||
|
|
ed82170c2c | ||
|
|
3472d42cdd | ||
|
|
b8926350b9 | ||
|
|
e5194b313d | ||
|
|
94228375ad | ||
|
|
0e8385a5a7 | ||
|
|
afd3763d96 | ||
|
|
1122f09093 | ||
|
|
1123af15c9 | ||
|
|
b2f6e865a7 | ||
|
|
93b9ac2d13 | ||
|
|
64af65b0e7 | ||
|
|
390c8437c5 | ||
|
|
cc65ca9b6a | ||
|
|
4bd7cba247 | ||
|
|
92e2f7f8b7 | ||
|
|
98054ddff6 | ||
|
|
4e5c1aba2e | ||
|
|
5eb44320ae | ||
|
|
f751269289 | ||
|
|
0e836622ec | ||
|
|
a6ac0a813d | ||
|
|
21c47f60f2 | ||
|
|
66142b1dd8 | ||
|
|
3a28f4a0cb | ||
|
|
71eeca96ee | ||
|
|
b56cde47ca | ||
|
|
793e65bac0 | ||
|
|
4d1a569269 | ||
|
|
5c56317d50 | ||
|
|
32491ae2ad | ||
|
|
28aeb11565 | ||
|
|
61eb3ff908 | ||
|
|
ff5455881e | ||
|
|
143b7ce678 | ||
|
|
9c3dfc78df | ||
|
|
017872c5ef | ||
|
|
b9ad67d147 | ||
|
|
62ea3b1a1a | ||
|
|
325b668d00 | ||
|
|
9ce6bf8e14 | ||
|
|
052448c59a | ||
|
|
c8b318b315 | ||
|
|
54daac329e | ||
|
|
b5a59e4de0 | ||
|
|
258876d13f | ||
|
|
9cc8e16d64 | ||
|
|
85d1254638 | ||
|
|
45eb261dcd | ||
|
|
8b2ff5f269 | ||
|
|
79ab568c80 | ||
|
|
382b1737cb | ||
|
|
c9b3a83592 | ||
|
|
0b21ce7ce9 | ||
|
|
523bf8c689 | ||
|
|
8ead5a9d1f | ||
|
|
46e3199438 | ||
|
|
d94e614a54 | ||
|
|
9f459867bf | ||
|
|
f5ce319c24 | ||
|
|
6b9c02d42a | ||
|
|
e2e06a8f83 | ||
|
|
8402f73d52 | ||
|
|
84334ace6a | ||
|
|
43e2518fbf | ||
|
|
13098713e9 | ||
|
|
77c6e541d6 | ||
|
|
bbd49f9372 | ||
|
|
015be517bf | ||
|
|
8faa936a68 | ||
|
|
39922cca5e | ||
|
|
63dd68081a | ||
|
|
3694b19a97 | ||
|
|
c58b2a5e45 | ||
|
|
5b8562f5bd | ||
|
|
67020426f6 | ||
|
|
3840894201 | ||
|
|
888f3feaff | ||
|
|
2e339d7457 | ||
|
|
fd0ff633e6 | ||
|
|
02923fe9ce | ||
|
|
875f97813e | ||
|
|
6a29385762 | ||
|
|
fbc9f9dec9 | ||
|
|
4607ade8e9 | ||
|
|
e4a553c022 | ||
|
|
bc733bab0c | ||
|
|
1a522ddf48 | ||
|
|
1de0f4c4fa | ||
|
|
71eb5c25b5 | ||
|
|
ea34979a29 | ||
|
|
1f784ad44c | ||
|
|
f89726752f | ||
|
|
921b6b7ba4 | ||
|
|
6c5cc7a0ce | ||
|
|
f851e2f2cf | ||
|
|
b73207f29a | ||
|
|
8d8f0b55ea | ||
|
|
0e1e9b9cf3 | ||
|
|
ab4eed78af | ||
|
|
8a36c15d5c | ||
|
|
bd895286bc | ||
|
|
b76990ec16 | ||
|
|
9bc93c5cf5 | ||
|
|
1d8ae79f84 | ||
|
|
4269aa2422 | ||
|
|
c887dc1ec8 | ||
|
|
8bbacd691d | ||
|
|
d8f3f85fb7 | ||
|
|
159c0b3350 | ||
|
|
45788f3235 | ||
|
|
4c85c6ccdf | ||
|
|
08408ae005 | ||
|
|
d470279fb7 | ||
|
|
8260458f8f | ||
|
|
0512dd1e86 | ||
|
|
4972019cb9 | ||
|
|
5e615cab52 | ||
|
|
64ea2b6d3b | ||
|
|
27018fa8e2 | ||
|
|
263d634801 | ||
|
|
c61c33f71c | ||
|
|
bfb30b8fed | ||
|
|
ca3e27937e | ||
|
|
50c34a4186 | ||
|
|
84875aaf82 | ||
|
|
b7a8b95820 | ||
|
|
5cad5ca11a | ||
|
|
7598de29dd | ||
|
|
af6ffe67c8 | ||
|
|
6944c4b3bf | ||
|
|
24817d5707 | ||
|
|
3d855712ee | ||
|
|
872263d253 | ||
|
|
1c491fb1c5 | ||
|
|
29374c579a | ||
|
|
206d7b6ca9 | ||
|
|
ab6ca8c894 | ||
|
|
e7f5fde051 | ||
|
|
030b358b0e | ||
|
|
4b1791d35c | ||
|
|
b788cf823d | ||
|
|
11cbc0288f | ||
|
|
2269c80b02 | ||
|
|
c922cc042e | ||
|
|
9cb6f44f72 | ||
|
|
f376cd5ab1 | ||
|
|
8b8a02b68c | ||
|
|
5759b1336a | ||
|
|
76f17cb6d7 | ||
|
|
7149c421cf | ||
|
|
353c599767 | ||
|
|
285c43dab7 | ||
|
|
bd5933f4c2 | ||
|
|
0faac08237 | ||
|
|
14f94b1566 | ||
|
|
415e2ff265 | ||
|
|
26e97358d1 | ||
|
|
0ed6fb33c5 | ||
|
|
df98e7512b | ||
|
|
99ede6477f | ||
|
|
ee0debe14a | ||
|
|
dd1e11f102 | ||
|
|
ed47493459 | ||
|
|
5b8391067d | ||
|
|
4f41a59ceb | ||
|
|
4525cb38e4 | ||
|
|
704dfc25e6 | ||
|
|
7022ebe4ca | ||
|
|
094863f0ea | ||
|
|
a6282125cf | ||
|
|
63387a09af | ||
|
|
d4c47cecf2 | ||
|
|
b839069af1 | ||
|
|
e3c98100cc | ||
|
|
afcce04650 | ||
|
|
5fb35c2d48 | ||
|
|
dfe89f0d83 | ||
|
|
c500ec071e | ||
|
|
6e47f16fe1 | ||
|
|
13e6c2dbeb | ||
|
|
9d27dfb41c | ||
|
|
9529905875 | ||
|
|
eef7fc4b51 | ||
|
|
a88435f6b6 | ||
|
|
0b22598783 | ||
|
|
40cc019a51 | ||
|
|
781094ab05 | ||
|
|
21cfe2e535 | ||
|
|
4f72684080 | ||
|
|
5ac514366e | ||
|
|
d5d6285eef | ||
|
|
0e34b41bce | ||
|
|
e157066bcc | ||
|
|
f43a63ac6e | ||
|
|
a5828fa01a | ||
|
|
662d3ff795 | ||
|
|
a97f05db3b | ||
|
|
5a439ca70c | ||
|
|
551faca3a3 | ||
|
|
c25ffe9639 | ||
|
|
900021ec1b | ||
|
|
cc1ad16688 | ||
|
|
fabac9fcd9 | ||
|
|
da170b9b16 | ||
|
|
0e417430b8 | ||
|
|
0cfac31bca | ||
|
|
0f5e8fd314 | ||
|
|
e457155950 | ||
|
|
5a98790dd3 | ||
|
|
e188f65e7c | ||
|
|
6bf3ea3c46 | ||
|
|
fa33ac57fb | ||
|
|
0bed9eea09 | ||
|
|
36e9f7a8cb | ||
|
|
b155dbef6a | ||
|
|
e48b428725 | ||
|
|
68884c9126 | ||
|
|
a713976f9c | ||
|
|
51d7306380 | ||
|
|
0b83f75ec8 | ||
|
|
0a80635943 | ||
|
|
e6a5f4b203 | ||
|
|
061b2fdbcb | ||
|
|
7922e32618 | ||
|
|
0949d10da6 | ||
|
|
0c8ad9262e | ||
|
|
f841aef82d | ||
|
|
d42e51315d | ||
|
|
e761b73cfd | ||
|
|
1a9c9a7767 | ||
|
|
eb7ce7cef1 | ||
|
|
68fd8dcd76 | ||
|
|
ce8bb2567c | ||
|
|
ab3d9bc120 | ||
|
|
432ec161c1 | ||
|
|
fcaf4a9b51 | ||
|
|
cbb7e6599b | ||
|
|
8f89a144ca | ||
|
|
50bd05b351 | ||
|
|
4e7f5401ec | ||
|
|
3fd56e8c36 | ||
|
|
daf028ab2b | ||
|
|
5467620b32 | ||
|
|
a3cae0f094 | ||
|
|
f74eea4314 | ||
|
|
04e930a766 | ||
|
|
89ac13e805 | ||
|
|
0b86d38517 | ||
|
|
7e76809860 | ||
|
|
4d46b98e7f | ||
|
|
6c1a46aee5 | ||
|
|
a7cef5a1c6 | ||
|
|
8a218a6626 | ||
|
|
542fe18dcd | ||
|
|
25f18ac951 | ||
|
|
5540438595 | ||
|
|
b25456835a | ||
|
|
f86091382f | ||
|
|
dbdf8871cc | ||
|
|
1e6926ee2d | ||
|
|
24cbe98ac2 | ||
|
|
c590b026d6 | ||
|
|
d28221aec9 | ||
|
|
df0c6c8c2d | ||
|
|
876f0f9a14 | ||
|
|
f9f0fc1d67 | ||
|
|
42e357341a | ||
|
|
edc5402aad | ||
|
|
3f61ffa6fb | ||
|
|
1c22b889fe | ||
|
|
e80f5939b2 | ||
|
|
c068088cc1 | ||
|
|
1767f371f7 | ||
|
|
5d5caf64fa | ||
|
|
e095b9419a | ||
|
|
a5ef072f56 | ||
|
|
5f6a2456d4 | ||
|
|
b0f24889f4 | ||
|
|
0a69f0fd51 | ||
|
|
43c0142682 | ||
|
|
1ff16cc54b | ||
|
|
3609cf54c0 | ||
|
|
198038f5ea | ||
|
|
b5a3df85a8 | ||
|
|
6e56c6e0d4 | ||
|
|
568f3d6007 | ||
|
|
6b505a1d06 | ||
|
|
acc7e91c55 | ||
|
|
8e43df8d95 | ||
|
|
ca81b382d3 | ||
|
|
a60d7976f5 | ||
|
|
46cf63da37 | ||
|
|
099079b962 | ||
|
|
5b8121516f | ||
|
|
23eacaa78a | ||
|
|
659292a3f1 | ||
|
|
f88756d3a8 | ||
|
|
81e7bab22a | ||
|
|
1eeb5fc2b0 | ||
|
|
126cdd73e4 | ||
|
|
2251267ae4 | ||
|
|
f3e4b6d9a2 | ||
|
|
b22379537b | ||
|
|
feb5bb0653 | ||
|
|
ebfe99be79 | ||
|
|
49c1b939d5 | ||
|
|
84a37f124b | ||
|
|
f0f48f4ddf | ||
|
|
cf15e3a9a1 | ||
|
|
927f98c66b | ||
|
|
782f6ac27e | ||
|
|
5140e4ec9a | ||
|
|
e255f9bfc5 | ||
|
|
9e9b369dc2 | ||
|
|
765dc734a4 | ||
|
|
3abe8a1103 | ||
|
|
73333074fd | ||
|
|
a415786e1c | ||
|
|
a3a1077dea | ||
|
|
58a812c2c9 | ||
|
|
7e69ec4367 | ||
|
|
4623c01896 | ||
|
|
e85322de62 | ||
|
|
090ff3da13 | ||
|
|
fe068b1bfb | ||
|
|
d3b0a685b9 | ||
|
|
245c04fc5a | ||
|
|
94acb0c6ae | ||
|
|
ad4a16cc4e | ||
|
|
dae39113f6 | ||
|
|
1e1cb8fe57 | ||
|
|
606254d941 | ||
|
|
8fd51b9e9e | ||
|
|
f90442cfb1 | ||
|
|
2a44c0f65a | ||
|
|
cd8d2b5e95 | ||
|
|
8d89b2e80a | ||
|
|
3255f8af0c | ||
|
|
ca0784fa59 | ||
|
|
827060b6ce | ||
|
|
60c069cb1f | ||
|
|
70dfea5876 | ||
|
|
ab475d65ee | ||
|
|
fa9963a737 | ||
|
|
d65d9c9a4c | ||
|
|
6f8c7a613f | ||
|
|
1b45c927b2 | ||
|
|
ab1cc896de | ||
|
|
4b91600e35 | ||
|
|
69e43e40eb | ||
|
|
930622ea3c | ||
|
|
7ac104c3c6 | ||
|
|
c47dc5ecdb | ||
|
|
a3f05c1146 | ||
|
|
04b1fa378d | ||
|
|
cbc9d9ab8c | ||
|
|
7f61c63425 | ||
|
|
3d379dec58 | ||
|
|
9968a816db | ||
|
|
8f721cd12a | ||
|
|
d45f2e1ec7 | ||
|
|
7e0f1794a5 | ||
|
|
20959fc68f | ||
|
|
a942224f46 | ||
|
|
fef16f6b28 | ||
|
|
1abc4d0365 | ||
|
|
862761d46f | ||
|
|
94743a16be | ||
|
|
f6729834b6 | ||
|
|
1b4c435900 | ||
|
|
24e6a86bf5 | ||
|
|
88f74d5e9c | ||
|
|
171f93662f | ||
|
|
c5429f7efa | ||
|
|
36847e6a06 | ||
|
|
438fd7d825 | ||
|
|
767b61a3bb | ||
|
|
dd2542dcd6 | ||
|
|
f8b63d487a | ||
|
|
fcd0feb7f6 | ||
|
|
8287cf386b | ||
|
|
8415bee5da | ||
|
|
30eb48321c | ||
|
|
7cff2e01a4 | ||
|
|
fa94341b60 | ||
|
|
a1b34d5e7b | ||
|
|
14ef3cf07c | ||
|
|
db6cddefd4 | ||
|
|
95c6ecb724 | ||
|
|
fc27f6b3f0 | ||
|
|
2ce0bb2caa | ||
|
|
94e1c006ab | ||
|
|
fc2e3586a2 | ||
|
|
bca8b6b606 | ||
|
|
bb6a3471d5 | ||
|
|
09d07e4890 | ||
|
|
8826ad8990 | ||
|
|
b350edc250 | ||
|
|
af326c0b1e | ||
|
|
edc346a6b3 | ||
|
|
1e3fc97f2b | ||
|
|
51b78ef5f3 | ||
|
|
4e4036382b | ||
|
|
daf7cb9d6b | ||
|
|
08c97f280f | ||
|
|
9c836a34ee | ||
|
|
e839430270 | ||
|
|
cf5e530ed9 | ||
|
|
49ac5e647f | ||
|
|
5a7b3ded20 | ||
|
|
bcf06f0d69 | ||
|
|
4f3fe28905 | ||
|
|
f695861731 | ||
|
|
2fee966fc8 | ||
|
|
6964124c1b | ||
|
|
f9e2d91e48 | ||
|
|
2c483e7f7c | ||
|
|
3d078108a3 | ||
|
|
794967bd9f | ||
|
|
554474776c | ||
|
|
81c5f9cd4e | ||
|
|
e4735401a7 | ||
|
|
758c3be3c5 | ||
|
|
2be9ae7868 | ||
|
|
3ee4540f7d | ||
|
|
84c1175379 | ||
|
|
ad4d87a138 | ||
|
|
5feff0d0ec | ||
|
|
08e7a2c065 | ||
|
|
a524521fdb | ||
|
|
0b2312a407 | ||
|
|
c7d9e24c00 | ||
|
|
0d827e490e | ||
|
|
6402125233 | ||
|
|
7bfbda4490 | ||
|
|
f6b357691c | ||
|
|
0c1e4e6888 | ||
|
|
c7ecdcf625 | ||
|
|
957b9b1cf4 | ||
|
|
1b4eed1a6e | ||
|
|
acd47b870f | ||
|
|
9ab8d03697 | ||
|
|
9c6f23d7cc | ||
|
|
9af00d6d58 | ||
|
|
186c09f29b | ||
|
|
70b185a37a | ||
|
|
5af01505c2 | ||
|
|
5f6ab2fc08 | ||
|
|
c8f7ea934c | ||
|
|
a4aa8dafac | ||
|
|
8ca9866440 | ||
|
|
97eff7f21d | ||
|
|
e30fd8eb9b | ||
|
|
6fa48c9222 | ||
|
|
c36c7144cb | ||
|
|
bbd0a893b1 | ||
|
|
cfea2b8d9d | ||
|
|
0fcbe821cd | ||
|
|
ca41164980 | ||
|
|
cf20e2a510 | ||
|
|
02f1231e7d | ||
|
|
6846e7941f | ||
|
|
719287c3a6 | ||
|
|
194670c3bf | ||
|
|
c4222e0981 | ||
|
|
61dc4b8bb8 | ||
|
|
df357c1008 | ||
|
|
871b52a288 | ||
|
|
8a4a043e9f | ||
|
|
c0297aa061 | ||
|
|
2aaa2eeaff | ||
|
|
5f62e5bacf | ||
|
|
ab78559710 | ||
|
|
0d75bb1d3e | ||
|
|
6441062e7f | ||
|
|
4827a45f22 | ||
|
|
40eef457ab | ||
|
|
c22a4d109b | ||
|
|
571c3e04c4 | ||
|
|
f9fb97a572 | ||
|
|
e6484bb2d5 | ||
|
|
19f51fdc1f | ||
|
|
25f7ba3ee6 | ||
|
|
a3cd1ff4e1 | ||
|
|
f62c717f79 | ||
|
|
3ed99133d4 | ||
|
|
ab25cc331d | ||
|
|
2648992a9b | ||
|
|
7bcc3d081e | ||
|
|
affd1c45ca | ||
|
|
8c7458812d | ||
|
|
053908c772 | ||
|
|
d623736f9f | ||
|
|
a3e416ff68 | ||
|
|
8bf207b9f7 | ||
|
|
e1520d5e7e | ||
|
|
4f89c9f7c6 | ||
|
|
d34fb92211 | ||
|
|
3f7b006d16 | ||
|
|
a15cf33691 | ||
|
|
4c1699df6c | ||
|
|
404535c512 | ||
|
|
2cedcecc38 | ||
|
|
ba7b02d8d4 | ||
|
|
6aa3c16646 | ||
|
|
5a03e17e00 | ||
|
|
417d145b43 | ||
|
|
e5636a2afa | ||
|
|
5291d2d129 | ||
|
|
c6f6a87499 | ||
|
|
1ef21c6ed6 | ||
|
|
6ee1932cbf | ||
|
|
83cf43a314 | ||
|
|
52c14a79a1 | ||
|
|
152db0551d | ||
|
|
3a2467ccfe | ||
|
|
5a28f3597c | ||
|
|
9ce179f91b | ||
|
|
7aeb072cc3 | ||
|
|
8a652761a3 | ||
|
|
be667575d5 | ||
|
|
59ca8a44a0 | ||
|
|
c9f0d3bfd8 | ||
|
|
06e7a1fda1 | ||
|
|
495203c112 |
@@ -1,64 +0,0 @@
|
|||||||
FROM {{ if eq "CI" .Orbit.Image }}golang:1.10-stretch{{ else }}debian:stretch-slim{{ end }}
|
|
||||||
|
|
||||||
LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Libraries
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | Installs all required libraries.
|
|
||||||
# |
|
|
||||||
|
|
||||||
RUN echo "deb http://httpredir.debian.org/debian/ stretch main contrib non-free" > /etc/apt/sources.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y xvfb wkhtmltopdf pdftk unoconv ttf-mscorefonts-installer &&\
|
|
||||||
ln -s /usr/bin/xvfb-run /usr/local/bin/xvfb-run &&\
|
|
||||||
ln -s /usr/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf &&\
|
|
||||||
ln -s /usr/bin/pdftk /usr/local/bin/pdftk &&\
|
|
||||||
ln -s /usr/bin/unoconv /usr/local/bin/unoconv
|
|
||||||
|
|
||||||
RUN apt-get install -y curl gnupg &&\
|
|
||||||
curl -sL https://deb.nodesource.com/setup_8.x | bash - &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y nodejs
|
|
||||||
|
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &&\
|
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y yarn bzip2 &&\
|
|
||||||
yarn global add markdown-pdf --prefix /usr/local
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Gotenberg
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | All Gotenberg related stuff.
|
|
||||||
# |
|
|
||||||
|
|
||||||
{{- if eq "CI" .Orbit.Image }}
|
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/thecodingmachine/gotenberg
|
|
||||||
|
|
||||||
# Installs lint dependencies.
|
|
||||||
RUN go get -u gopkg.in/alecthomas/gometalinter.v2 &&\
|
|
||||||
gometalinter.v2 --install
|
|
||||||
|
|
||||||
# Copies our Go source.
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Installs project dependencies.
|
|
||||||
RUN go get -d -v ./...
|
|
||||||
|
|
||||||
ENTRYPOINT [".ci/docker-entrypoint.sh"]
|
|
||||||
{{- else }}
|
|
||||||
|
|
||||||
COPY .ci/gotenberg /usr/bin/gotenberg
|
|
||||||
RUN ln -s /usr/bin/gotenberg /usr/local/bin/gotenberg
|
|
||||||
|
|
||||||
COPY .ci/gotenberg.yml /gotenberg/gotenberg.yml
|
|
||||||
|
|
||||||
WORKDIR /gotenberg
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["gotenberg"]
|
|
||||||
{{- end }}
|
|
||||||
76
.bruno/Chromium/Convert/HTML to PDF.bru
Normal file
76
.bruno/Chromium/Convert/HTML to PDF.bru
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
meta {
|
||||||
|
name: HTML to PDF
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/convert/html
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../test/integration/testdata/page-1-html/index.html)
|
||||||
|
~landscape: false
|
||||||
|
~printBackground: false
|
||||||
|
~scale: 1.0
|
||||||
|
~singlePage: false
|
||||||
|
~paperWidth: 8.5
|
||||||
|
~paperHeight: 11
|
||||||
|
~marginTop: 0.39
|
||||||
|
~marginBottom: 0.39
|
||||||
|
~marginLeft: 0.39
|
||||||
|
~marginRight: 0.39
|
||||||
|
~nativePageRanges: 1-2
|
||||||
|
~preferCssPageSize: false
|
||||||
|
~generateDocumentOutline: false
|
||||||
|
~generateTaggedPdf: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: print
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
~splitMode: intervals
|
||||||
|
~splitSpan: 1
|
||||||
|
~splitUnify: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_1.xml)
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_2.xml)
|
||||||
|
~embedsMetadata: {"embed_1.xml":{"mimeType":"text/xml","relationship":"Data"}, "embed_2.xml":{"mimeType":"text/xml","relationship":"Data"}}
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-file
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
77
.bruno/Chromium/Convert/Markdown to PDF.bru
Normal file
77
.bruno/Chromium/Convert/Markdown to PDF.bru
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
meta {
|
||||||
|
name: Markdown to PDF
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/convert/markdown
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page-1-markdown/index.html)
|
||||||
|
files: @file(../../test/integration/testdata/page-1-markdown/page_1.md)
|
||||||
|
~landscape: false
|
||||||
|
~printBackground: false
|
||||||
|
~scale: 1.0
|
||||||
|
~singlePage: false
|
||||||
|
~paperWidth: 8.5
|
||||||
|
~paperHeight: 11
|
||||||
|
~marginTop: 0.39
|
||||||
|
~marginBottom: 0.39
|
||||||
|
~marginLeft: 0.39
|
||||||
|
~marginRight: 0.39
|
||||||
|
~nativePageRanges: 1-2
|
||||||
|
~preferCssPageSize: false
|
||||||
|
~generateDocumentOutline: false
|
||||||
|
~generateTaggedPdf: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: print
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
~splitMode: intervals
|
||||||
|
~splitSpan: 1
|
||||||
|
~splitUnify: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_1.xml)
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_2.xml)
|
||||||
|
~embedsMetadata: {"embed_1.xml":{"mimeType":"text/xml","relationship":"Data"}, "embed_2.xml":{"mimeType":"text/xml","relationship":"Data"}}
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-file
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
76
.bruno/Chromium/Convert/URL to PDF.bru
Normal file
76
.bruno/Chromium/Convert/URL to PDF.bru
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
meta {
|
||||||
|
name: URL to PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/convert/url
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
url: https://example.com
|
||||||
|
~landscape: false
|
||||||
|
~printBackground: false
|
||||||
|
~scale: 1.0
|
||||||
|
~singlePage: false
|
||||||
|
~paperWidth: 8.5
|
||||||
|
~paperHeight: 11
|
||||||
|
~marginTop: 0.39
|
||||||
|
~marginBottom: 0.39
|
||||||
|
~marginLeft: 0.39
|
||||||
|
~marginRight: 0.39
|
||||||
|
~nativePageRanges: 1-2
|
||||||
|
~preferCssPageSize: false
|
||||||
|
~generateDocumentOutline: false
|
||||||
|
~generateTaggedPdf: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: print
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
~splitMode: intervals
|
||||||
|
~splitSpan: 1
|
||||||
|
~splitUnify: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_1.xml)
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_2.xml)
|
||||||
|
~embedsMetadata: {"embed_1.xml":{"mimeType":"text/xml","relationship":"Data"}, "embed_2.xml":{"mimeType":"text/xml","relationship":"Data"}}
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-file
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
47
.bruno/Chromium/Screenshot/HTML Screenshot.bru
Normal file
47
.bruno/Chromium/Screenshot/HTML Screenshot.bru
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
meta {
|
||||||
|
name: HTML Screenshot
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/screenshot/html
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page-1-html/index.html)
|
||||||
|
~width: 800
|
||||||
|
~height: 600
|
||||||
|
~clip: false
|
||||||
|
~format: png
|
||||||
|
~quality: 100
|
||||||
|
~optimizeForSpeed: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: screen
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-screenshot
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
48
.bruno/Chromium/Screenshot/Markdown Screenshot.bru
Normal file
48
.bruno/Chromium/Screenshot/Markdown Screenshot.bru
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
meta {
|
||||||
|
name: Markdown Screenshot
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/screenshot/markdown
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page-1-markdown/index.html)
|
||||||
|
files: @file(../../test/integration/testdata/page-1-markdown/page_1.md)
|
||||||
|
~width: 800
|
||||||
|
~height: 600
|
||||||
|
~clip: false
|
||||||
|
~format: png
|
||||||
|
~quality: 100
|
||||||
|
~optimizeForSpeed: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: screen
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-screenshot
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
47
.bruno/Chromium/Screenshot/URL Screenshot.bru
Normal file
47
.bruno/Chromium/Screenshot/URL Screenshot.bru
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
meta {
|
||||||
|
name: URL Screenshot
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/chromium/screenshot/url
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
url: https://example.com
|
||||||
|
~width: 800
|
||||||
|
~height: 600
|
||||||
|
~clip: false
|
||||||
|
~format: png
|
||||||
|
~quality: 100
|
||||||
|
~optimizeForSpeed: false
|
||||||
|
~skipNetworkIdleEvent: false
|
||||||
|
~failOnHttpStatusCodes: [499,599]
|
||||||
|
~failOnResourceHttpStatusCodes: []
|
||||||
|
~ignoreResourceHttpStatusDomains: []
|
||||||
|
~failOnResourceLoadingFailed: false
|
||||||
|
~failOnConsoleExceptions: false
|
||||||
|
~waitDelay: 0s
|
||||||
|
~waitWindowStatus:
|
||||||
|
~waitForExpression:
|
||||||
|
~waitForSelector:
|
||||||
|
~cookies: [{"name":"my_cookie","value":"my_value","domain":"example.com"}]
|
||||||
|
~userAgent:
|
||||||
|
~extraHttpHeaders: {"X-Custom-Header":"value"}
|
||||||
|
~emulatedMediaType: screen
|
||||||
|
~emulatedMediaFeatures: {"prefers-color-scheme":"dark"}
|
||||||
|
~omitBackground: false
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-screenshot
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
11
.bruno/Health & Info/Debug.bru
Normal file
11
.bruno/Health & Info/Debug.bru
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Debug
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/debug
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
11
.bruno/Health & Info/Health.bru
Normal file
11
.bruno/Health & Info/Health.bru
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Health
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/health
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
11
.bruno/Health & Info/Prometheus Metrics.bru
Normal file
11
.bruno/Health & Info/Prometheus Metrics.bru
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Prometheus Metrics
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/prometheus/metrics
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
11
.bruno/Health & Info/Version.bru
Normal file
11
.bruno/Health & Info/Version.bru
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Version
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/version
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
93
.bruno/LibreOffice/Convert to PDF.bru
Normal file
93
.bruno/LibreOffice/Convert to PDF.bru
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
meta {
|
||||||
|
name: Convert to PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/libreoffice/convert
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../test/integration/testdata/page_1.docx)
|
||||||
|
~password:
|
||||||
|
~landscape: false
|
||||||
|
~nativePageRanges:
|
||||||
|
~updateIndexes: false
|
||||||
|
~exportFormFields: true
|
||||||
|
~allowDuplicateFieldNames: false
|
||||||
|
~exportBookmarks: true
|
||||||
|
~exportBookmarksToPdfDestination: false
|
||||||
|
~exportPlaceholders: false
|
||||||
|
~exportNotes: false
|
||||||
|
~exportNotesPages: false
|
||||||
|
~exportOnlyNotesPages: false
|
||||||
|
~exportNotesInMargin: false
|
||||||
|
~convertOooTargetToPdfTarget: false
|
||||||
|
~exportLinksRelativeFsys: false
|
||||||
|
~exportHiddenSlides: false
|
||||||
|
~skipEmptyPages: false
|
||||||
|
~addOriginalDocumentAsStream: false
|
||||||
|
~singlePageSheets: false
|
||||||
|
~initialView: 0
|
||||||
|
~initialPage: 1
|
||||||
|
~magnification: 0
|
||||||
|
~zoom: 100
|
||||||
|
~pageLayout: 0
|
||||||
|
~firstPageOnLeft: false
|
||||||
|
~resizeWindowToInitialPage: false
|
||||||
|
~centerWindow: false
|
||||||
|
~openInFullScreenMode: false
|
||||||
|
~displayPDFDocumentTitle: true
|
||||||
|
~hideViewerMenubar: false
|
||||||
|
~hideViewerToolbar: false
|
||||||
|
~hideViewerWindowControls: false
|
||||||
|
~useTransitionEffects: true
|
||||||
|
~openBookmarkLevels: -1
|
||||||
|
~losslessImageCompression: false
|
||||||
|
~quality: 90
|
||||||
|
~reduceImageResolution: false
|
||||||
|
~maxImageResolution: 300
|
||||||
|
~nativeWatermarkText:
|
||||||
|
~nativeWatermarkColor: 0
|
||||||
|
~nativeWatermarkFontHeight: 0
|
||||||
|
~nativeWatermarkRotateAngle: 0
|
||||||
|
~nativeWatermarkFontName:
|
||||||
|
~nativeTiledWatermarkText:
|
||||||
|
~nativePdfFormats: true
|
||||||
|
~merge: false
|
||||||
|
~flatten: false
|
||||||
|
~splitMode: intervals
|
||||||
|
~splitSpan: 1
|
||||||
|
~splitUnify: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_1.xml)
|
||||||
|
~embeds: @file(../test/integration/testdata/embed_2.xml)
|
||||||
|
~embedsMetadata: {"embed_1.xml":{"mimeType":"text/xml","relationship":"Data"}, "embed_2.xml":{"mimeType":"text/xml","relationship":"Data"}}
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: my-file
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
24
.bruno/PDF Engines/Bookmarks/Read Bookmarks.bru
Normal file
24
.bruno/PDF Engines/Bookmarks/Read Bookmarks.bru
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
meta {
|
||||||
|
name: Read Bookmarks
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/bookmarks/read
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
26
.bruno/PDF Engines/Bookmarks/Write Bookmarks.bru
Normal file
26
.bruno/PDF Engines/Bookmarks/Write Bookmarks.bru
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
meta {
|
||||||
|
name: Write Bookmarks
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/bookmarks/write
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
bookmarks: [{"title":"Chapter 1","page":1,"children":[{"title":"Section 1.1","page":1}]}]
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: with-bookmarks
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
27
.bruno/PDF Engines/Convert/Convert PDF.bru
Normal file
27
.bruno/PDF Engines/Convert/Convert PDF.bru
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Convert PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/convert
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: converted
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
29
.bruno/PDF Engines/Embed/Embed Files.bru
Normal file
29
.bruno/PDF Engines/Embed/Embed Files.bru
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
meta {
|
||||||
|
name: Embed Files
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/embed
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../test/integration/testdata/page_1.pdf)
|
||||||
|
embeds: @file(../test/integration/testdata/embed_1.xml)
|
||||||
|
embeds: @file(../test/integration/testdata/embed_2.xml)
|
||||||
|
embedsMetadata: {"embed_1.xml":{"mimeType":"text/xml","relationship":"Data"}, "embed_2.xml":{"mimeType":"text/xml","relationship":"Data"}}
|
||||||
|
~downloadFrom: [{"url":"https://example.com/attachment.xml","embedded":true}]
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: with-embeds
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
27
.bruno/PDF Engines/Encrypt/Encrypt PDF.bru
Normal file
27
.bruno/PDF Engines/Encrypt/Encrypt PDF.bru
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Encrypt PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/encrypt
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
userPassword: secret123
|
||||||
|
~ownerPassword: owner456
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: encrypted
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
25
.bruno/PDF Engines/Flatten/Flatten PDF.bru
Normal file
25
.bruno/PDF Engines/Flatten/Flatten PDF.bru
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
meta {
|
||||||
|
name: Flatten PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/flatten
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: flattened
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
44
.bruno/PDF Engines/Merge/Merge PDFs.bru
Normal file
44
.bruno/PDF Engines/Merge/Merge PDFs.bru
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
meta {
|
||||||
|
name: Merge PDFs
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/merge
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
files: @file(../../test/integration/testdata/page_2.pdf)
|
||||||
|
~flatten: false
|
||||||
|
~autoIndexBookmarks: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~bookmarks: [{"title":"Page 1","page":1},{"title":"Page 2","page":2}]
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: merged
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
24
.bruno/PDF Engines/Metadata/Read Metadata.bru
Normal file
24
.bruno/PDF Engines/Metadata/Read Metadata.bru
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
meta {
|
||||||
|
name: Read Metadata
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/metadata/read
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
26
.bruno/PDF Engines/Metadata/Write Metadata.bru
Normal file
26
.bruno/PDF Engines/Metadata/Write Metadata.bru
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
meta {
|
||||||
|
name: Write Metadata
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/metadata/write
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
metadata: {"Author":"Bruno","Copyright":"Bruno","Creator":"Gotenberg","Keywords":["test"],"Marked":true,"Producer":"Gotenberg","Subject":"Test","Title":"Test Document","Trapped":"Unknown"}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: with-metadata
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
27
.bruno/PDF Engines/Rotate/Rotate PDF.bru
Normal file
27
.bruno/PDF Engines/Rotate/Rotate PDF.bru
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Rotate PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/rotate
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
rotateAngle: 90
|
||||||
|
~rotatePages: 1-2
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: rotated
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
44
.bruno/PDF Engines/Split/Split PDF.bru
Normal file
44
.bruno/PDF Engines/Split/Split PDF.bru
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
meta {
|
||||||
|
name: Split PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/split
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/pages_3.pdf)
|
||||||
|
splitMode: intervals
|
||||||
|
splitSpan: 1
|
||||||
|
~splitUnify: false
|
||||||
|
~flatten: false
|
||||||
|
~pdfa: PDF/A-1b
|
||||||
|
~pdfua: true
|
||||||
|
~metadata: {"Author":"Bruno","Title":"Test"}
|
||||||
|
~userPassword:
|
||||||
|
~ownerPassword:
|
||||||
|
~watermarkSource: text
|
||||||
|
~watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages:
|
||||||
|
~watermarkOptions: {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"}
|
||||||
|
~stampSource: text
|
||||||
|
~stampExpression: DRAFT
|
||||||
|
~stampPages:
|
||||||
|
~stampOptions: {"scale":"0.5 abs","rot":"45"}
|
||||||
|
~rotateAngle: 90
|
||||||
|
~rotatePages:
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: split
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
31
.bruno/PDF Engines/Stamp/Stamp PDF.bru
Normal file
31
.bruno/PDF Engines/Stamp/Stamp PDF.bru
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
meta {
|
||||||
|
name: Stamp PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/stamp
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
stampSource: text
|
||||||
|
stampExpression: APPROVED
|
||||||
|
~stampPages: 1-2
|
||||||
|
~stampOptions: {"font":"Helvetica","fontSize":"48","color":"#00FF00","opacity":"0.5","rotation":"0"}
|
||||||
|
~stamp: @file(../../test/integration/testdata/watermark.png)
|
||||||
|
~downloadFrom: [{"url":"https://example.com/stamp.png","field":"stamp"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: stamped
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
31
.bruno/PDF Engines/Watermark/Watermark PDF.bru
Normal file
31
.bruno/PDF Engines/Watermark/Watermark PDF.bru
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
meta {
|
||||||
|
name: Watermark PDF
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/pdfengines/watermark
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/page_1.pdf)
|
||||||
|
watermarkSource: text
|
||||||
|
watermarkExpression: CONFIDENTIAL
|
||||||
|
~watermarkPages: 1-2
|
||||||
|
~watermarkOptions: {"font":"Helvetica","fontSize":"48","color":"#FF0000","opacity":"0.3","rotation":"45"}
|
||||||
|
~watermark: @file(../../test/integration/testdata/watermark.png)
|
||||||
|
~downloadFrom: [{"url":"https://example.com/watermark.png","field":"watermark"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: watermarked
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
65
.bruno/README.md
Normal file
65
.bruno/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Bruno API Collection
|
||||||
|
|
||||||
|
[Bruno](https://www.usebruno.com/) collection mirroring every Gotenberg route. Update the collection when adding or modifying a route.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.bruno/
|
||||||
|
├── bruno.json # Collection config
|
||||||
|
├── collection.bru # Collection-level defaults (Gotenberg-Trace header)
|
||||||
|
├── environments/
|
||||||
|
│ ├── Local.bru # baseUrl: http://localhost:3000
|
||||||
|
│ └── Demo.bru # baseUrl: https://demo.gotenberg.dev
|
||||||
|
├── Health & Info/ # GET routes
|
||||||
|
├── Chromium/Convert/ # POST routes grouped by module
|
||||||
|
├── Chromium/Screenshot/
|
||||||
|
├── LibreOffice/
|
||||||
|
└── PDF Engines/<Feature>/ # One folder per feature (Merge, Split, Rotate, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `.bru` file format
|
||||||
|
|
||||||
|
```bru
|
||||||
|
meta {
|
||||||
|
name: <Human-readable name>
|
||||||
|
type: http
|
||||||
|
seq: <order within folder>
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/forms/<path>
|
||||||
|
body: multipartForm
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:multipart-form {
|
||||||
|
files: @file(../../test/integration/testdata/<file>)
|
||||||
|
<mandatoryField>: <value>
|
||||||
|
~<optionalField>: <value>
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
~Gotenberg-Output-Filename: <name>
|
||||||
|
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||||
|
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||||
|
~Gotenberg-Webhook-Method: POST
|
||||||
|
~Gotenberg-Webhook-Error-Method: POST
|
||||||
|
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Mandatory fields have no prefix. Optional fields use `~` (disabled by default in Bruno).
|
||||||
|
- File references use relative paths to `test/integration/testdata/`.
|
||||||
|
- Webhook and output filename headers appear on every POST route as optional (`~`).
|
||||||
|
- One `.bru` file per request. For routes with read/write variants (e.g., bookmarks, metadata), create separate files in the same folder.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
1. Create or update the `.bru` file in the matching folder under `.bruno/`.
|
||||||
|
2. Include all form fields from the route handler. Check `FormData*` calls in the route function.
|
||||||
|
3. For file upload fields (`files`, `watermark`, `stamp`, `embeds`), use `@file(...)` with a suitable test file.
|
||||||
|
4. Verify the URL path matches the route's `Path` field exactly.
|
||||||
|
5. For new module folders, keep the naming consistent (e.g., `PDF Engines/Rotate/`).
|
||||||
6
.bruno/bruno.json
Normal file
6
.bruno/bruno.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"name": "Gotenberg",
|
||||||
|
"type": "collection",
|
||||||
|
"ignore": ["node_modules", ".git"]
|
||||||
|
}
|
||||||
3
.bruno/collection.bru
Normal file
3
.bruno/collection.bru
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
headers {
|
||||||
|
Gotenberg-Trace: {{traceId}}
|
||||||
|
}
|
||||||
4
.bruno/environments/Demo.bru
Normal file
4
.bruno/environments/Demo.bru
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
vars {
|
||||||
|
baseUrl: https://demo.gotenberg.dev
|
||||||
|
traceId: bruno-demo
|
||||||
|
}
|
||||||
4
.bruno/environments/Local.bru
Normal file
4
.bruno/environments/Local.bru
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
vars {
|
||||||
|
baseUrl: http://localhost:3000
|
||||||
|
traceId: bruno-local
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -xe
|
|
||||||
|
|
||||||
# Statically checking Go source for errors and warnings.
|
|
||||||
gometalinter.v2 --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode -E gocyclo --vendor ./...;
|
|
||||||
|
|
||||||
# Running tests according to current Gotenberg version.
|
|
||||||
if [[ "$VERSION" == "snapshot" ]]; then
|
|
||||||
for d in $(go list ./... | grep -v vendor); do
|
|
||||||
go test -race -cover -covermode=atomic $d;
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "" > .ci/coverage.txt;
|
|
||||||
for d in $(go list ./... | grep -v vendor); do
|
|
||||||
go test -race -coverprofile=profile.out -covermode=atomic $d;
|
|
||||||
if [ -f profile.out ]; then
|
|
||||||
cat profile.out >> .ci/coverage.txt;
|
|
||||||
rm profile.out;
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Builds the Linux binary.
|
|
||||||
if [ -f .ci/gotenberg ]; then
|
|
||||||
rm .ci/gotenberg
|
|
||||||
fi
|
|
||||||
|
|
||||||
env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.version=${VERSION}" && mv gotenberg .ci/;
|
|
||||||
|
|
||||||
# Bye!
|
|
||||||
exit 0;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# The port the application will listen to.
|
|
||||||
port: 3000
|
|
||||||
|
|
||||||
logs:
|
|
||||||
# Accepted values, in order of severity: DEBUG, INFO, WARN, ERROR, FATAL, PANIC.
|
|
||||||
# Messages at and above the selected level will be logged.
|
|
||||||
level: "INFO"
|
|
||||||
|
|
||||||
# Accepted values: text, json.
|
|
||||||
# When a TTY is not attached, the output will be in the defined format.
|
|
||||||
formatter: "text"
|
|
||||||
|
|
||||||
# You don't like a library which is used for a conversion? You want to handle a new file type?
|
|
||||||
# You may provide here your own implementation!
|
|
||||||
commands:
|
|
||||||
|
|
||||||
# Some libraries like unoconv cannot perform concurrent conversions. That's why the API does only one conversion at a time.
|
|
||||||
# If your current implementation uses libraries which are able to perform concurrent conversions, you may
|
|
||||||
# change this value to false.
|
|
||||||
lock: true
|
|
||||||
|
|
||||||
# Unlike others commands' templates, you have access to FilesPaths instead of FilePath: it gathers all PDF files which should be merged.
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
|
|
||||||
# You may add more commands (or less, or even none).
|
|
||||||
conversions:
|
|
||||||
|
|
||||||
# The command template: you have access to FilePath and ResultFilePath variables.
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
# The binary which will call the command.
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
# Duration in seconds after which the command will be killed if it has not finished.
|
|
||||||
timeout: 30
|
|
||||||
# Files with the following extensions will be converted by the current command.
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.git
|
||||||
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
GOTENBERG_VERSION=snapshot
|
||||||
|
DOCKER_REGISTRY=gotenberg
|
||||||
|
DOCKER_REPOSITORY=gotenberg
|
||||||
|
DOCKERFILE=build/Dockerfile
|
||||||
|
DOCKER_BUILD_CONTEXT='.'
|
||||||
|
TARGET=gotenberg
|
||||||
46
.github/CODE_OF_CONDUCT.md
vendored
46
.github/CODE_OF_CONDUCT.md
vendored
@@ -1,46 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at neuhart.julien@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
||||||
33
.github/CONTRIBUTING.md
vendored
33
.github/CONTRIBUTING.md
vendored
@@ -1,33 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
Hi! Thank your for considering contributing to Gotenberg. You'll
|
|
||||||
find below useful information about how to contribute to the Gotenberg project.
|
|
||||||
|
|
||||||
## Contributing code
|
|
||||||
|
|
||||||
### Install from sources
|
|
||||||
|
|
||||||
1. Fork this repository
|
|
||||||
2. Clone it to your local Go environment (requires *Go* >= 1.10)
|
|
||||||
3. Install the latest [Orbit](https://github.com/gulien/orbit) release
|
|
||||||
|
|
||||||
### Working with git
|
|
||||||
|
|
||||||
1. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
2. Commit your changes (`git commit -am 'Add some feature'`)
|
|
||||||
3. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
4. Create a new pull request
|
|
||||||
|
|
||||||
## Reporting bugs and feature request
|
|
||||||
|
|
||||||
Your issue or feature request may already be reported!
|
|
||||||
Please search on the [issue tracker](../../../issues) before creating one.
|
|
||||||
|
|
||||||
If you do not find any relevant issue or feature request, feel free to
|
|
||||||
add a new one!
|
|
||||||
|
|
||||||
## Additional resources
|
|
||||||
|
|
||||||
* [Code of conduct](CODE_OF_CONDUCT.md)
|
|
||||||
* [Issue template](ISSUE_TEMPLATE.md)
|
|
||||||
* [Pull request template](PULL_REQUEST_TEMPLATE.md)
|
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: [gulien]
|
||||||
32
.github/ISSUE_TEMPLATE.md
vendored
32
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,32 +0,0 @@
|
|||||||
Your issue may already be reported!
|
|
||||||
Please search on the [issue tracker](../../../issues) before creating one.
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
<!--- If you're describing a bug, tell us what should happen -->
|
|
||||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
|
||||||
|
|
||||||
## Current Behavior
|
|
||||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
|
||||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
|
||||||
|
|
||||||
## Possible Solution
|
|
||||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!--- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
## Steps to Reproduce (for bugs)
|
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
## Context
|
|
||||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
|
||||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
|
||||||
|
|
||||||
## Your Environment
|
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
|
||||||
* Version used:
|
|
||||||
* Operating System and version:
|
|
||||||
* Link to your project:
|
|
||||||
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,44 +0,0 @@
|
|||||||
A similar PR may already be submitted!
|
|
||||||
Please search among the [pull requests](../../../pulls) before creating one.
|
|
||||||
|
|
||||||
Thanks for submitting a pull request! Please provide enough information so that others can review your pull request:
|
|
||||||
|
|
||||||
For more information, see the [CONTRIBUTING](CONTRIBUTING.md) guide.
|
|
||||||
|
|
||||||
**Summary**
|
|
||||||
|
|
||||||
<!-- Summary of the PR -->
|
|
||||||
|
|
||||||
This PR fixes/implements the following **bugs/features**
|
|
||||||
|
|
||||||
* [ ] Bug 1
|
|
||||||
* [ ] Bug 2
|
|
||||||
* [ ] Feature 1
|
|
||||||
* [ ] Feature 2
|
|
||||||
* [ ] Breaking changes
|
|
||||||
|
|
||||||
<!-- You can skip this if you're fixing a typo or adding an app to the Showcase. -->
|
|
||||||
|
|
||||||
Explain the **motivation** for making this change. What existing problem does the pull request solve?
|
|
||||||
|
|
||||||
<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->
|
|
||||||
|
|
||||||
**Test plan (required)**
|
|
||||||
|
|
||||||
Demonstrate the code is solid. Example: The exact commands you ran and their output.
|
|
||||||
|
|
||||||
<!-- Make sure tests pass on both Travis and AppVeyor. -->
|
|
||||||
|
|
||||||
**Closing issues**
|
|
||||||
|
|
||||||
<!-- Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). -->
|
|
||||||
Fixes #
|
|
||||||
|
|
||||||
**Checklist**
|
|
||||||
|
|
||||||
- [ ] Have you followed the guidelines in our [CONTRIBUTING](CONTRIBUTING.md) guide?
|
|
||||||
- [ ] Have you lint your code locally prior to submission (`orbit run fmt`)?
|
|
||||||
- [ ] Have you written new tests for your core changes, as applicable?
|
|
||||||
- [ ] Have you successfully ran tests with your changes locally (`orbit run ci`)?
|
|
||||||
- [ ] I have squashed any insignificant commits
|
|
||||||
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
|
|
||||||
130
.github/actions/build-test-push/action.yml
vendored
Normal file
130
.github/actions/build-test-push/action.yml
vendored
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
name: Build Test Push
|
||||||
|
description: Build, test and push Docker images for a given platform
|
||||||
|
author: Julien Neuhart
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
github_token:
|
||||||
|
description: The GitHub token
|
||||||
|
required: true
|
||||||
|
default: ${{ github.token }}
|
||||||
|
docker_hub_username:
|
||||||
|
description: The Docker Hub username
|
||||||
|
required: true
|
||||||
|
docker_hub_password:
|
||||||
|
description: The Docker Hub password
|
||||||
|
required: true
|
||||||
|
platform:
|
||||||
|
description: linux/amd64, linux/ppc64le, linux/386, linux/arm64, linux/arm/v7
|
||||||
|
required: true
|
||||||
|
version:
|
||||||
|
description: Gotenberg version
|
||||||
|
required: true
|
||||||
|
skip_integrations_tests:
|
||||||
|
description: Define whether to skip integration testing
|
||||||
|
default: false
|
||||||
|
alternate_repository:
|
||||||
|
description: Alternate repository to push the tags to
|
||||||
|
dry_run:
|
||||||
|
description: Dry run this action
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
tags:
|
||||||
|
description: Comma separated list of full variant tags
|
||||||
|
value: ${{ steps.build.outputs.tags }}
|
||||||
|
tags_chromium:
|
||||||
|
description: Comma separated list of Chromium variant tags
|
||||||
|
value: ${{ steps.build.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice:
|
||||||
|
description: Comma separated list of LibreOffice variant tags
|
||||||
|
value: ${{ steps.build.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run:
|
||||||
|
description: Comma separated list of Cloud Run full variant tags (linux/amd64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium:
|
||||||
|
description: Comma separated list of Cloud Run Chromium variant tags (linux/amd64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice:
|
||||||
|
description: Comma separated list of Cloud Run LibreOffice variant tags (linux/amd64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda:
|
||||||
|
description: Comma separated list of AWS Lambda full variant tags (linux/amd64 and linux/arm64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium:
|
||||||
|
description: Comma separated list of AWS Lambda Chromium variant tags (linux/amd64 and linux/arm64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice:
|
||||||
|
description: Comma separated list of AWS Lambda LibreOffice variant tags (linux/amd64 and linux/arm64 only)
|
||||||
|
value: ${{ steps.build.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
if: inputs.docker_hub_username != ''
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ inputs.docker_hub_username }}
|
||||||
|
password: ${{ inputs.docker_hub_password }}
|
||||||
|
|
||||||
|
- name: Build ${{ inputs.platform }}
|
||||||
|
id: build
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_VERSION: ${{ inputs.version }}
|
||||||
|
INPUT_PLATFORM: ${{ inputs.platform }}
|
||||||
|
INPUT_ALTERNATE_REPOSITORY: ${{ inputs.alternate_repository }}
|
||||||
|
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
.github/actions/build-test-push/build.sh \
|
||||||
|
--version "$INPUT_VERSION" \
|
||||||
|
--platform "$INPUT_PLATFORM" \
|
||||||
|
--alternate-repository "$INPUT_ALTERNATE_REPOSITORY" \
|
||||||
|
--dry-run "$INPUT_DRY_RUN"
|
||||||
|
|
||||||
|
- name: Run integration tests
|
||||||
|
if: inputs.skip_integrations_tests != 'true'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_VERSION: ${{ inputs.version }}
|
||||||
|
INPUT_PLATFORM: ${{ inputs.platform }}
|
||||||
|
INPUT_ALTERNATE_REPOSITORY: ${{ inputs.alternate_repository }}
|
||||||
|
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
.github/actions/build-test-push/test.sh \
|
||||||
|
--version "$INPUT_VERSION" \
|
||||||
|
--platform "$INPUT_PLATFORM" \
|
||||||
|
--alternate-repository "$INPUT_ALTERNATE_REPOSITORY" \
|
||||||
|
--dry-run "$INPUT_DRY_RUN"
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
if: inputs.docker_hub_username != ''
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_TAGS: ${{ steps.build.outputs.tags }},${{ steps.build.outputs.tags_chromium }},${{ steps.build.outputs.tags_libreoffice }},${{ steps.build.outputs.tags_cloud_run }},${{ steps.build.outputs.tags_cloud_run_chromium }},${{ steps.build.outputs.tags_cloud_run_libreoffice }},${{ steps.build.outputs.tags_aws_lambda }},${{ steps.build.outputs.tags_aws_lambda_chromium }},${{ steps.build.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
.github/actions/build-test-push/push.sh \
|
||||||
|
--tags "$INPUT_TAGS" \
|
||||||
|
--dry-run "$INPUT_DRY_RUN"
|
||||||
|
|
||||||
|
- name: Outputs
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "tags=${{ steps.build.outputs.tags }}"
|
||||||
|
echo "tags_chromium=${{ steps.build.outputs.tags_chromium }}"
|
||||||
|
echo "tags_libreoffice=${{ steps.build.outputs.tags_libreoffice }}"
|
||||||
|
echo "tags_cloud_run=${{ steps.build.outputs.tags_cloud_run }}"
|
||||||
|
echo "tags_cloud_run_chromium=${{ steps.build.outputs.tags_cloud_run_chromium }}"
|
||||||
|
echo "tags_cloud_run_libreoffice=${{ steps.build.outputs.tags_cloud_run_libreoffice }}"
|
||||||
|
echo "tags_aws_lambda=${{ steps.build.outputs.tags_aws_lambda }}"
|
||||||
|
echo "tags_aws_lambda_chromium=${{ steps.build.outputs.tags_aws_lambda_chromium }}"
|
||||||
|
echo "tags_aws_lambda_libreoffice=${{ steps.build.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
304
.github/actions/build-test-push/build.sh
vendored
Executable file
304
.github/actions/build-test-push/build.sh
vendored
Executable file
@@ -0,0 +1,304 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit early.
|
||||||
|
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source dot env file.
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
version=""
|
||||||
|
platform=""
|
||||||
|
alternate_repository=""
|
||||||
|
dry_run=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--version)
|
||||||
|
version="${2//v/}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--platform)
|
||||||
|
platform="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--alternate-repository)
|
||||||
|
alternate_repository="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Build and push 👷"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Gotenberg version: $version"
|
||||||
|
echo "Target platform: $platform"
|
||||||
|
|
||||||
|
if [ -n "$alternate_repository" ]; then
|
||||||
|
DOCKER_REPOSITORY=$alternate_repository
|
||||||
|
echo "⚠️ Using $alternate_repository for DOCKER_REPOSITORY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build tags arrays.
|
||||||
|
tags=()
|
||||||
|
tags_chromium=()
|
||||||
|
tags_libreoffice=()
|
||||||
|
tags_cloud_run=()
|
||||||
|
tags_cloud_run_chromium=()
|
||||||
|
tags_cloud_run_libreoffice=()
|
||||||
|
tags_aws_lambda=()
|
||||||
|
tags_aws_lambda_chromium=()
|
||||||
|
tags_aws_lambda_libreoffice=()
|
||||||
|
|
||||||
|
IFS='/' read -ra arch <<< "$platform"
|
||||||
|
IFS='.' read -ra semver <<< "$version"
|
||||||
|
|
||||||
|
if [ "${#semver[@]}" -eq 3 ]; then
|
||||||
|
echo
|
||||||
|
echo "Semver version detected"
|
||||||
|
|
||||||
|
major="${semver[0]}"
|
||||||
|
minor="${semver[1]}"
|
||||||
|
patch="${semver[2]}"
|
||||||
|
|
||||||
|
for suffix in "latest" "$major" "$major.$minor" "$major.$minor.$patch"; do
|
||||||
|
tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-${arch[1]}")
|
||||||
|
tags_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-chromium-${arch[1]}")
|
||||||
|
tags_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-libreoffice-${arch[1]}")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$platform" = "linux/amd64" ]; then
|
||||||
|
for suffix in "latest" "$major" "$major.$minor" "$major.$minor.$patch"; do
|
||||||
|
tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-cloudrun")
|
||||||
|
tags_cloud_run_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-chromium-cloudrun")
|
||||||
|
tags_cloud_run_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-libreoffice-cloudrun")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$platform" = "linux/amd64" ] || [ "$platform" = "linux/arm64" ]; then
|
||||||
|
for suffix in "latest" "$major" "$major.$minor" "$major.$minor.$patch"; do
|
||||||
|
tags_aws_lambda+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-aws-lambda-${arch[1]}")
|
||||||
|
tags_aws_lambda_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-chromium-aws-lambda-${arch[1]}")
|
||||||
|
tags_aws_lambda_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$suffix-libreoffice-aws-lambda-${arch[1]}")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "Non-semver version detected, fallback to $version"
|
||||||
|
|
||||||
|
tags+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-${arch[1]}")
|
||||||
|
tags_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-chromium-${arch[1]}")
|
||||||
|
tags_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-libreoffice-${arch[1]}")
|
||||||
|
|
||||||
|
if [ "$platform" = "linux/amd64" ]; then
|
||||||
|
tags_cloud_run+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-cloudrun")
|
||||||
|
tags_cloud_run_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-chromium-cloudrun")
|
||||||
|
tags_cloud_run_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-libreoffice-cloudrun")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$platform" = "linux/amd64" ] || [ "$platform" = "linux/arm64" ]; then
|
||||||
|
tags_aws_lambda+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-aws-lambda-${arch[1]}")
|
||||||
|
tags_aws_lambda_chromium+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-chromium-aws-lambda-${arch[1]}")
|
||||||
|
tags_aws_lambda_libreoffice+=("$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version-libreoffice-aws-lambda-${arch[1]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
tags_flags=()
|
||||||
|
tags_chromium_flags=()
|
||||||
|
tags_libreoffice_flags=()
|
||||||
|
tags_cloud_run_flags=()
|
||||||
|
tags_cloud_run_chromium_flags=()
|
||||||
|
tags_cloud_run_libreoffice_flags=()
|
||||||
|
tags_aws_lambda_flags=()
|
||||||
|
tags_aws_lambda_chromium_flags=()
|
||||||
|
tags_aws_lambda_libreoffice_flags=()
|
||||||
|
|
||||||
|
echo "Will use the following tags:"
|
||||||
|
for tag in "${tags[@]}"; do
|
||||||
|
tags_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_chromium[@]}"; do
|
||||||
|
tags_chromium_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_libreoffice[@]}"; do
|
||||||
|
tags_libreoffice_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_cloud_run[@]}"; do
|
||||||
|
tags_cloud_run_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_cloud_run_chromium[@]}"; do
|
||||||
|
tags_cloud_run_chromium_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_cloud_run_libreoffice[@]}"; do
|
||||||
|
tags_cloud_run_libreoffice_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_aws_lambda[@]}"; do
|
||||||
|
tags_aws_lambda_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_aws_lambda_chromium[@]}"; do
|
||||||
|
tags_aws_lambda_chromium_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
for tag in "${tags_aws_lambda_libreoffice[@]}"; do
|
||||||
|
tags_aws_lambda_libreoffice_flags+=("-t" "$tag")
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Build images.
|
||||||
|
run_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run - would run the following command:"
|
||||||
|
echo "$cmd"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "⚙️ Running command:"
|
||||||
|
echo "$cmd"
|
||||||
|
eval "$cmd"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
join() {
|
||||||
|
local delimiter="$1"
|
||||||
|
shift
|
||||||
|
local IFS="$delimiter"
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
no_arch_tag="$DOCKER_REGISTRY/$DOCKER_REPOSITORY:$version"
|
||||||
|
|
||||||
|
# Full variant.
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_flags[*]} \
|
||||||
|
-t $no_arch_tag \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
# Chromium only variant.
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-chromium \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_chromium_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
# LibreOffice only variant.
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-libreoffice \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_libreoffice_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
# Cloud Run variants (amd64 only).
|
||||||
|
if [ "$platform" = "linux/amd64" ]; then
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-cloudrun \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_cloud_run_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-cloudrun-chromium \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_cloud_run_chromium_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-cloudrun-libreoffice \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_cloud_run_libreoffice_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# AWS Lambda variants (amd64 + arm64 only).
|
||||||
|
if [ "$platform" = "linux/amd64" ] || [ "$platform" = "linux/arm64" ]; then
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-aws-lambda \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_aws_lambda_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-aws-lambda-chromium \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_aws_lambda_chromium_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
cmd="docker buildx build \
|
||||||
|
--target gotenberg-aws-lambda-libreoffice \
|
||||||
|
--build-arg GOTENBERG_VERSION=$version \
|
||||||
|
--platform $platform \
|
||||||
|
--load \
|
||||||
|
${tags_aws_lambda_libreoffice_flags[*]} \
|
||||||
|
-f $DOCKERFILE $DOCKER_BUILD_CONTEXT
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Done!"
|
||||||
|
echo "tags=$(join "," "${tags[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_chromium=$(join "," "${tags_chromium[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_libreoffice=$(join "," "${tags_libreoffice[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_cloud_run=$(join "," "${tags_cloud_run[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_cloud_run_chromium=$(join "," "${tags_cloud_run_chromium[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_cloud_run_libreoffice=$(join "," "${tags_cloud_run_libreoffice[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_aws_lambda=$(join "," "${tags_aws_lambda[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_aws_lambda_chromium=$(join "," "${tags_aws_lambda_chromium[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tags_aws_lambda_libreoffice=$(join "," "${tags_aws_lambda_libreoffice[@]}")" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
76
.github/actions/build-test-push/push.sh
vendored
Executable file
76
.github/actions/build-test-push/push.sh
vendored
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit early.
|
||||||
|
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source dot env file.
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
tags=""
|
||||||
|
dry_run=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--tags)
|
||||||
|
tags="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Push tag(s) 📦"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Tag(s) to push:"
|
||||||
|
IFS=',' read -ra tmp_tags_to_push <<< "$tags"
|
||||||
|
|
||||||
|
tags_to_push=()
|
||||||
|
for tag in "${tmp_tags_to_push[@]}"; do
|
||||||
|
[ -n "$tag" ] && tags_to_push+=("$tag")
|
||||||
|
done
|
||||||
|
|
||||||
|
for tag in "${tags_to_push[@]}"; do
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Push tags.
|
||||||
|
run_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run - would run the following command:"
|
||||||
|
echo "$cmd"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "⚙️ Running command:"
|
||||||
|
echo "$cmd"
|
||||||
|
eval "$cmd"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag in "${tags_to_push[@]}"; do
|
||||||
|
cmd="docker push $tag"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
echo "➡️ $tag pushed"
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Done!"
|
||||||
|
exit 0
|
||||||
79
.github/actions/build-test-push/test.sh
vendored
Executable file
79
.github/actions/build-test-push/test.sh
vendored
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit early.
|
||||||
|
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source dot env file.
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
version=""
|
||||||
|
platform=""
|
||||||
|
alternate_repository=""
|
||||||
|
dry_run=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--version)
|
||||||
|
version="${2//v/}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--platform)
|
||||||
|
platform="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--alternate-repository)
|
||||||
|
alternate_repository="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Integration testing 🧪"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Gotenberg version: $version"
|
||||||
|
echo "Target platform: $platform"
|
||||||
|
|
||||||
|
repository=$DOCKER_REPOSITORY
|
||||||
|
|
||||||
|
if [ -n "$alternate_repository" ]; then
|
||||||
|
echo "⚠️ Using $alternate_repository for DOCKER_REPOSITORY"
|
||||||
|
repository=$alternate_repository
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Test image.
|
||||||
|
run_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run - would run the following command:"
|
||||||
|
echo "$cmd"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "⚙️ Running command:"
|
||||||
|
echo "$cmd"
|
||||||
|
eval "$cmd"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd="make test-integration DOCKER_REPOSITORY=$repository GOTENBERG_VERSION=$version PLATFORM=$platform NO_CONCURRENCY=true"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
echo "✅ Done!"
|
||||||
|
exit 0
|
||||||
34
.github/actions/clean/action.yml
vendored
Normal file
34
.github/actions/clean/action.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Clean
|
||||||
|
description: Clean tags from Docker Hub
|
||||||
|
author: Julien Neuhart
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
docker_hub_username:
|
||||||
|
description: The Docker Hub username
|
||||||
|
required: true
|
||||||
|
docker_hub_password:
|
||||||
|
description: The Docker Hub password
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
description: Comma separated list of tags to clean
|
||||||
|
snapshot_version:
|
||||||
|
description: Snapshot version to clean
|
||||||
|
dry_run:
|
||||||
|
description: Dry run this action
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Clean tags from Docker Hub
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ inputs.docker_hub_username }}
|
||||||
|
DOCKERHUB_TOKEN: ${{ inputs.docker_hub_password }}
|
||||||
|
INPUT_TAGS: ${{ inputs.tags }}
|
||||||
|
INPUT_SNAPSHOT_VERSION: ${{ inputs.snapshot_version }}
|
||||||
|
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
.github/actions/clean/clean.sh \
|
||||||
|
--tags "$INPUT_TAGS" \
|
||||||
|
--snapshot-version "$INPUT_SNAPSHOT_VERSION" \
|
||||||
|
--dry-run "$INPUT_DRY_RUN"
|
||||||
123
.github/actions/clean/clean.sh
vendored
Executable file
123
.github/actions/clean/clean.sh
vendored
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit early.
|
||||||
|
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source dot env file.
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
tags=""
|
||||||
|
snapshot_version=""
|
||||||
|
dry_run=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--tags)
|
||||||
|
tags="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--snapshot-version)
|
||||||
|
snapshot_version="${2//v/}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Clean tag(s) from Docker Hub 🧹"
|
||||||
|
echo
|
||||||
|
|
||||||
|
IFS=',' read -ra tags_to_delete <<< "$tags"
|
||||||
|
if [ -n "$snapshot_version" ]; then
|
||||||
|
tags_to_delete+=("$DOCKER_REGISTRY/snapshot:$snapshot_version")
|
||||||
|
tags_to_delete+=("$DOCKER_REGISTRY/snapshot:$snapshot_version-cloudrun")
|
||||||
|
tags_to_delete+=("$DOCKER_REGISTRY/snapshot:$snapshot_version-aws-lambda")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Will delete the following tag(s):"
|
||||||
|
for tag in "${tags_to_delete[@]}"; do
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Delete tags.
|
||||||
|
base_url="https://hub.docker.com/v2"
|
||||||
|
token=""
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
token="placeholder"
|
||||||
|
echo "🚧 Dry run - would call $base_url to get a token"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "🌐 Get token from $base_url"
|
||||||
|
|
||||||
|
readarray -t lines < <(
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"$DOCKERHUB_USERNAME\", \"password\":\"$DOCKERHUB_TOKEN\"}" \
|
||||||
|
-w "\n%{http_code}" \
|
||||||
|
"$base_url/users/login"
|
||||||
|
)
|
||||||
|
|
||||||
|
http_code="${lines[-1]}"
|
||||||
|
unset 'lines[-1]'
|
||||||
|
json_body=$(printf "%s\n" "${lines[@]}")
|
||||||
|
|
||||||
|
if [ "$http_code" -ne "200" ]; then
|
||||||
|
echo "❌ Wrong HTTP status - $http_code"
|
||||||
|
echo "$json_body"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
token=$(jq -r '.token' <<< "$json_body")
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$token" ]; then
|
||||||
|
echo "❌ No token from Docker Hub"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for tag in "${tags_to_delete[@]}"; do
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run - would call $base_url to delete tag $tag"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "🌐 Delete tag $tag"
|
||||||
|
IFS=':' read -ra tag_parts <<< "$tag"
|
||||||
|
|
||||||
|
readarray -t lines < <(
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: Bearer $token" \
|
||||||
|
-w "\n%{http_code}" \
|
||||||
|
"$base_url/repositories/${tag_parts[0]}/tags/${tag_parts[1]}/"
|
||||||
|
)
|
||||||
|
|
||||||
|
http_code="${lines[-1]}"
|
||||||
|
unset 'lines[-1]'
|
||||||
|
|
||||||
|
if [ "$http_code" -ne "200" ] && [ "$http_code" -ne "204" ]; then
|
||||||
|
echo "❌ Wrong HTTP status - $http_code"
|
||||||
|
printf '%s\n' "${lines[@]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Done!"
|
||||||
|
exit 0
|
||||||
52
.github/actions/merge/action.yml
vendored
Normal file
52
.github/actions/merge/action.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: Merge
|
||||||
|
description: Merge tags to single multi-platform tags
|
||||||
|
author: Julien Neuhart
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
github_token:
|
||||||
|
description: The GitHub token
|
||||||
|
required: true
|
||||||
|
default: ${{ github.token }}
|
||||||
|
docker_hub_username:
|
||||||
|
description: The Docker Hub username
|
||||||
|
required: true
|
||||||
|
docker_hub_password:
|
||||||
|
description: The Docker Hub password
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
description: Comma separated tags to merge
|
||||||
|
required: true
|
||||||
|
alternate_registry:
|
||||||
|
description: Alternate registry to also push resulting tags
|
||||||
|
dry_run:
|
||||||
|
description: Dry run this action
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ inputs.docker_hub_username }}
|
||||||
|
password: ${{ inputs.docker_hub_password }}
|
||||||
|
|
||||||
|
- name: Merge
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_TAGS: ${{ inputs.tags }}
|
||||||
|
INPUT_ALTERNATE_REGISTRY: ${{ inputs.alternate_registry }}
|
||||||
|
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
.github/actions/merge/merge.sh \
|
||||||
|
--tags "$INPUT_TAGS" \
|
||||||
|
--alternate-registry "$INPUT_ALTERNATE_REGISTRY" \
|
||||||
|
--dry-run "$INPUT_DRY_RUN"
|
||||||
108
.github/actions/merge/merge.sh
vendored
Executable file
108
.github/actions/merge/merge.sh
vendored
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit early.
|
||||||
|
# See: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#The-Set-Builtin.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Source dot env file.
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
tags=""
|
||||||
|
alternate_registry=""
|
||||||
|
dry_run=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--tags)
|
||||||
|
tags="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--alternate-registry)
|
||||||
|
alternate_registry="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Merge tag(s) 👷"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Tag(s) to merge:"
|
||||||
|
IFS=',' read -ra tags_to_merge <<< "$tags"
|
||||||
|
for tag in "${tags_to_merge[@]}"; do
|
||||||
|
echo "- $tag"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$alternate_registry" ]; then
|
||||||
|
echo "⚠️ Will also push to $alternate_registry registry"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Build merge map.
|
||||||
|
declare -A merge_map
|
||||||
|
|
||||||
|
for tag in "${tags_to_merge[@]}"; do
|
||||||
|
target_tag="${tag//-amd64/}"
|
||||||
|
target_tag="${target_tag//-ppc64le/}"
|
||||||
|
target_tag="${target_tag//-arm64/}"
|
||||||
|
target_tag="${target_tag//-arm/}"
|
||||||
|
target_tag="${target_tag//-386/}"
|
||||||
|
|
||||||
|
merge_map["$target_tag"]+="$tag "
|
||||||
|
done
|
||||||
|
|
||||||
|
# Merge tags.
|
||||||
|
run_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo "🚧 Dry run - would run the following command:"
|
||||||
|
echo "$cmd"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "⚙️ Running command:"
|
||||||
|
echo "$cmd"
|
||||||
|
eval "$cmd"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for target in "${!merge_map[@]}"; do
|
||||||
|
IFS=' ' read -ra source_tags <<< "${merge_map[$target]}"
|
||||||
|
|
||||||
|
cmd="docker buildx imagetools create \
|
||||||
|
-t $target \
|
||||||
|
${source_tags[*]}
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
echo "➡️ $target pushed"
|
||||||
|
echo
|
||||||
|
if [ -n "$alternate_registry" ]; then
|
||||||
|
alternate_target="${target/$DOCKER_REGISTRY/$alternate_registry}"
|
||||||
|
cmd="docker buildx imagetools create \
|
||||||
|
-t $alternate_target \
|
||||||
|
$target
|
||||||
|
"
|
||||||
|
run_cmd "$cmd"
|
||||||
|
|
||||||
|
echo "➡️ $alternate_target pushed"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Done!"
|
||||||
|
exit 0
|
||||||
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/build"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
18
.github/stale.yml
vendored
Normal file
18
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 7
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 3
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- bug
|
||||||
|
- documentation
|
||||||
|
- enhancement
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: wontfix
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
206
.github/workflows/continuous-delivery.yml
vendored
Normal file
206
.github/workflows/continuous-delivery.yml
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
name: Continuous Delivery
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release_amd64:
|
||||||
|
name: Release linux/amd64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
platform: linux/amd64
|
||||||
|
skip_integrations_tests: true
|
||||||
|
|
||||||
|
release_386:
|
||||||
|
name: Release linux/386
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
platform: linux/386
|
||||||
|
skip_integrations_tests: true
|
||||||
|
|
||||||
|
release_ppc64le:
|
||||||
|
name: Release linux/ppc64le
|
||||||
|
runs-on: ubuntu-24.04-ppc64le
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
platform: linux/ppc64le
|
||||||
|
skip_integrations_tests: true
|
||||||
|
|
||||||
|
release_arm64:
|
||||||
|
name: Release linux/arm64
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
platform: linux/arm64
|
||||||
|
skip_integrations_tests: true
|
||||||
|
|
||||||
|
release_arm_v7:
|
||||||
|
name: Release linux/arm/v7
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
platform: linux/arm/v7
|
||||||
|
skip_integrations_tests: true
|
||||||
|
|
||||||
|
merge_clean_release_tags:
|
||||||
|
needs:
|
||||||
|
- release_amd64
|
||||||
|
- release_386
|
||||||
|
- release_ppc64le
|
||||||
|
- release_arm64
|
||||||
|
- release_arm_v7
|
||||||
|
name: Merge and clean release tags
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Merge
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_ppc64le.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }}"
|
||||||
|
|
||||||
|
- name: Merge Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags_chromium }},${{ needs.release_386.outputs.tags_chromium }},${{ needs.release_ppc64le.outputs.tags_chromium }},${{ needs.release_arm64.outputs.tags_chromium }},${{ needs.release_arm_v7.outputs.tags_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags_libreoffice }},${{ needs.release_386.outputs.tags_libreoffice }},${{ needs.release_ppc64le.outputs.tags_libreoffice }},${{ needs.release_arm64.outputs.tags_libreoffice }},${{ needs.release_arm_v7.outputs.tags_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags_aws_lambda }},${{ needs.release_arm64.outputs.tags_aws_lambda }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.release_arm64.outputs.tags_aws_lambda_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.release_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Clean
|
||||||
|
uses: ./.github/actions/clean
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.release_amd64.outputs.tags }},${{ needs.release_386.outputs.tags }},${{ needs.release_ppc64le.outputs.tags }},${{ needs.release_arm64.outputs.tags }},${{ needs.release_arm_v7.outputs.tags }},${{ needs.release_amd64.outputs.tags_chromium }},${{ needs.release_386.outputs.tags_chromium }},${{ needs.release_ppc64le.outputs.tags_chromium }},${{ needs.release_arm64.outputs.tags_chromium }},${{ needs.release_arm_v7.outputs.tags_chromium }},${{ needs.release_amd64.outputs.tags_libreoffice }},${{ needs.release_386.outputs.tags_libreoffice }},${{ needs.release_ppc64le.outputs.tags_libreoffice }},${{ needs.release_arm64.outputs.tags_libreoffice }},${{ needs.release_arm_v7.outputs.tags_libreoffice }},${{ needs.release_amd64.outputs.tags_aws_lambda }},${{ needs.release_arm64.outputs.tags_aws_lambda }},${{ needs.release_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.release_arm64.outputs.tags_aws_lambda_chromium }},${{ needs.release_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.release_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
512
.github/workflows/continuous-integration.yml
vendored
Normal file
512
.github/workflows/continuous-integration.yml
vendored
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ (github.event_name == 'pull_request' && github.event.pull_request.number) || 'main' }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint Golang codebase
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
uses: golangci/golangci-lint-action@v9
|
||||||
|
with:
|
||||||
|
version: v2.10.1
|
||||||
|
|
||||||
|
lint-prettier:
|
||||||
|
name: Lint non-Golang codebase
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: .node-version
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci --ignore-scripts
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
run: make lint-prettier
|
||||||
|
|
||||||
|
test-unit:
|
||||||
|
needs:
|
||||||
|
- lint
|
||||||
|
- lint-prettier
|
||||||
|
name: Run unit tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: make test-unit
|
||||||
|
|
||||||
|
snapshot_amd64:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Snapshot linux/amd64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: pr-${{ github.event.pull_request.number }}
|
||||||
|
platform: linux/amd64
|
||||||
|
alternate_repository: snapshot
|
||||||
|
|
||||||
|
snapshot_ppc64le:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Snapshot linux/ppc64le
|
||||||
|
runs-on: ubuntu-24.04-ppc64le
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: pr-${{ github.event.pull_request.number }}
|
||||||
|
platform: linux/ppc64le
|
||||||
|
alternate_repository: snapshot
|
||||||
|
|
||||||
|
snapshot_386:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Snapshot linux/386
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: pr-${{ github.event.pull_request.number }}
|
||||||
|
platform: linux/386
|
||||||
|
alternate_repository: snapshot
|
||||||
|
|
||||||
|
snapshot_arm64:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Snapshot linux/arm64
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: pr-${{ github.event.pull_request.number }}
|
||||||
|
platform: linux/arm64
|
||||||
|
alternate_repository: snapshot
|
||||||
|
|
||||||
|
snapshot_arm_v7:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Snapshot linux/arm/v7
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: pr-${{ github.event.pull_request.number }}
|
||||||
|
platform: linux/arm/v7
|
||||||
|
alternate_repository: snapshot
|
||||||
|
|
||||||
|
merge_clean_snapshot_guard:
|
||||||
|
needs:
|
||||||
|
- snapshot_amd64
|
||||||
|
- snapshot_386
|
||||||
|
- snapshot_ppc64le
|
||||||
|
- snapshot_arm64
|
||||||
|
- snapshot_arm_v7
|
||||||
|
name: Secrets access check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
continue: ${{ steps.check.outputs.continue }}
|
||||||
|
steps:
|
||||||
|
- name: Check
|
||||||
|
id: check
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ secrets.DOCKERHUB_USERNAME }}" ]; then
|
||||||
|
echo "continue=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "continue=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
merge_clean_snapshot_tags:
|
||||||
|
if: needs.merge_clean_snapshot_guard.outputs.continue == 'true'
|
||||||
|
needs:
|
||||||
|
- merge_clean_snapshot_guard
|
||||||
|
- snapshot_amd64
|
||||||
|
- snapshot_386
|
||||||
|
- snapshot_ppc64le
|
||||||
|
- snapshot_arm64
|
||||||
|
- snapshot_arm_v7
|
||||||
|
name: Merge and clean snapshot tags
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Merge
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_ppc64le.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }}"
|
||||||
|
|
||||||
|
- name: Merge Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags_chromium }},${{ needs.snapshot_386.outputs.tags_chromium }},${{ needs.snapshot_ppc64le.outputs.tags_chromium }},${{ needs.snapshot_arm64.outputs.tags_chromium }},${{ needs.snapshot_arm_v7.outputs.tags_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags_libreoffice }},${{ needs.snapshot_386.outputs.tags_libreoffice }},${{ needs.snapshot_ppc64le.outputs.tags_libreoffice }},${{ needs.snapshot_arm64.outputs.tags_libreoffice }},${{ needs.snapshot_arm_v7.outputs.tags_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags_aws_lambda }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Clean
|
||||||
|
uses: ./.github/actions/clean
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.snapshot_amd64.outputs.tags }},${{ needs.snapshot_386.outputs.tags }},${{ needs.snapshot_ppc64le.outputs.tags }},${{ needs.snapshot_arm64.outputs.tags }},${{ needs.snapshot_arm_v7.outputs.tags }},${{ needs.snapshot_amd64.outputs.tags_chromium }},${{ needs.snapshot_386.outputs.tags_chromium }},${{ needs.snapshot_ppc64le.outputs.tags_chromium }},${{ needs.snapshot_arm64.outputs.tags_chromium }},${{ needs.snapshot_arm_v7.outputs.tags_chromium }},${{ needs.snapshot_amd64.outputs.tags_libreoffice }},${{ needs.snapshot_386.outputs.tags_libreoffice }},${{ needs.snapshot_ppc64le.outputs.tags_libreoffice }},${{ needs.snapshot_arm64.outputs.tags_libreoffice }},${{ needs.snapshot_arm_v7.outputs.tags_libreoffice }},${{ needs.snapshot_amd64.outputs.tags_aws_lambda }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda }},${{ needs.snapshot_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda_chromium }},${{ needs.snapshot_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.snapshot_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
|
|
||||||
|
edge_amd64:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Edge linux/amd64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: edge
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
edge_386:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Edge linux/386
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: edge
|
||||||
|
platform: linux/386
|
||||||
|
|
||||||
|
edge_ppc64le:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Edge linux/ppc64le
|
||||||
|
runs-on: ubuntu-24.04-ppc64le
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: edge
|
||||||
|
platform: linux/ppc64le
|
||||||
|
|
||||||
|
edge_arm64:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Edge linux/arm64
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: edge
|
||||||
|
platform: linux/arm64
|
||||||
|
|
||||||
|
edge_arm_v7:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-unit
|
||||||
|
name: Edge linux/arm/v7
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
outputs:
|
||||||
|
tags: ${{ steps.build_test_push.outputs.tags }}
|
||||||
|
tags_chromium: ${{ steps.build_test_push.outputs.tags_chromium }}
|
||||||
|
tags_libreoffice: ${{ steps.build_test_push.outputs.tags_libreoffice }}
|
||||||
|
tags_cloud_run: ${{ steps.build_test_push.outputs.tags_cloud_run }}
|
||||||
|
tags_cloud_run_chromium: ${{ steps.build_test_push.outputs.tags_cloud_run_chromium }}
|
||||||
|
tags_cloud_run_libreoffice: ${{ steps.build_test_push.outputs.tags_cloud_run_libreoffice }}
|
||||||
|
tags_aws_lambda: ${{ steps.build_test_push.outputs.tags_aws_lambda }}
|
||||||
|
tags_aws_lambda_chromium: ${{ steps.build_test_push.outputs.tags_aws_lambda_chromium }}
|
||||||
|
tags_aws_lambda_libreoffice: ${{ steps.build_test_push.outputs.tags_aws_lambda_libreoffice }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build, test and push
|
||||||
|
id: build_test_push
|
||||||
|
uses: ./.github/actions/build-test-push
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
version: edge
|
||||||
|
platform: linux/arm/v7
|
||||||
|
|
||||||
|
merge_clean_edge_tags:
|
||||||
|
needs:
|
||||||
|
- edge_amd64
|
||||||
|
- edge_386
|
||||||
|
- edge_ppc64le
|
||||||
|
- edge_arm64
|
||||||
|
- edge_arm_v7
|
||||||
|
name: Merge and clean edge tags
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Merge
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_ppc64le.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }}"
|
||||||
|
|
||||||
|
- name: Merge Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags_chromium }},${{ needs.edge_386.outputs.tags_chromium }},${{ needs.edge_ppc64le.outputs.tags_chromium }},${{ needs.edge_arm64.outputs.tags_chromium }},${{ needs.edge_arm_v7.outputs.tags_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags_libreoffice }},${{ needs.edge_386.outputs.tags_libreoffice }},${{ needs.edge_ppc64le.outputs.tags_libreoffice }},${{ needs.edge_arm64.outputs.tags_libreoffice }},${{ needs.edge_arm_v7.outputs.tags_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags_aws_lambda }},${{ needs.edge_arm64.outputs.tags_aws_lambda }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda Chromium
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.edge_arm64.outputs.tags_aws_lambda_chromium }}"
|
||||||
|
|
||||||
|
- name: Merge AWS Lambda LibreOffice
|
||||||
|
uses: ./.github/actions/merge
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.edge_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
|
|
||||||
|
- name: Clean
|
||||||
|
uses: ./.github/actions/clean
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
tags: "${{ needs.edge_amd64.outputs.tags }},${{ needs.edge_386.outputs.tags }},${{ needs.edge_ppc64le.outputs.tags }},${{ needs.edge_arm64.outputs.tags }},${{ needs.edge_arm_v7.outputs.tags }},${{ needs.edge_amd64.outputs.tags_chromium }},${{ needs.edge_386.outputs.tags_chromium }},${{ needs.edge_ppc64le.outputs.tags_chromium }},${{ needs.edge_arm64.outputs.tags_chromium }},${{ needs.edge_arm_v7.outputs.tags_chromium }},${{ needs.edge_amd64.outputs.tags_libreoffice }},${{ needs.edge_386.outputs.tags_libreoffice }},${{ needs.edge_ppc64le.outputs.tags_libreoffice }},${{ needs.edge_arm64.outputs.tags_libreoffice }},${{ needs.edge_arm_v7.outputs.tags_libreoffice }},${{ needs.edge_amd64.outputs.tags_aws_lambda }},${{ needs.edge_arm64.outputs.tags_aws_lambda }},${{ needs.edge_amd64.outputs.tags_aws_lambda_chromium }},${{ needs.edge_arm64.outputs.tags_aws_lambda_chromium }},${{ needs.edge_amd64.outputs.tags_aws_lambda_libreoffice }},${{ needs.edge_arm64.outputs.tags_aws_lambda_libreoffice }}"
|
||||||
24
.github/workflows/pull-request-cleanup.yml
vendored
Normal file
24
.github/workflows/pull-request-cleanup.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Pull Request Cleanup
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
name: Cleanup Docker images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
uses: ./.github/actions/clean
|
||||||
|
with:
|
||||||
|
docker_hub_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
docker_hub_password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
snapshot_version: pr-${{ github.event.pull_request.number }}
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
.ci/coverage.txt
|
.idea
|
||||||
.ci/gotenberg
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
/node_modules/
|
||||||
|
|||||||
58
.golangci.yml
Normal file
58
.golangci.yml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
version: "2"
|
||||||
|
run:
|
||||||
|
issues-exit-code: 1
|
||||||
|
tests: false
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
|
- errname
|
||||||
|
- exhaustive
|
||||||
|
- gosec
|
||||||
|
- govet
|
||||||
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- prealloc
|
||||||
|
- staticcheck
|
||||||
|
- testableexamples
|
||||||
|
- tparallel
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- sloglint
|
||||||
|
- usetesting
|
||||||
|
- gocritic
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/gotenberg/gotenberg/v8)
|
||||||
|
custom-order: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
project_name: Gotenberg
|
|
||||||
builds:
|
|
||||||
- main: ./main.go
|
|
||||||
binary: gotenberg
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
- darwin
|
|
||||||
- windows
|
|
||||||
goarch:
|
|
||||||
- 386
|
|
||||||
- amd64
|
|
||||||
- arm
|
|
||||||
- arm64
|
|
||||||
archive:
|
|
||||||
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
|
||||||
replacements:
|
|
||||||
darwin: Darwin
|
|
||||||
linux: Linux
|
|
||||||
windows: Windows
|
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
format: tar.gz
|
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
|
||||||
format: zip
|
|
||||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
24.15.0
|
||||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build
|
||||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-gherkin", "prettier-plugin-sh"],
|
||||||
|
"escapeBackslashes": true
|
||||||
|
}
|
||||||
23
.travis.yml
23
.travis.yml
@@ -1,23 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
|
|
||||||
language: generic
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- secure: d6pliM7iR7KJ3TQkr1C0tHi0HgU6Vdmc/ycw11XrXs794/dgj6g9UDrhgcvT2mTzOVIUNouHx7gyEWAlwJv7p8PPyguHJfIsyqMWfaXNPoytTx1ETUiygl1G/UsvXVdry6I5tZF/U+/PJ3hBXoAvo8EOFe0/MxxvP3ghCeeCpeEwEdoYP2jbADGffywVUNI7OEdmS29A28GusXXp2cWCIZYSq5Gn5iu1ZMPaJA6SkRTXpS+iVVLUvlbz5HW09ETFfurTvsdz5Ze4JICiuQt///CR1uwdxD8K7Ils/mkenaMtja9NHA9RdQBa51Azn8iCLsObCTYc52Zb6atbCi/X4ae9M8GA8qaaLigNz5G4v1BHneJylYfWKj5GwqUA3VmRu1mlEw7d2sN2uFRmgIl5kgsYYyOVOLBOKaJWhyBhNnR4U8KeqwUkOnlSdmNZnjfexXXBEcKHen1SozpXwMnzZh8LZuN6BNYd6TVpKmjulGI3vqNMM0c4JJZ9pSm4q5FjbNGQArwCsujDDwY4bCGZSJJTPMXjYOrx4yJ1RY+0TpyV9oDU3ogEqaWnhrkgrphfiYBlDY2bEvrxMTMukLCxJb0Nru6LGCpg4X1P9hu9r0gceqUSnBgdJI4rYTNddenxlEd4j/Thqv835stJWz6YoV+rfvERuSUpntl8xvrk3U8=
|
|
||||||
- secure: CmLh169xwt3OC7l19lDUwdZYfw+l/+XXKjTGxnuJiXHa9hAH+nc4mEUSmqHMHwjquyYE6LnSVGZ94LH8sCaGed/TMm0Lj3DZtZ0VsjYu3sKsNrVJn5WdYEah+GvNJ8i8uzvHnGk1L2fAagLpXFUB2K9UraA1CFiHs1fF5Fua7Ra0mE7lMz0MFvyvzcmc9u8nMz71K2YVsXqn+I/rv8onJCTtc/Ds0hVrBXwG5Fh0T3xHqjOhME9ZI2hRHnnQVdBojSY3USifQ//qAKowfBVc0pebEtgFpyXx/I17A6xCm0KVtnA2tNqu1+XFkjWaBzryaLE9ujhJcTRC/YK0JUZ+QxfYbD5C7jVy9Jlx1lOEjJ42B/ImNxPP+9p3wi2MWNbcbZJPxIe635fnT17lI7CLK2Qg1xjfTQ4WTEKW5IhnYMiyfRKiripFh4PCkDm8cJlTbpntsX1n0uheydbsgf/11vS7o1WGhVQ8+Jelc4DvIbBHCqienQEOLJDBwm7FiK5JbwZJ6ZAGeDM/IhGU1h9PNjVbHUyJhUxYoPYUE9QsZFT7ENCrRQpq49KOzXBRfffyJNJRh+Ep9X5YoW0dUp0MNu5B7z2+6w9X9n/C+loa11xF42bFzniCvvbSOQ37J+EngDinVxJkNPeyivR4fye2ENhIAjAEjouTJLOSXL9nAOU=
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- wget -qO- https://github.com/gulien/orbit/releases/download/v3.0.0/orbit_Linux_x86_64.tar.gz | tar xvz -C .
|
|
||||||
- sudo mv ./orbit /usr/local/bin && chmod +x /usr/local/bin/orbit
|
|
||||||
|
|
||||||
script:
|
|
||||||
- orbit run ci -p "Version,${TRAVIS_BRANCH}"
|
|
||||||
- orbit run build -p "Version,${TRAVIS_BRANCH}"
|
|
||||||
# Uploads coverage.
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
# Let's push to Docker hub only if not in a pull request and the branch name matches the semver format.
|
|
||||||
- if [[ "$TRAVIS_PULL_REQUEST" = false && "$TRAVIS_BRANCH" =~ ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$ ]]; then docker login -u $DOCKER_USER -p $DOCKER_PASS && docker push thecodingmachine/gotenberg:${TRAVIS_BRANCH}; fi
|
|
||||||
178
CONTRIBUTING.md
Normal file
178
CONTRIBUTING.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Contributing to Gotenberg
|
||||||
|
|
||||||
|
Gotenberg is a Docker-based API for converting documents to PDF. Two rules override everything else:
|
||||||
|
|
||||||
|
- **Backward compatibility.** Never rename or remove CLI flags, environment variables, API form fields, or HTTP endpoints without discussion.
|
||||||
|
- **Defensive programming.** Assume input is malformed, handle errors explicitly, never panic.
|
||||||
|
|
||||||
|
## Toolchain
|
||||||
|
|
||||||
|
- Module: `github.com/gotenberg/gotenberg/v8`
|
||||||
|
- Go: see version in `go.mod`
|
||||||
|
- Docker
|
||||||
|
- Node.js (see `.node-version`), for Prettier linting
|
||||||
|
- [golangci-lint](https://golangci-lint.run/) v2+
|
||||||
|
|
||||||
|
## Before you start
|
||||||
|
|
||||||
|
For non-trivial changes, open an issue or a draft PR first. Describe what needs to change, the proposed solution (files to modify, interface changes, form fields), and which integration test tags are affected.
|
||||||
|
|
||||||
|
One thing per PR. Keep features, bug fixes, and refactoring in separate PRs.
|
||||||
|
|
||||||
|
When adding a feature or route, write the Gherkin scenario before the Go code, and plan to update the Bruno collection (`.bruno/`) if a route changes.
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/gotenberg/ -> Entry point only (wiring/startup). No business logic.
|
||||||
|
pkg/gotenberg/ -> Core module system, interfaces, utilities, mocks.
|
||||||
|
pkg/modules/ -> Feature modules (api, chromium, libreoffice, pdfengines, etc.).
|
||||||
|
pkg/standard/ -> Wires all standard modules together via imports.
|
||||||
|
test/integration/ -> Gherkin feature files + Go test infrastructure.
|
||||||
|
build/ -> Dockerfile, fonts, Chromium config.
|
||||||
|
.bruno/ -> Bruno API collection (mirrors every route).
|
||||||
|
```
|
||||||
|
|
||||||
|
Key interfaces live in `pkg/gotenberg/`: `Module`, `Provisioner`, `Validator`, `Debuggable`. Every module implements `Descriptor()` and self-registers via `init()`.
|
||||||
|
|
||||||
|
## Setup and Makefile
|
||||||
|
|
||||||
|
All build and verification tasks go through the Makefile. Do not run `go` commands directly unless debugging a specific package.
|
||||||
|
|
||||||
|
| Command | Purpose | When to use |
|
||||||
|
| ----------------------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||||
|
| `make build` | Build the Gotenberg Docker image | Before integration tests or manual testing |
|
||||||
|
| `make run` | Run a Gotenberg container via `docker compose` | Manual testing. Flags configured via Makefile variables and compose.yaml |
|
||||||
|
| `make telemetry` | Start an OpenTelemetry collector and OpenObserve | When testing telemetry locally |
|
||||||
|
| `make down` | Stop all compose containers | After manual testing |
|
||||||
|
| `make godoc` | Serve GoDoc at `localhost:6060` | To verify documentation |
|
||||||
|
| `make fmt` | Format Go code | Before committing |
|
||||||
|
| `make lint` | Lint Go code (zero errors permitted) | Before committing |
|
||||||
|
| `make prettify` | Format non-Go files (Markdown, YAML, JSON) | Before committing |
|
||||||
|
| `make lint-prettier` | Lint non-Go files | Before committing |
|
||||||
|
| `make test-unit` | Run unit tests | Before committing |
|
||||||
|
| `make test-integration` | Run all integration tests (40 min timeout) | Before committing |
|
||||||
|
|
||||||
|
Run only the integration test tag(s) relevant to your change rather than the full suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test-integration TAGS=health
|
||||||
|
make test-integration TAGS=chromium-convert-html
|
||||||
|
make test-integration TAGS="merge,split"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code conventions
|
||||||
|
|
||||||
|
### Module system
|
||||||
|
|
||||||
|
Gotenberg uses a self-registering module architecture inspired by CaddyServer. Each module lives in `pkg/modules/<name>/`, implements at minimum `gotenberg.Module` (`Descriptor()`), and self-registers via `init()`. Wiring happens through `pkg/standard/`.
|
||||||
|
|
||||||
|
Determine if a feature belongs in an existing module before creating a new one. Only create a new module for a genuinely separate concern.
|
||||||
|
|
||||||
|
The `cmd/gotenberg/` package is strictly for wiring and startup. No business logic.
|
||||||
|
|
||||||
|
### Backward compatibility
|
||||||
|
|
||||||
|
CLI flags, environment variables, API form fields, HTTP endpoints, and default values that alter existing behavior must not change without discussion. Deprecate old names with `fs.MarkDeprecated()` and register both the old and new names side by side.
|
||||||
|
|
||||||
|
If a change violates backward compatibility, flag it as a breaking change in the PR description.
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
- Wrap every error with context: `fmt.Errorf("description: %w", err)`.
|
||||||
|
- Never swallow errors silently.
|
||||||
|
- Match errors with `errors.Is`, never `strings.Contains`.
|
||||||
|
- No panics in production code paths.
|
||||||
|
- Validate input defensively.
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
Use `gotenberg.Logger(mod)` to get the module's slog logger during `Provision()`. All log calls must be context-aware: `logger.DebugContext(ctx, msg)`, `logger.InfoContext(ctx, msg)`, `logger.ErrorContext(ctx, msg)`. This propagates trace/span IDs into structured logs when OpenTelemetry is active.
|
||||||
|
|
||||||
|
### Telemetry
|
||||||
|
|
||||||
|
External tool calls (Chromium, LibreOffice, PDF engines, webhooks, downloads) must create OTEL spans with `trace.SpanKindClient` and `semconv.ServerAddress("toolname")`. Use `gotenberg.Tracer()` and `gotenberg.Meter()` for traces and metrics.
|
||||||
|
|
||||||
|
### Import ordering
|
||||||
|
|
||||||
|
Enforced by `gci`: standard library, then third-party, then `github.com/gotenberg/gotenberg/v8`. Three groups separated by blank lines.
|
||||||
|
|
||||||
|
## Documentation conventions
|
||||||
|
|
||||||
|
### Tone
|
||||||
|
|
||||||
|
- Short, declarative sentences. Say what it does, then stop.
|
||||||
|
- Lead with the action. "Validates font embedding", not "This function validates font embedding".
|
||||||
|
- Active voice. "Gotenberg checks the profile", not "The profile is checked by Gotenberg".
|
||||||
|
- No em dashes. Use a period, colon, or comma.
|
||||||
|
- No "we" hedging. "Don't...", not "We do not recommend...".
|
||||||
|
|
||||||
|
### Godoc
|
||||||
|
|
||||||
|
Every exported type and function has a Godoc comment starting with its identifier name:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Violation records a single rule violation with context.
|
||||||
|
type Violation struct { ... }
|
||||||
|
|
||||||
|
// ValidatePDFA audits the document against a PDF/A profile.
|
||||||
|
func ValidatePDFA(ctx context.Context, ...) ([]error, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
Each package should have a `doc.go` with a `// Package foo ...` comment.
|
||||||
|
|
||||||
|
Reference identifiers with `[Name]` brackets for pkg.go.dev linking:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ValidatePDFA returns violations as []error where each element
|
||||||
|
// is a [Violation] value. See [Rule] for the structured fields.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code comments
|
||||||
|
|
||||||
|
- Explain _why_, not _what_.
|
||||||
|
- No numbered step comments (`// 1. Do X`, `// 2. Do Y`).
|
||||||
|
- No section dividers with numbers (`// --- 8. Foo ---`). Plain dividers are fine for major boundaries.
|
||||||
|
- No noise comments that restate the code (`// Check if err is nil`, `// Return results`).
|
||||||
|
- Reference spec clauses where relevant (`// Per ISO 32000-2, Table 116...`).
|
||||||
|
- Mark debt with `// TODO: [context]`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
|
||||||
|
Table-driven tests in `*_test.go` files. Use the comprehensive mock implementations in `pkg/gotenberg/mocks.go` rather than rolling new ones.
|
||||||
|
|
||||||
|
### Integration tests
|
||||||
|
|
||||||
|
Gherkin (BDD) via Godog with `testcontainers-go` for Docker orchestration. Feature files live in `test/integration/features/`; step definitions live in `test/integration/scenario/`. Read `scenario.go` and `containers.go` before writing new tests.
|
||||||
|
|
||||||
|
`make build` is required before running integration tests. The full suite has a 40-minute timeout, so run only the tag(s) relevant to your change.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
[Conventional Commits](https://www.conventionalcommits.org/): `<type>(<scope>): <description>`.
|
||||||
|
|
||||||
|
Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`, `build`. The scope matches the module or area of the change (e.g., `chromium`, `pdfengines`, `api`).
|
||||||
|
|
||||||
|
Stage specific files. Never `git add -A` or `git add .`.
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
Before opening the PR, confirm:
|
||||||
|
|
||||||
|
- [ ] No backward-compatibility regression. See [Backward compatibility](#backward-compatibility).
|
||||||
|
- [ ] Code conventions met (error wrapping, logging, telemetry, import ordering, no panics, no business logic in `cmd/`). See [Code conventions](#code-conventions).
|
||||||
|
- [ ] Documentation conventions met (Godoc on every exported identifier, `doc.go` for new packages, tone). See [Documentation conventions](#documentation-conventions).
|
||||||
|
- [ ] `make fmt && make lint && make prettify && make lint-prettier` pass with zero warnings.
|
||||||
|
- [ ] `make test-unit` passes.
|
||||||
|
- [ ] Relevant `make test-integration TAGS=...` passes.
|
||||||
|
- [ ] Bruno collection updated if routes were added or modified.
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
- [`test/integration/README.md`](test/integration/README.md) — Gherkin step reference, available tags, writing new tests.
|
||||||
|
- [`.bruno/README.md`](.bruno/README.md) — `.bru` file format, conventions, route update checklist.
|
||||||
|
- [`pkg/modules/pdfengines/README.md`](pkg/modules/pdfengines/README.md) — adding new engine features (Makefile variable and flag).
|
||||||
46
Dockerfile
46
Dockerfile
@@ -1,46 +0,0 @@
|
|||||||
FROM debian:stretch-slim
|
|
||||||
|
|
||||||
LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Libraries
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | Installs all required libraries.
|
|
||||||
# |
|
|
||||||
|
|
||||||
RUN echo "deb http://httpredir.debian.org/debian/ stretch main contrib non-free" > /etc/apt/sources.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y xvfb wkhtmltopdf pdftk unoconv ttf-mscorefonts-installer &&\
|
|
||||||
ln -s /usr/bin/xvfb-run /usr/local/bin/xvfb-run &&\
|
|
||||||
ln -s /usr/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf &&\
|
|
||||||
ln -s /usr/bin/pdftk /usr/local/bin/pdftk &&\
|
|
||||||
ln -s /usr/bin/unoconv /usr/local/bin/unoconv
|
|
||||||
|
|
||||||
RUN apt-get install -y curl gnupg &&\
|
|
||||||
curl -sL https://deb.nodesource.com/setup_8.x | bash - &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y nodejs
|
|
||||||
|
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &&\
|
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y yarn bzip2 &&\
|
|
||||||
yarn global add markdown-pdf --prefix /usr/local
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Gotenberg
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | All Gotenberg related stuff.
|
|
||||||
# |
|
|
||||||
|
|
||||||
COPY .ci/gotenberg /usr/bin/gotenberg
|
|
||||||
RUN ln -s /usr/bin/gotenberg /usr/local/bin/gotenberg
|
|
||||||
|
|
||||||
COPY .ci/gotenberg.yml /gotenberg/gotenberg.yml
|
|
||||||
|
|
||||||
WORKDIR /gotenberg
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["gotenberg"]
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
FROM golang:1.10-stretch
|
|
||||||
|
|
||||||
LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Libraries
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | Installs all required libraries.
|
|
||||||
# |
|
|
||||||
|
|
||||||
RUN echo "deb http://httpredir.debian.org/debian/ stretch main contrib non-free" > /etc/apt/sources.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y xvfb wkhtmltopdf pdftk unoconv ttf-mscorefonts-installer &&\
|
|
||||||
ln -s /usr/bin/xvfb-run /usr/local/bin/xvfb-run &&\
|
|
||||||
ln -s /usr/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf &&\
|
|
||||||
ln -s /usr/bin/pdftk /usr/local/bin/pdftk &&\
|
|
||||||
ln -s /usr/bin/unoconv /usr/local/bin/unoconv
|
|
||||||
|
|
||||||
RUN apt-get install -y curl gnupg &&\
|
|
||||||
curl -sL https://deb.nodesource.com/setup_8.x | bash - &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y nodejs
|
|
||||||
|
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &&\
|
|
||||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list &&\
|
|
||||||
apt-get update &&\
|
|
||||||
apt-get install -y yarn bzip2 &&\
|
|
||||||
yarn global add markdown-pdf --prefix /usr/local
|
|
||||||
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# | Gotenberg
|
|
||||||
# |--------------------------------------------------------------------------
|
|
||||||
# |
|
|
||||||
# | All Gotenberg related stuff.
|
|
||||||
# |
|
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/thecodingmachine/gotenberg
|
|
||||||
|
|
||||||
# Installs lint dependencies.
|
|
||||||
RUN go get -u gopkg.in/alecthomas/gometalinter.v2 &&\
|
|
||||||
gometalinter.v2 --install
|
|
||||||
|
|
||||||
# Copies our Go source.
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Installs project dependencies.
|
|
||||||
RUN go get -d -v ./...
|
|
||||||
|
|
||||||
ENTRYPOINT [".ci/docker-entrypoint.sh"]
|
|
||||||
66
Gopkg.lock
generated
66
Gopkg.lock
generated
@@ -1,66 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/dustin/go-humanize"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "02af3965c54e8cacf948b97fef38925c4120652c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/context"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
|
||||||
version = "v1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/mux"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
|
|
||||||
version = "v1.6.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/justinas/alice"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "03f45bd4b7dad4734bc4620e46a35789349abb20"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/satori/go.uuid"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
|
|
||||||
version = "v1.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/sirupsen/logrus"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
|
||||||
version = "v1.0.5"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = ["ssh/terminal"]
|
|
||||||
revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = [
|
|
||||||
"unix",
|
|
||||||
"windows"
|
|
||||||
]
|
|
||||||
revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "86f5ed62f8a0ee96bd888d2efdfd6d4fb100a4eb"
|
|
||||||
version = "v2.2.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "97e0dbb20631792bc25ad19e6a85a90a9d24bb03f773cd36dee11eebae152091"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
||||||
54
Gopkg.toml
54
Gopkg.toml
@@ -1,54 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/satori/go.uuid"
|
|
||||||
version = "1.2.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/gorilla/mux"
|
|
||||||
version = "1.6.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/justinas/alice"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
version = "2.2.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/sirupsen/logrus"
|
|
||||||
version = "1.0.5"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/dustin/go-humanize"
|
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018 TheCodingMachine
|
Copyright (c) Julien Neuhart
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
203
Makefile
Normal file
203
Makefile
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
include .env
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Show the help
|
||||||
|
@grep -hE '^[A-Za-z0-9_ \-]*?:.*##.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: ## Build the Gotenberg's Docker image (use TARGET=gotenberg-chromium or TARGET=gotenberg-libreoffice for variants)
|
||||||
|
docker build \
|
||||||
|
--target $(TARGET) \
|
||||||
|
-t $(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY):$(GOTENBERG_VERSION) \
|
||||||
|
-f $(DOCKERFILE) $(DOCKER_BUILD_CONTEXT)
|
||||||
|
|
||||||
|
TZ=UTC
|
||||||
|
GOTENBERG_HIDE_BANNER=false
|
||||||
|
GOTENBERG_GRACEFUL_SHUTDOWN_DURATION=30s
|
||||||
|
GOTENBERG_BUILD_DEBUG_DATA=true
|
||||||
|
API_PORT=3000
|
||||||
|
API_PORT_FROM_ENV=
|
||||||
|
API_BIND_IP=
|
||||||
|
API_START_TIMEOUT=30s
|
||||||
|
API_TIMEOUT=30s
|
||||||
|
API_BODY_LIMIT=
|
||||||
|
API_ROOT_PATH=/
|
||||||
|
API_CORRELATION_ID_HEADER=Gotenberg-Trace
|
||||||
|
API_ENABLE_BASIC_AUTH=false
|
||||||
|
GOTENBERG_API_BASIC_AUTH_USERNAME=
|
||||||
|
GOTENBERG_API_BASIC_AUTH_PASSWORD=
|
||||||
|
API_DOWNLOAD_FROM_ALLOW_LIST=
|
||||||
|
API_DOWNLOAD_FROM_DENY_LIST=^https?://(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|169\.254\.|0\.0\.0\.0|127\.|localhost|\[::1\]|\[fd)
|
||||||
|
API_DOWNLOAD_FROM_MAX_RETRY=4
|
||||||
|
API_DISABLE_DOWNLOAD_FROM=false
|
||||||
|
API_DISABLE_HEALTH_CHECK_ROUTE_TELEMETRY=true
|
||||||
|
API_DISABLE_ROOT_ROUTE_TELEMETRY=true
|
||||||
|
API_DISABLE_DEBUG_ROUTE_TELEMETRY=true
|
||||||
|
API_DISABLE_VERSION_ROUTE_TELEMETRY=true
|
||||||
|
API_ENABLE_DEBUG_ROUTE=false
|
||||||
|
CHROMIUM_RESTART_AFTER=100
|
||||||
|
CHROMIUM_MAX_QUEUE_SIZE=0
|
||||||
|
CHROMIUM_IDLE_SHUTDOWN_TIMEOUT=0
|
||||||
|
CHROMIUM_MAX_CONCURRENCY=6
|
||||||
|
CHROMIUM_AUTO_START=false
|
||||||
|
CHROMIUM_START_TIMEOUT=20s
|
||||||
|
CHROMIUM_ALLOW_INSECURE_LOCALHOST=false
|
||||||
|
CHROMIUM_IGNORE_CERTIFICATE_ERRORS=false
|
||||||
|
CHROMIUM_DISABLE_WEB_SECURITY=false
|
||||||
|
CHROMIUM_ALLOW_FILE_ACCESS_FROM_FILES=false
|
||||||
|
CHROMIUM_HOST_RESOLVER_RULES=
|
||||||
|
CHROMIUM_PROXY_SERVER=
|
||||||
|
CHROMIUM_ALLOW_LIST=
|
||||||
|
CHROMIUM_DENY_LIST=^file:(?!//\/tmp/).*
|
||||||
|
CHROMIUM_CLEAR_CACHE=false
|
||||||
|
CHROMIUM_CLEAR_COOKIES=false
|
||||||
|
CHROMIUM_DISABLE_JAVASCRIPT=false
|
||||||
|
CHROMIUM_DISABLE_ROUTES=false
|
||||||
|
LIBREOFFICE_RESTART_AFTER=10
|
||||||
|
LIBREOFFICE_MAX_QUEUE_SIZE=0
|
||||||
|
LIBREOFFICE_IDLE_SHUTDOWN_TIMEOUT=0
|
||||||
|
LIBREOFFICE_AUTO_START=false
|
||||||
|
LIBREOFFICE_START_TIMEOUT=20s
|
||||||
|
LIBREOFFICE_ALLOW_LIST=
|
||||||
|
LIBREOFFICE_DENY_LIST=
|
||||||
|
LIBREOFFICE_DISABLE_ROUTES=false
|
||||||
|
LOG_LEVEL=info
|
||||||
|
LOG_FIELDS_PREFIX=
|
||||||
|
LOG_STD_FORMAT=auto
|
||||||
|
LOG_STD_ENABLE_GCP_FIELDS=false
|
||||||
|
PDFENGINES_DISABLE_ROUTES=false
|
||||||
|
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
|
||||||
|
PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk
|
||||||
|
PDFENGINES_FLATTEN_ENGINES=qpdf
|
||||||
|
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
|
||||||
|
PDFENGINES_READ_METADATA_ENGINES=exiftool
|
||||||
|
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
|
||||||
|
PDFENGINES_READ_BOOKMARKS_ENGINES=pdfcpu
|
||||||
|
PDFENGINES_WRITE_BOOKMARKS_ENGINES=pdfcpu
|
||||||
|
PDFENGINES_WATERMARK_ENGINES=pdfcpu,pdftk
|
||||||
|
PDFENGINES_STAMP_ENGINES=pdfcpu,pdftk
|
||||||
|
PDFENGINES_ENCRYPT_ENGINES=qpdf,pdfcpu,pdftk
|
||||||
|
PDFENGINES_ROTATE_ENGINES=pdfcpu,pdftk
|
||||||
|
PDFENGINES_EMBED_ENGINES=qpdf,pdfcpu
|
||||||
|
PDFENGINES_EMBED_METADATA_ENGINES=qpdf
|
||||||
|
PROMETHEUS_NAMESPACE=gotenberg
|
||||||
|
PROMETHEUS_COLLECT_INTERVAL=1s
|
||||||
|
PROMETHEUS_DISABLE_ROUTE_TELEMETRY=true
|
||||||
|
PROMETHEUS_DISABLE_COLLECT=false
|
||||||
|
PROMETHEUS_METRICS_PATH=/prometheus/metrics
|
||||||
|
OTEL_SERVICE_NAME=gotenberg
|
||||||
|
OTEL_TRACES_EXPORTER=none
|
||||||
|
OTEL_METRICS_EXPORTER=none
|
||||||
|
OTEL_LOGS_EXPORTER=none
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
|
||||||
|
OTEL_EXPORTER_OTLP_INSECURE=true
|
||||||
|
WEBHOOK_ENABLE_SYNC_MODE=false
|
||||||
|
WEBHOOK_ALLOW_LIST=
|
||||||
|
WEBHOOK_DENY_LIST=^https?://(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|169\.254\.|0\.0\.0\.0|127\.|localhost|\[::1\]|\[fd)
|
||||||
|
WEBHOOK_MAX_RETRY=4
|
||||||
|
WEBHOOK_RETRY_MIN_WAIT=1s
|
||||||
|
WEBHOOK_RETRY_MAX_WAIT=30s
|
||||||
|
WEBHOOK_CLIENT_TIMEOUT=30s
|
||||||
|
WEBHOOK_DISABLE=false
|
||||||
|
|
||||||
|
# Export all variables so they are available to Compose
|
||||||
|
export
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run: ## Start a Gotenberg container via Compose
|
||||||
|
docker compose up gotenberg
|
||||||
|
|
||||||
|
.PHONY: telemetry
|
||||||
|
telemetry: ## Start an OpenTelemetry collector and OpenObserve containers via Compose
|
||||||
|
docker compose up otel-collector openobserve
|
||||||
|
|
||||||
|
.PHONY: down
|
||||||
|
down: ## Stop all containers
|
||||||
|
docker compose down -v
|
||||||
|
|
||||||
|
.PHONY: test-unit
|
||||||
|
test-unit: ## Run unit tests
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
PLATFORM=
|
||||||
|
NO_CONCURRENCY=false
|
||||||
|
# Available tags:
|
||||||
|
# chromium
|
||||||
|
# chromium-concurrent
|
||||||
|
# chromium-convert-html
|
||||||
|
# chromium-convert-markdown
|
||||||
|
# chromium-convert-url
|
||||||
|
# chromium-screenshot-html
|
||||||
|
# chromium-screenshot-markdown
|
||||||
|
# chromium-screenshot-url
|
||||||
|
# debug
|
||||||
|
# health
|
||||||
|
# libreoffice
|
||||||
|
# libreoffice-convert
|
||||||
|
# output-filename
|
||||||
|
# pdfengines
|
||||||
|
# pdfengines-convert
|
||||||
|
# pdfengines-embed
|
||||||
|
# embed
|
||||||
|
# pdfengines-encrypt
|
||||||
|
# encrypt
|
||||||
|
# pdfengines-flatten
|
||||||
|
# flatten
|
||||||
|
# pdfengines-merge
|
||||||
|
# merge
|
||||||
|
# pdfengines-metadata
|
||||||
|
# metadata
|
||||||
|
# pdfengines-split
|
||||||
|
# split
|
||||||
|
# pdfengines-watermark
|
||||||
|
# watermark
|
||||||
|
# pdfengines-stamp
|
||||||
|
# stamp
|
||||||
|
# pdfengines-rotate
|
||||||
|
# rotate
|
||||||
|
# pdfengines-bookmarks
|
||||||
|
# bookmarks
|
||||||
|
# prometheus-metrics
|
||||||
|
# root
|
||||||
|
# version
|
||||||
|
# webhook
|
||||||
|
# download-from
|
||||||
|
TAGS=
|
||||||
|
|
||||||
|
.PHONY: test-integration
|
||||||
|
test-integration: ## Run integration tests (automatically retries failed scenarios up to 3 times)
|
||||||
|
go test -timeout 40m -tags=integration -v github.com/gotenberg/gotenberg/v8/test/integration -args \
|
||||||
|
--gotenberg-docker-repository=$(DOCKER_REPOSITORY) \
|
||||||
|
--gotenberg-version=$(GOTENBERG_VERSION) \
|
||||||
|
--gotenberg-container-platform=$(PLATFORM) \
|
||||||
|
--no-concurrency=$(NO_CONCURRENCY) \
|
||||||
|
--tags="$(TAGS)"
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: ## Lint Golang codebase
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
.PHONY: lint-prettier
|
||||||
|
lint-prettier: ## Lint non-Golang codebase
|
||||||
|
npx prettier --check .
|
||||||
|
|
||||||
|
.PHONY: lint-todo
|
||||||
|
lint-todo: ## Find TODOs in Golang codebase
|
||||||
|
golangci-lint run --no-config --disable-all --enable godox
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: ## Format Golang codebase and "optimize" the dependencies
|
||||||
|
go fix ./...
|
||||||
|
golangci-lint fmt
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
.PHONY: prettify
|
||||||
|
prettify: ## Format non-Golang codebase
|
||||||
|
npx prettier --write .
|
||||||
|
|
||||||
|
# go install golang.org/x/tools/cmd/godoc@latest
|
||||||
|
.PHONY: godoc
|
||||||
|
godoc: ## Run a webserver with Gotenberg godoc
|
||||||
|
$(info http://localhost:6060/pkg/github.com/gotenberg/gotenberg/v8)
|
||||||
|
godoc -http=:6060
|
||||||
237
README.md
237
README.md
@@ -1,202 +1,71 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://user-images.githubusercontent.com/8983173/38133342-11df3bd8-340f-11e8-9fe4-50baecdceeca.png" alt="Gotenberg's logo" width="250" height="250" />
|
<img src="https://user-images.githubusercontent.com/8983173/130322857-185831e2-f041-46eb-a17f-0a69d066c4e5.png" alt="Gotenberg Logo" width="150" height="150" />
|
||||||
</p>
|
<h3 align="center">Gotenberg</h3>
|
||||||
<h3 align="center">Gotenberg</h3>
|
<p align="center">A Docker-based API for converting documents to PDF</p>
|
||||||
<p align="center">A stateless API for converting Markdown files, HTML files and Office documents to PDF</p>
|
<p align="center">
|
||||||
<p align="center">
|
<a href="https://hub.docker.com/r/gotenberg/gotenberg"><img alt="Total downloads (gotenberg/gotenberg)" src="https://img.shields.io/docker/pulls/gotenberg/gotenberg"></a>
|
||||||
<a href="https://microbadger.com/images/thecodingmachine/gotenberg:2.0.0">
|
<a href="https://github.com/gotenberg/gotenberg/actions/workflows/continuous-integration.yml"><img alt="Continuous Integration" src="https://github.com/gotenberg/gotenberg/actions/workflows/continuous-integration.yml/badge.svg"></a>
|
||||||
<img src="https://images.microbadger.com/badges/image/thecodingmachine/gotenberg:2.0.0.svg" alt="MicroBadger layers">
|
<a href="https://pkg.go.dev/github.com/gotenberg/gotenberg/v8"><img alt="Go Reference" src="https://pkg.go.dev/badge/github.com/gotenberg/gotenberg.svg"></a>
|
||||||
</a>
|
</p>
|
||||||
<a href="https://travis-ci.org/thecodingmachine/gotenberg">
|
<p align="center">
|
||||||
<img src="https://travis-ci.org/thecodingmachine/gotenberg.svg?branch=master" alt="Travis CI">
|
<a href="https://trendshift.io/repositories/2996"><img src="https://trendshift.io/api/badge/repositories/2996" alt="gotenberg%2Fgotenberg | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
</a>
|
</p>
|
||||||
<a href="https://godoc.org/github.com/thecodingmachine/gotenberg">
|
<p align="center">
|
||||||
<img src="https://godoc.org/github.com/thecodingmachine/gotenberg?status.svg" alt="GoDoc">
|
<a href="https://gotenberg.dev/docs/getting-started/introduction"><strong>Documentation</strong></a> ·
|
||||||
</a>
|
<a href="https://gotenberg.dev/docs/getting-started/installation#live-demo"><strong>Live Demo</strong></a> ·
|
||||||
<a href="https://goreportcard.com/report/thecodingmachine/gotenberg">
|
<a href="https://github.com/gotenberg/gotenberg/discussions"><strong>Community</strong></a>
|
||||||
<img src="https://goreportcard.com/badge/github.com/thecodingmachine/gotenberg" alt="Go Report Card">
|
</p>
|
||||||
</a>
|
|
||||||
<a href="https://codecov.io/gh/thecodingmachine/gotenberg/branch/master">
|
|
||||||
<img src="https://codecov.io/gh/thecodingmachine/gotenberg/branch/master/graph/badge.svg" alt="Codecov">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
At TheCodingMachine, we build a lot of web applications (intranets, extranets and so on) which require to generate PDF from
|
**Gotenberg** is a Docker-based API for converting documents to PDF. Trusted in production by thousands of companies. Also adopted by notable open-source projects.
|
||||||
various sources. Each time, we ended up using some well known libraries like **wkhtmltopdf** or **unoconv** and kind of lost time by
|
|
||||||
reimplementing a solution from a project to another project. Meh.
|
|
||||||
|
|
||||||
# Menu
|
Send your files via `multipart/form-data`, get a PDF back. No need to manage Chromium, LibreOffice, or fonts yourself.
|
||||||
|
|
||||||
* [Usage](#usage)
|
## Quick Start
|
||||||
* [Security](#security)
|
|
||||||
* [Scalability](#scalability)
|
|
||||||
* [Custom implementation](#custom-implementation)
|
|
||||||
* [Clients](#clients)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Let's say you're starting the API using this simple command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ docker run --rm -p 3000:3000 thecodingmachine/gotenberg:2.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
The API is now available on your host under `http://127.0.0.1:3000`.
|
|
||||||
|
|
||||||
It accepts `POST` requests with a `multipart/form-data` Content-Type. Your form data should provide one or more files to convert.
|
|
||||||
The default image accepts the following:
|
|
||||||
|
|
||||||
* Markdown files
|
|
||||||
* HTML files
|
|
||||||
* Office documents (.docx, .doc, .odt, .pptx, .ppt, .odp and so on)
|
|
||||||
* PDF files
|
|
||||||
|
|
||||||
**Heads up:** the API relies on the file extension to determine which library to use for conversion.
|
|
||||||
|
|
||||||
There are two use cases:
|
|
||||||
|
|
||||||
* If you send one file, it will convert it and return the resulting PDF
|
|
||||||
* If many files, it will convert them to PDF, merge the resulting PDFs into a single PDF and return it
|
|
||||||
|
|
||||||
### Examples:
|
|
||||||
|
|
||||||
* One file
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ curl --request POST \
|
|
||||||
--url http://127.0.0.1:3000 \
|
|
||||||
--header 'Content-Type: multipart/form-data' \
|
|
||||||
--form files=@file.docx \
|
|
||||||
> result.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
* Many files
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ curl --request POST \
|
|
||||||
--url http://127.0.0.1:3000 \
|
|
||||||
--header 'Content-Type: multipart/form-data' \
|
|
||||||
--form files=@file.md \
|
|
||||||
--form files=@file.html \
|
|
||||||
--form files=@file.pdf \
|
|
||||||
--form files=@file.docx \
|
|
||||||
> result.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
The API does not provide any authentication mechanisms. Make sure to not put it on a public facing port and your client(s) should always
|
|
||||||
controls what is sent to the API.
|
|
||||||
|
|
||||||
## Scalability
|
|
||||||
|
|
||||||
Some libraries like **unoconv** cannot perform concurrent conversions. That's why the API does only one conversion at a time.
|
|
||||||
If your API is under heavy load, a request will take time to be processed.
|
|
||||||
|
|
||||||
Fortunately, you may pass through this limitation by scaling the API.
|
|
||||||
|
|
||||||
In the following example, I'll demonstrate how to do some vertical scaling (= on the same machine) with Docker Compose, but of course horizontal scaling works too!
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
# your others services
|
|
||||||
|
|
||||||
gotenberg:
|
|
||||||
image: thecodingmachine/gotenberg:2.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
You may now launch your services using:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up --scale gotenberg=your_number_of_instances
|
docker run --rm -p 3000:3000 gotenberg/gotenberg:8
|
||||||
```
|
```
|
||||||
|
|
||||||
When requesting the Gotenberg service with your client(s), Docker will automatically redirect a request to a Gotenberg container
|
Convert a URL to PDF:
|
||||||
according to the round-robin strategy.
|
|
||||||
|
|
||||||
## Custom implementation
|
```bash
|
||||||
|
curl \
|
||||||
The API relies on a simple YAML configuration file called `gotenberg.yml`. It allows you to tweak some values and even provides you
|
--request POST http://localhost:3000/forms/chromium/convert/url \
|
||||||
a way to change the commands called for each kind of conversion.
|
--form url=https://sparksuite.github.io/simple-html-invoice-template/ \
|
||||||
|
-o invoice.pdf
|
||||||
Below the default configuration file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# The port the application will listen to.
|
|
||||||
port: 3000
|
|
||||||
|
|
||||||
logs:
|
|
||||||
# Accepted values, in order of severity: DEBUG, INFO, WARN, ERROR, FATAL, PANIC.
|
|
||||||
# Messages at and above the selected level will be logged.
|
|
||||||
level: "INFO"
|
|
||||||
|
|
||||||
# Accepted values: text, json.
|
|
||||||
# When a TTY is not attached, the output will be in the defined format.
|
|
||||||
formatter: "text"
|
|
||||||
|
|
||||||
# You don't like a library which is used for a conversion? You want to handle a new file type?
|
|
||||||
# You may provide here your own implementation!
|
|
||||||
commands:
|
|
||||||
|
|
||||||
# Some libraries like unoconv cannot perform concurrent conversions. That's why the API does only one conversion at a time.
|
|
||||||
# If your current implementation uses libraries which are able to perform concurrent conversions, you may
|
|
||||||
# change this value to false.
|
|
||||||
lock: true
|
|
||||||
|
|
||||||
# Unlike others commands' templates, you have access to FilesPaths instead of FilePath: it gathers all PDF files which should be merged.
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
|
|
||||||
# You may add more commands (or less, or even none).
|
|
||||||
conversions:
|
|
||||||
|
|
||||||
# The command template: you have access to FilePath and ResultFilePath variables.
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
# The binary which will call the command.
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
# Duration in seconds after which the command will be killed if it has not finished.
|
|
||||||
timeout: 30
|
|
||||||
# Files with the following extensions will be converted by the current command.
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We provide binaries for a wide range of OS and architecture in the [releases page](../../releases),
|
## Features
|
||||||
so feel free to create your own Docker image for your implementation of the Gotenberg API :metal:
|
|
||||||
|
|
||||||
## Clients
|
- **HTML, URL, Markdown to PDF** via Headless Chromium
|
||||||
|
- **Office documents to PDF** via LibreOffice (100+ formats)
|
||||||
|
- **Merge, split, rotate, flatten** PDFs
|
||||||
|
- **Watermark, stamp, encrypt** PDFs
|
||||||
|
- **PDF/A and PDF/UA** compliance
|
||||||
|
- **Screenshots** of URLs and HTML
|
||||||
|
- **Read/write metadata and bookmarks**
|
||||||
|
|
||||||
* https://github.com/thecodingmachine/gotenberg-php-client (PHP client)
|
See the [full documentation](https://gotenberg.dev/docs/getting-started/introduction).
|
||||||
* Add your own client by submitting a [pull request](../../pulls)!
|
|
||||||
|
|
||||||
---
|
## Contributing
|
||||||
|
|
||||||
Would you like to update this documentation ? Feel free to open an [issue](../../issues).
|
Questions and feedback: [GitHub Discussions](https://github.com/gotenberg/gotenberg/discussions).
|
||||||
|
Bug reports: [GitHub Issues](https://github.com/gotenberg/gotenberg/issues).
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
If Gotenberg powers your workflow or your business, consider [**becoming a sponsor**](https://github.com/sponsors/gulien).
|
||||||
|
|
||||||
|
**Historic & GitHub Sponsors**
|
||||||
|
|
||||||
|
- [TheCodingMachine](https://thecodingmachine.com/)
|
||||||
|
- [pdfme](https://pdfme.com/)
|
||||||
|
- [PDFBolt](https://pdfbolt.com)
|
||||||
|
|
||||||
|
**Powered By**
|
||||||
|
|
||||||
|
- [Docker](https://docs.docker.com/docker-hub/repos/manage/trusted-content/dsos-program/)
|
||||||
|
- [JetBrains](https://www.jetbrains.com/community/opensource/)
|
||||||
|
|||||||
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Only the latest version receives security updates and patches. Keep your environment up to date.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Do not publish vulnerabilities publicly. Report them through [GitHub's private vulnerability reporting](https://github.com/gotenberg/gotenberg/security/advisories/new).
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
- A detailed description of the vulnerability.
|
||||||
|
- Steps to reproduce the issue.
|
||||||
|
- Potential impact on users or the system.
|
||||||
|
|
||||||
|
This process is handled on a best-effort basis. Response speed may vary depending on severity and available resources.
|
||||||
|
|
||||||
|
## Disclosure Policy
|
||||||
|
|
||||||
|
Once a report is received and confirmed:
|
||||||
|
|
||||||
|
- A fix and release timeline will be prepared.
|
||||||
|
- The reporter will be notified when the fix is released.
|
||||||
|
- The reporter will be credited for the discovery (unless anonymity is requested).
|
||||||
|
|
||||||
|
## Comments on this Policy
|
||||||
|
|
||||||
|
Submit a pull request with suggestions for improving this process.
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: {
|
|
||||||
value: [
|
|
||||||
...
|
|
||||||
}
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 0
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: false
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 0
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 0
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 0
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 0
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "DEBUG"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "text"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
port: 3000
|
|
||||||
logs:
|
|
||||||
level: "DEBUG"
|
|
||||||
formatter: "text"
|
|
||||||
commands:
|
|
||||||
lock: true
|
|
||||||
merge:
|
|
||||||
template: "pdftk {{ range $filePath := FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
conversions:
|
|
||||||
- template: "markdown-pdf {{ .FilePath }} -o {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".md"
|
|
||||||
- template: "xvfb-run -e /dev/stdout wkhtmltopdf {{ .FilePath }} {{ .ResultFilePath }}"
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".html"
|
|
||||||
- ".htm"
|
|
||||||
- template: "unoconv --format pdf --output \"{{ .ResultFilePath }}\" \"{{ .FilePath }}\""
|
|
||||||
interpreter: "/bin/sh -c"
|
|
||||||
timeout: 30
|
|
||||||
extensions:
|
|
||||||
- ".doc"
|
|
||||||
- ".docx"
|
|
||||||
- ".odt"
|
|
||||||
- ".xls"
|
|
||||||
- ".xlsx"
|
|
||||||
- ".ods"
|
|
||||||
- ".ppt"
|
|
||||||
- ".pptx"
|
|
||||||
- ".odp"
|
|
||||||
BIN
_tests/file.docx
BIN
_tests/file.docx
Binary file not shown.
@@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Some title</title>
|
|
||||||
<meta name="author" content="Julien Neuhart">
|
|
||||||
<meta name="description" content="Simple HTML page">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<p>Hi! I have been converted from an HTML page!</p>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Hi, I've been converted from a markdown file!
|
|
||||||
BIN
_tests/file.pdf
BIN
_tests/file.pdf
Binary file not shown.
@@ -1,213 +0,0 @@
|
|||||||
/*
|
|
||||||
Package config contains all the logic allowing us to instantiate the application's configuration.
|
|
||||||
|
|
||||||
The application's configuration is loaded from a YAML file named gotenberg.yml.
|
|
||||||
It should be located where the user starts the application from the CLI.
|
|
||||||
*/
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// appConfig gathers all configuration data.
|
|
||||||
appConfig struct {
|
|
||||||
port string
|
|
||||||
logsLevel logrus.Level
|
|
||||||
logsFormatter logrus.Formatter
|
|
||||||
lock bool
|
|
||||||
// commands associates a file extension with a Command instance.
|
|
||||||
// Particular case: ".pdf" extension is used for the merge command.
|
|
||||||
commands map[string]*Command
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command gathers information on how to launch an external binary used for converting
|
|
||||||
// a file to PDF.
|
|
||||||
Command struct {
|
|
||||||
// Template is the data-driven template of the command.
|
|
||||||
Template *template.Template
|
|
||||||
// The binary which will call the command.
|
|
||||||
Interpreter []string
|
|
||||||
// Timeout is the duration in seconds after which the command's process will be killed
|
|
||||||
// if it does not finish before.
|
|
||||||
Timeout int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// our default instance of appConfig.
|
|
||||||
var config = &appConfig{}
|
|
||||||
|
|
||||||
// Reset reinitializes our configuration.
|
|
||||||
func Reset() {
|
|
||||||
config = &appConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPort sets the port which will be used by the application.
|
|
||||||
func WithPort(port string) {
|
|
||||||
config.port = port
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPort returns the current port.
|
|
||||||
func GetPort() string {
|
|
||||||
return config.port
|
|
||||||
}
|
|
||||||
|
|
||||||
// levels associates logging levels as defined in the configuration file gotenberg.yml
|
|
||||||
// with its counterpart from the logrus library.
|
|
||||||
var levels = map[string]logrus.Level{
|
|
||||||
"DEBUG": logrus.DebugLevel,
|
|
||||||
"INFO": logrus.InfoLevel,
|
|
||||||
"WARN": logrus.WarnLevel,
|
|
||||||
"ERROR": logrus.ErrorLevel,
|
|
||||||
"FATAL": logrus.FatalLevel,
|
|
||||||
"PANIC": logrus.PanicLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrongLogsLevelError struct{}
|
|
||||||
|
|
||||||
const wrongLogsLevelErrorMessage = "accepted values for logs level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC"
|
|
||||||
|
|
||||||
func (e *wrongLogsLevelError) Error() string {
|
|
||||||
return wrongLogsLevelErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLogsLevel sets the logs level.
|
|
||||||
// If the given string does not match with a logrus level,
|
|
||||||
// throws an error.
|
|
||||||
func WithLogsLevel(level string) error {
|
|
||||||
l, ok := levels[level]
|
|
||||||
if !ok {
|
|
||||||
return &wrongLogsLevelError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.logsLevel = l
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogsLevel returns the current logs level.
|
|
||||||
func GetLogsLevel() logrus.Level {
|
|
||||||
return config.logsLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatters associates logging formatter as defined in the configuration file gotenberg.yml
|
|
||||||
// with its counterpart from the logrus library.
|
|
||||||
var formatters = map[string]logrus.Formatter{
|
|
||||||
"text": &logrus.TextFormatter{},
|
|
||||||
"json": &logrus.JSONFormatter{},
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrongLogsFormatterError struct{}
|
|
||||||
|
|
||||||
const wrongLogsFormatterErrorMessage = "accepted value for logs formatter: text, json"
|
|
||||||
|
|
||||||
func (e *wrongLogsFormatterError) Error() string {
|
|
||||||
return wrongLogsFormatterErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLogsFormatter sets the logs formatter.
|
|
||||||
// If the given string does not match with a logrus formatter,
|
|
||||||
// throws an error.
|
|
||||||
func WithLogsFormatter(formatter string) error {
|
|
||||||
f, ok := formatters[formatter]
|
|
||||||
if !ok {
|
|
||||||
return &wrongLogsFormatterError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.logsFormatter = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogsFormatter returns the current logs formatter.
|
|
||||||
func GetLogsFormatter() logrus.Formatter {
|
|
||||||
return config.logsFormatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLock sets the lock strategy.
|
|
||||||
func WithLock(lock bool) {
|
|
||||||
config.lock = lock
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasLock returns the current lock strategy.
|
|
||||||
func HasLock() bool {
|
|
||||||
return config.lock
|
|
||||||
}
|
|
||||||
|
|
||||||
type interpreterEmptyError struct {
|
|
||||||
command string
|
|
||||||
}
|
|
||||||
|
|
||||||
const interpreterEmptyErrorMessage = "the interepreter for command %s should not be empty"
|
|
||||||
|
|
||||||
func (e *interpreterEmptyError) Error() string {
|
|
||||||
return fmt.Sprintf(interpreterEmptyErrorMessage, e.command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommand instantiates a Command. If the given command string
|
|
||||||
// is not a valid template, throws an error.
|
|
||||||
func NewCommand(command string, interpreter string, timeout int) (*Command, error) {
|
|
||||||
t, err := template.New(command).Parse(command)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if interpreter == "" {
|
|
||||||
return nil, &interpreterEmptyError{command}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Command{t, strings.Fields(interpreter), timeout}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileExtensionAlreadyUsedError struct {
|
|
||||||
extension string
|
|
||||||
command *Command
|
|
||||||
existingCommand *Command
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileExtensionAlreadyUsedErrorMessage = "file extension %s from command %s is already used by command %s"
|
|
||||||
|
|
||||||
func (e *fileExtensionAlreadyUsedError) Error() string {
|
|
||||||
return fmt.Sprintf(fileExtensionAlreadyUsedErrorMessage, e.extension, e.command.Template.Name(), e.existingCommand.Template.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCommand adds a Command instance and associates it with the given
|
|
||||||
// file extension. If the file extension is already used by another Command
|
|
||||||
// instance, throws an error.
|
|
||||||
func WithCommand(extension string, command *Command) error {
|
|
||||||
if config.commands == nil {
|
|
||||||
config.commands = make(map[string]*Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
existingCommand, ok := config.commands[extension]
|
|
||||||
if ok {
|
|
||||||
return &fileExtensionAlreadyUsedError{extension, command, existingCommand}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.commands[extension] = command
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type noCommandFoundForFileExtensionError struct {
|
|
||||||
extension string
|
|
||||||
}
|
|
||||||
|
|
||||||
const noCommandFoundForFileExtensionErrorMessage = "no command found for file extension %s"
|
|
||||||
|
|
||||||
func (e *noCommandFoundForFileExtensionError) Error() string {
|
|
||||||
return fmt.Sprintf(noCommandFoundForFileExtensionErrorMessage, e.extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommand returns the Command instance associated with the given
|
|
||||||
// file extension. If no Command instance found, throws an error.
|
|
||||||
func GetCommand(extension string) (*Command, error) {
|
|
||||||
c, ok := config.commands[extension]
|
|
||||||
if !ok {
|
|
||||||
return nil, &noCommandFoundForFileExtensionError{extension}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReset(t *testing.T) {
|
|
||||||
c := &appConfig{}
|
|
||||||
config.port = "3000"
|
|
||||||
Reset()
|
|
||||||
|
|
||||||
if c.port != config.port {
|
|
||||||
t.Error("Configuration should have been reset")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithPort(t *testing.T) {
|
|
||||||
port := "3000"
|
|
||||||
WithPort(port)
|
|
||||||
|
|
||||||
if config.port != port {
|
|
||||||
t.Errorf("Configuration populated with a wrong port: got '%s' want '%s'", config.port, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPort(t *testing.T) {
|
|
||||||
port := "3000"
|
|
||||||
config.port = port
|
|
||||||
|
|
||||||
if GetPort() != port {
|
|
||||||
t.Errorf("Configuration returned a wrong port: got '%s' want '%s'", GetPort(), port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrongLogsLevelError(t *testing.T) {
|
|
||||||
err := &wrongLogsLevelError{}
|
|
||||||
if err.Error() != wrongLogsLevelErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), wrongLogsLevelErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithLogsLevel(t *testing.T) {
|
|
||||||
var lvl string
|
|
||||||
|
|
||||||
// case 1: uses a wrong logs level.
|
|
||||||
lvl = "text"
|
|
||||||
if err := WithLogsLevel(lvl); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated by using '%s' as logs level", lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a correct logs level.
|
|
||||||
lvl = "DEBUG"
|
|
||||||
if err := WithLogsLevel(lvl); err != nil {
|
|
||||||
t.Errorf("Configuration should have been populated by using '%s' as logs level", lvl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLogsLevel(t *testing.T) {
|
|
||||||
lvl := logrus.DebugLevel
|
|
||||||
config.logsLevel = lvl
|
|
||||||
|
|
||||||
if GetLogsLevel() != lvl {
|
|
||||||
t.Errorf("Configuration returned a wrong logs level: got '%s' want '%s'", GetLogsLevel(), lvl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrongLogsFormatterError(t *testing.T) {
|
|
||||||
err := &wrongLogsFormatterError{}
|
|
||||||
if err.Error() != wrongLogsFormatterErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), wrongLogsFormatterErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithLogsFormatter(t *testing.T) {
|
|
||||||
var formatter string
|
|
||||||
|
|
||||||
// case 1: uses a wrong logs formatter.
|
|
||||||
formatter = "DEBUG"
|
|
||||||
if err := WithLogsFormatter(formatter); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated by using '%s' as logs formatter", formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a correct logs formatter.
|
|
||||||
formatter = "text"
|
|
||||||
if err := WithLogsFormatter(formatter); err != nil {
|
|
||||||
t.Errorf("Configuration should have been populated by using '%s' as logs formatter", formatter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLogsFormatter(t *testing.T) {
|
|
||||||
formatter := &logrus.TextFormatter{}
|
|
||||||
config.logsFormatter = formatter
|
|
||||||
|
|
||||||
if GetLogsFormatter() != formatter {
|
|
||||||
t.Errorf("Configuration returned a wrong logs formatter: got '%v' want '%v'", GetLogsFormatter(), formatter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithLock(t *testing.T) {
|
|
||||||
lock := true
|
|
||||||
WithLock(lock)
|
|
||||||
|
|
||||||
if config.lock != lock {
|
|
||||||
t.Errorf("Configuration populated with a wrong lock strategy: got '%t' want '%t'", config.lock, lock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasLock(t *testing.T) {
|
|
||||||
lock := true
|
|
||||||
config.lock = true
|
|
||||||
|
|
||||||
if HasLock() != lock {
|
|
||||||
t.Errorf("Configuration returned a wrong port: got '%t' want '%t'", HasLock(), lock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterpreterEmptyError(t *testing.T) {
|
|
||||||
err := &interpreterEmptyError{"echo hello world"}
|
|
||||||
expected := fmt.Sprintf(interpreterEmptyErrorMessage, err.command)
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewCommand(t *testing.T) {
|
|
||||||
var cmd string
|
|
||||||
|
|
||||||
// case 1: uses a wrong command template.
|
|
||||||
cmd = "pdftk {{ range $filePath := FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
if _, err := NewCommand(cmd, "/bin/sh -c", 0); err == nil {
|
|
||||||
t.Errorf("Command should not have been instantiated by using '%s' as command template", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a correct command template.
|
|
||||||
cmd = "pdftk {{ range $filePath := .FilesPaths }} {{ $filePath }} {{ end }} cat output {{ .ResultFilePath }}"
|
|
||||||
if _, err := NewCommand(cmd, "/bin/sh -c", 0); err != nil {
|
|
||||||
t.Errorf("Command should have been instantiated by using '%s' as command template", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: uses an empty interpreter.
|
|
||||||
if _, err := NewCommand(cmd, "", 0); err == nil {
|
|
||||||
t.Error("Command should not have been instantiated by using an empty interpreter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileExtensionAlreadyUsedError(t *testing.T) {
|
|
||||||
ext := ".pdf"
|
|
||||||
cmd1, _ := NewCommand("echo", "/bin/sh -c", 0)
|
|
||||||
cmd2, _ := NewCommand("echo", "/bin/sh -c", 0)
|
|
||||||
err := &fileExtensionAlreadyUsedError{ext, cmd1, cmd2}
|
|
||||||
expected := fmt.Sprintf(fileExtensionAlreadyUsedErrorMessage, err.extension, err.command.Template.Name(), err.existingCommand.Template.Name())
|
|
||||||
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithCommand(t *testing.T) {
|
|
||||||
ext := ".pdf"
|
|
||||||
cmd, _ := NewCommand("echo", "/bin/sh -c", 0)
|
|
||||||
|
|
||||||
// case 1: uses a command with a file extension not already referenced.
|
|
||||||
if err := WithCommand(ext, cmd); err != nil {
|
|
||||||
t.Errorf("Configuration should have been populated by using a command with the file extension '%s'", ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a command with a file extension already referenced.
|
|
||||||
if err := WithCommand(ext, cmd); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated by using a command with the file extension '%s'", ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoCommandFoundForFileExtensionError(t *testing.T) {
|
|
||||||
err := &noCommandFoundForFileExtensionError{".pdf"}
|
|
||||||
expected := fmt.Sprintf(noCommandFoundForFileExtensionErrorMessage, err.extension)
|
|
||||||
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCommand(t *testing.T) {
|
|
||||||
Reset()
|
|
||||||
ext := ".pdf"
|
|
||||||
cmd, _ := NewCommand("echo", "/bin/sh -c", 0)
|
|
||||||
WithCommand(ext, cmd)
|
|
||||||
|
|
||||||
// case 1: uses a file extension which has a command associated.
|
|
||||||
if _, err := GetCommand(ext); err != nil {
|
|
||||||
t.Errorf("Configuration should have been able to return a command by using the file extension '%s'", ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a file extension which has no command associated.
|
|
||||||
ext = ".docx"
|
|
||||||
if _, err := GetCommand(ext); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been able to return a command by using the file extension '%s'", ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseFile instantiates the application's configuration using the given YAML file.
|
|
||||||
func ParseFile(configurationFilePath string) error {
|
|
||||||
fileConfig, err := readFile(configurationFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
WithPort(fileConfig.Port)
|
|
||||||
|
|
||||||
if err := WithLogsLevel(fileConfig.Logs.Level); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := WithLogsFormatter(fileConfig.Logs.Formatter); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
WithLock(fileConfig.Commands.Lock)
|
|
||||||
|
|
||||||
// handles merge command first...
|
|
||||||
cmd, err := NewCommand(fileConfig.Commands.Merge.Template, fileConfig.Commands.Merge.Interpreter, fileConfig.Commands.Merge.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
WithCommand(".pdf", cmd)
|
|
||||||
|
|
||||||
// ...then conversion commands!
|
|
||||||
for _, command := range fileConfig.Commands.Conversions {
|
|
||||||
cmd, err := NewCommand(command.Template, command.Interpreter, command.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ext := range command.Extensions {
|
|
||||||
if err := WithCommand(ext, cmd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// fileConfig gathers all data coming from the configuration file gotenberg.yml.
|
|
||||||
fileConfig struct {
|
|
||||||
Port string `yaml:"port"`
|
|
||||||
Logs struct {
|
|
||||||
Level string `yaml:"level"`
|
|
||||||
Formatter string `yaml:"formatter"`
|
|
||||||
} `yaml:"logs"`
|
|
||||||
Commands struct {
|
|
||||||
Lock bool `yaml:"lock"`
|
|
||||||
Merge *mergeCommand `yaml:"merge"`
|
|
||||||
Conversions []*conversionCommand `yaml:"conversions,omitempty"`
|
|
||||||
} `yaml:"commands"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeCommand gathers all data regarding the... merge command.
|
|
||||||
mergeCommand struct {
|
|
||||||
Template string `yaml:"template"`
|
|
||||||
Interpreter string `yaml:"interpreter"`
|
|
||||||
Timeout int `yaml:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// conversionCommand gathers all data regarding a conversion command.
|
|
||||||
conversionCommand struct {
|
|
||||||
Template string `yaml:"template"`
|
|
||||||
Interpreter string `yaml:"interpreter"`
|
|
||||||
Timeout int `yaml:"timeout"`
|
|
||||||
Extensions []string `yaml:"extensions"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// readFile instantiates a fileConfig instance by reading
|
|
||||||
// the given YAML file.
|
|
||||||
func readFile(configurationFilePath string) (*fileConfig, error) {
|
|
||||||
c := &fileConfig{}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(configurationFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(data, &c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func load(configurationFilePath string) error {
|
|
||||||
Reset()
|
|
||||||
return ParseFile(configurationFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFile(t *testing.T) {
|
|
||||||
var path string
|
|
||||||
|
|
||||||
// case 1: uses an empty configuration file path.
|
|
||||||
if err := load(""); err == nil {
|
|
||||||
t.Error("Configuration should not have been populated by using an empty configuration file path")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a broken configuration file.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/broken-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: uses a configuration file with a wrong logging level.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/wrong-logging-level-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 4: uses a configuration file with a wrong logging formatter.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/wrong-logging-formatter-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 5: uses a configuration file with a wrong merge command template.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/wrong-merge-command-template-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 6: uses a configuration file with a wrong command template.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/wrong-command-template-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 7: uses a configuration file with a duplicate command.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/duplicate-command-gotenberg.yml")
|
|
||||||
if err := load(path); err == nil {
|
|
||||||
t.Errorf("Configuration should not have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 8: uses a correct configuration file.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
|
||||||
if err := load(path); err != nil {
|
|
||||||
t.Errorf("Configuration should have been populated with '%s'", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// Package context provides functions for setting and getting values from
|
|
||||||
// a request's context.
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
requestIDKey key = iota
|
|
||||||
converterKey
|
|
||||||
resultFilePathKey
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithRequestID populates a request's context with the given request ID
|
|
||||||
// and returns the updated request.
|
|
||||||
func WithRequestID(r *http.Request, requestID string) *http.Request {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx = context.WithValue(ctx, requestIDKey, requestID)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestIDNotFoundError struct{}
|
|
||||||
|
|
||||||
const requestIDNotFoundErrorMessage = "the request ID was not found in request context"
|
|
||||||
|
|
||||||
func (e *requestIDNotFoundError) Error() string {
|
|
||||||
return requestIDNotFoundErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRequestID returns the request ID if found in
|
|
||||||
// the request's context. Otherwise throws an error.
|
|
||||||
func GetRequestID(r *http.Request) (string, error) {
|
|
||||||
ID, ok := r.Context().Value(requestIDKey).(string)
|
|
||||||
if !ok {
|
|
||||||
return "", &requestIDNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithConverter populates a request's context with the given converter
|
|
||||||
// and returns the updated request.
|
|
||||||
func WithConverter(r *http.Request, converter *converter.Converter) *http.Request {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx = context.WithValue(ctx, converterKey, converter)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type converterNotFoundError struct{}
|
|
||||||
|
|
||||||
const converterNotFoundErrorMessage = "the converter was not found in request context"
|
|
||||||
|
|
||||||
func (e *converterNotFoundError) Error() string {
|
|
||||||
return converterNotFoundErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConverter returns the converter if found in
|
|
||||||
// the request's context. Otherwise throws an error.
|
|
||||||
func GetConverter(r *http.Request) (*converter.Converter, error) {
|
|
||||||
c, ok := r.Context().Value(converterKey).(*converter.Converter)
|
|
||||||
if !ok {
|
|
||||||
return nil, &converterNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithResultFilePath populates a request's context with the given result file path
|
|
||||||
// and returns the updated request.
|
|
||||||
func WithResultFilePath(r *http.Request, resultFilePath string) *http.Request {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx = context.WithValue(ctx, resultFilePathKey, resultFilePath)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type resultFilePathNotFoundError struct{}
|
|
||||||
|
|
||||||
const resultFilePathNotFoundErrorMessage = "the result file path was not found in request context"
|
|
||||||
|
|
||||||
func (e *resultFilePathNotFoundError) Error() string {
|
|
||||||
return resultFilePathNotFoundErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResultFilePath returns the result file path if found in
|
|
||||||
// the request's context. Otherwise throws an error.
|
|
||||||
func GetResultFilePath(r *http.Request) (string, error) {
|
|
||||||
path, ok := r.Context().Value(resultFilePathKey).(string)
|
|
||||||
if !ok {
|
|
||||||
return "", &resultFilePathNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter"
|
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWithRequestID(t *testing.T) {
|
|
||||||
requestID := uuid.NewV4().String()
|
|
||||||
req := WithRequestID(httptest.NewRequest(http.MethodPost, "/", nil), requestID)
|
|
||||||
if ID, _ := req.Context().Value(requestIDKey).(string); ID != requestID {
|
|
||||||
t.Errorf("Context returned a wrong converter: got '%s' want '%s'", ID, requestID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestIDNotFoundError(t *testing.T) {
|
|
||||||
err := &requestIDNotFoundError{}
|
|
||||||
if err.Error() != requestIDNotFoundErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), requestIDNotFoundErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRequestID(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
|
|
||||||
// case 1: uses a request without a request ID entry in its context.
|
|
||||||
if _, err := GetRequestID(req); err == nil {
|
|
||||||
t.Error("Context should not have a request ID entry")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with a request ID entry in its context.
|
|
||||||
req = WithRequestID(req, uuid.NewV4().String())
|
|
||||||
if _, err := GetRequestID(req); err != nil {
|
|
||||||
t.Error("Context should have a request ID entry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithConverter(t *testing.T) {
|
|
||||||
req := WithConverter(httptest.NewRequest(http.MethodPost, "/", nil), &converter.Converter{})
|
|
||||||
if c, _ := req.Context().Value(converterKey).(*converter.Converter); c == nil {
|
|
||||||
t.Errorf("Context returned a wrong converter: got '%v' want not nil", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConverterNotFoundError(t *testing.T) {
|
|
||||||
err := &converterNotFoundError{}
|
|
||||||
if err.Error() != converterNotFoundErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), converterNotFoundErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetConverter(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
|
|
||||||
// case 1: uses a request without a converter entry in its context.
|
|
||||||
if _, err := GetConverter(req); err == nil {
|
|
||||||
t.Error("Context should not have a converter entry")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with a converter entry in its context.
|
|
||||||
req = WithConverter(req, &converter.Converter{})
|
|
||||||
if _, err := GetConverter(req); err != nil {
|
|
||||||
t.Error("Context should have a converter entry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithResultFilePath(t *testing.T) {
|
|
||||||
filePath := "file.pdf"
|
|
||||||
req := WithResultFilePath(httptest.NewRequest(http.MethodPost, "/", nil), filePath)
|
|
||||||
if path, _ := req.Context().Value(resultFilePathKey).(string); path != filePath {
|
|
||||||
t.Errorf("Context returned a wrong result file path: got '%s' want '%s'", path, filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResultFilePathNotFoundError(t *testing.T) {
|
|
||||||
err := &resultFilePathNotFoundError{}
|
|
||||||
if err.Error() != resultFilePathNotFoundErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), resultFilePathNotFoundErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetResultFilePath(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
|
|
||||||
// case 1: uses a request without a result file path entry in its context.
|
|
||||||
if _, err := GetResultFilePath(req); err == nil {
|
|
||||||
t.Error("Context should not have a result file path entry")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with a result file path entry in its context.
|
|
||||||
req = WithResultFilePath(req, "file.pdf")
|
|
||||||
if _, err := GetResultFilePath(req); err != nil {
|
|
||||||
t.Error("Context should have a result file path entry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
// Package converter implements a solution for converting one or more files to PDF.
|
|
||||||
package converter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter/process"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/logger"
|
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Converter handles conversion into PDF of files coming from a request.
|
|
||||||
type Converter struct {
|
|
||||||
files []*gfile.File
|
|
||||||
workingDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoFileToConvertError is raided when a request has not file attached to it.
|
|
||||||
type NoFileToConvertError struct{}
|
|
||||||
|
|
||||||
const noFileToConvertErrorMessage = "no file to convert"
|
|
||||||
|
|
||||||
func (e *NoFileToConvertError) Error() string {
|
|
||||||
return noFileToConvertErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConverter instantiates a converter by parsing a request.
|
|
||||||
func NewConverter(r *http.Request) (*Converter, error) {
|
|
||||||
c := &Converter{
|
|
||||||
workingDir: fmt.Sprintf("./%s/", uuid.NewV4().String()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Mkdir(c.workingDir, 0666); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debugf("created working directory %s", c.workingDir)
|
|
||||||
|
|
||||||
reader, err := r.MultipartReader()
|
|
||||||
if err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
part, err := reader.NextPart()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := part.FileName()
|
|
||||||
if fileName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := gfile.NewFile(c.workingDir, part, fileName)
|
|
||||||
if err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.files = append(c.files, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.files) == 0 {
|
|
||||||
return c, &NoFileToConvertError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert converts its associated files to PDF. If more than one file,
|
|
||||||
// it will merge all of them into one unique PDF file.
|
|
||||||
// Returns the new file path or an error if something bad happened.
|
|
||||||
func (c *Converter) Convert() (string, error) {
|
|
||||||
var filesPaths []string
|
|
||||||
for _, f := range c.files {
|
|
||||||
if f.Extension != ".pdf" {
|
|
||||||
path, err := process.Unconv(c.workingDir, f)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
filesPaths = append(filesPaths, path)
|
|
||||||
} else {
|
|
||||||
filesPaths = append(filesPaths, f.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filesPaths) == 1 {
|
|
||||||
return filesPaths[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := process.Merge(c.workingDir, filesPaths)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all file inside its working directory.
|
|
||||||
func (c *Converter) Clear() error {
|
|
||||||
if err := os.RemoveAll(c.workingDir); err != nil {
|
|
||||||
logger.Error(fmt.Errorf("failed to remove working directory %s", c.workingDir))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debugf("removed working directory %s", c.workingDir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package converter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeRequest(filesPaths ...string) *http.Request {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
mpw := multipart.NewWriter(w)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var part io.Writer
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
if len(filesPaths) == 0 {
|
|
||||||
part, _ = mpw.CreateFormField("foo")
|
|
||||||
part.Write([]byte("bar"))
|
|
||||||
} else {
|
|
||||||
for _, filePath := range filesPaths {
|
|
||||||
file, _ := os.Open(filePath)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileInfo, _ := file.Stat()
|
|
||||||
part, _ = mpw.CreateFormFile("files", fileInfo.Name())
|
|
||||||
io.Copy(part, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mpw.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", r)
|
|
||||||
req.Header.Set("Content-Type", mpw.FormDataContentType())
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func load(configurationFilePath string) {
|
|
||||||
config.Reset()
|
|
||||||
path, _ := filepath.Abs(configurationFilePath)
|
|
||||||
config.ParseFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFileToConvertError(t *testing.T) {
|
|
||||||
err := &NoFileToConvertError{}
|
|
||||||
if err.Error() != noFileToConvertErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), noFileToConvertErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewConverter(t *testing.T) {
|
|
||||||
var (
|
|
||||||
path string
|
|
||||||
oPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
load("../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
// case 1: uses a request with a single file.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
if _, err := NewConverter(makeRequest(path)); err != nil {
|
|
||||||
t.Errorf("Converter should have been instantiated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with wrong file type.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
|
||||||
if _, err := NewConverter(makeRequest(path)); err == nil {
|
|
||||||
t.Errorf("Converter should not have been instantiated with '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: uses a request with two files.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
if _, err := NewConverter(makeRequest(path, oPath)); err != nil {
|
|
||||||
t.Errorf("Converter should have been instantiated with '%s' and '%s'", path, oPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 4: uses a request with one Office file type and one wrong file type.
|
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
if _, err := NewConverter(makeRequest(path, oPath)); err == nil {
|
|
||||||
t.Errorf("Converter should not have been instantiated with '%s' and '%s'", path, oPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 5: uses a request with no file.
|
|
||||||
if _, err := NewConverter(makeRequest()); err == nil {
|
|
||||||
t.Error("Converter should not have been instantiated with no file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
|
||||||
var (
|
|
||||||
path string
|
|
||||||
oPath string
|
|
||||||
c *Converter
|
|
||||||
)
|
|
||||||
|
|
||||||
load("../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
// case 1: uses a request with a single file.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
c, _ = NewConverter(makeRequest(path))
|
|
||||||
if _, err := c.Convert(); err != nil {
|
|
||||||
t.Errorf("Converter should have been able to convert '%s' to PDF", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with two files.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
c, _ = NewConverter(makeRequest(path, oPath))
|
|
||||||
if _, err := c.Convert(); err != nil {
|
|
||||||
t.Errorf("Converter should have been able to convert '%s' and '%s' to PDF", path, oPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../../_tests/configurations/timeout-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 3: uses a request with a single file and a configuration with an unsuitable timeout for the conversion commands.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
c, _ = NewConverter(makeRequest(path))
|
|
||||||
if _, err := c.Convert(); err == nil {
|
|
||||||
t.Errorf("Converter should not have been able to convert '%s' to PDF", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../../_tests/configurations/merge-timeout-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 4: uses a request with two files and a configuration with an unsuitable timeout for the merge command.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
|
||||||
c, _ = NewConverter(makeRequest(path, oPath))
|
|
||||||
if _, err := c.Convert(); err == nil {
|
|
||||||
t.Errorf("Converter should not have been able to merge '%s' and '%s' into PDF", path, oPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClear(t *testing.T) {
|
|
||||||
load("../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
path, _ := filepath.Abs("../../_tests/file.docx")
|
|
||||||
c, _ := NewConverter(makeRequest(path))
|
|
||||||
if err := c.Clear(); err != nil {
|
|
||||||
t.Error("Converter should have been able to clear itself")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// Package file implements a solution for handling files coming from a request.
|
|
||||||
package file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/logger"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File represents a file which has been created
|
|
||||||
// from a request.
|
|
||||||
type File struct {
|
|
||||||
// Extension is the extension of the file.
|
|
||||||
Extension string
|
|
||||||
// Path is the file path.
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFile creates a file in the considered directory.
|
|
||||||
// Returns a *File instance or an error if something bad happened.
|
|
||||||
func NewFile(workingDir string, r io.Reader, fileName string) (*File, error) {
|
|
||||||
ext := filepath.Ext(fileName)
|
|
||||||
|
|
||||||
if _, err := config.GetCommand(ext); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f := &File{ext, MakeFilePath(workingDir, ext)}
|
|
||||||
|
|
||||||
file, err := os.Create(f.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
n, err := io.Copy(file, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// resets the read pointer.
|
|
||||||
file.Seek(0, 0)
|
|
||||||
|
|
||||||
logger.Debugf("working file %s has been created from %s (%s copied)", f.Path, fileName, humanize.Bytes(uint64(n)))
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeFilePath is a simple helper which generates a random file name
|
|
||||||
// and associates it with the considered directory to make a path.
|
|
||||||
func MakeFilePath(workingDir string, ext string) string {
|
|
||||||
return fmt.Sprintf("%s%s%s", workingDir, uuid.NewV4().String(), ext)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func load(configurationFilePath string) {
|
|
||||||
config.Reset()
|
|
||||||
path, _ := filepath.Abs(configurationFilePath)
|
|
||||||
config.ParseFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewFile(t *testing.T) {
|
|
||||||
load("../../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
workingDir := "test"
|
|
||||||
os.Mkdir(workingDir, 0666)
|
|
||||||
|
|
||||||
// case 1: uses a wrong file name.
|
|
||||||
if _, err := NewFile(workingDir, new(bytes.Buffer), "file.yml"); err == nil {
|
|
||||||
t.Error("File should not have been instantiated with an empty buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a file name.
|
|
||||||
filePath, _ := filepath.Abs("../../../_tests/file.pdf")
|
|
||||||
r, _ := os.Open(filePath)
|
|
||||||
defer r.Close()
|
|
||||||
if _, err := NewFile(workingDir, r, "file.pdf"); err != nil {
|
|
||||||
t.Errorf("File should have been instantiated using a reader of '%s'", filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.RemoveAll(workingDir)
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
// Package process handles all commands executions.
|
|
||||||
package process
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type runner struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var forest = &runner{}
|
|
||||||
|
|
||||||
type commandTimeoutError struct {
|
|
||||||
command string
|
|
||||||
timeout int
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandTimeoutErrorMessage = "the command %s has reached the %d second(s) timeout"
|
|
||||||
|
|
||||||
func (e *commandTimeoutError) Error() string {
|
|
||||||
return fmt.Sprintf(commandTimeoutErrorMessage, e.command, e.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run runs the given command. If timeout is reached or
|
|
||||||
// something bad happened, returns an error.
|
|
||||||
func (r *runner) run(command string, interpreter []string, timeout int) error {
|
|
||||||
if config.HasLock() {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
logger.Warn("lock disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
binary := interpreter[0]
|
|
||||||
parameters := append(interpreter[1:], command)
|
|
||||||
cmd := exec.Command(binary, parameters...)
|
|
||||||
logger.Debugf("executing command %s", cmd.Args)
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// wait for the process to finish or kill it after a timeout.
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Duration(timeout) * time.Second):
|
|
||||||
if err := cmd.Process.Kill(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &commandTimeoutError{command, timeout}
|
|
||||||
case err := <-done:
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// conversionData will be applied to the data-driven templates of conversions commands.
|
|
||||||
type conversionData struct {
|
|
||||||
FilePath string
|
|
||||||
ResultFilePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unconv converts a file to PDF and returns the new file path.
|
|
||||||
func Unconv(workingDir string, file *gfile.File) (string, error) {
|
|
||||||
cmdData := &conversionData{file.Path, gfile.MakeFilePath(workingDir, ".pdf")}
|
|
||||||
|
|
||||||
cmd, err := config.GetCommand(file.Extension)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var data bytes.Buffer
|
|
||||||
if err := cmd.Template.Execute(&data, cmdData); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = forest.run(data.String(), cmd.Interpreter, cmd.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debugf("created %s from %s", cmdData.ResultFilePath, cmdData.FilePath)
|
|
||||||
return cmdData.ResultFilePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeData will be applied to the data-driven template of the merge command.
|
|
||||||
type mergeData struct {
|
|
||||||
FilesPaths []string
|
|
||||||
ResultFilePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges many PDF files to one unique PDF file and returns the new file path.
|
|
||||||
func Merge(workingDir string, filesPaths []string) (string, error) {
|
|
||||||
cmdData := &mergeData{filesPaths, gfile.MakeFilePath(workingDir, ".pdf")}
|
|
||||||
|
|
||||||
cmd, err := config.GetCommand(".pdf")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var data bytes.Buffer
|
|
||||||
if err := cmd.Template.Execute(&data, cmdData); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = forest.run(data.String(), cmd.Interpreter, cmd.Timeout)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debugf("created %s from %+v", cmdData.ResultFilePath, cmdData.FilesPaths)
|
|
||||||
return cmdData.ResultFilePath, nil
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package process
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeFile(workingDir string, fileName string) *gfile.File {
|
|
||||||
filePath := fmt.Sprintf("%s%s", "../../../_tests/", fileName)
|
|
||||||
absPath, _ := filepath.Abs(filePath)
|
|
||||||
|
|
||||||
r, _ := os.Open(absPath)
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
f, _ := gfile.NewFile(workingDir, r, fileName)
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func load(configurationFilePath string) {
|
|
||||||
config.Reset()
|
|
||||||
path, _ := filepath.Abs(configurationFilePath)
|
|
||||||
config.ParseFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCommandTimeoutError(t *testing.T) {
|
|
||||||
err := &commandTimeoutError{"echo hello", 30}
|
|
||||||
expected := fmt.Sprintf(commandTimeoutErrorMessage, err.command, err.timeout)
|
|
||||||
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRun(t *testing.T) {
|
|
||||||
var cmd string
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
// case 1: uses a simple command.
|
|
||||||
cmd = "echo Hello world"
|
|
||||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 30); err != nil {
|
|
||||||
t.Errorf("Command '%s' should have worked", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a simple command but with an unsuitable timeout.
|
|
||||||
cmd = "sleep 5"
|
|
||||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 0); err == nil {
|
|
||||||
t.Errorf("Command '%s' should not have worked", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: uses a broken command.
|
|
||||||
cmd = "helloworld"
|
|
||||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 30); err == nil {
|
|
||||||
t.Errorf("Command '%s' should not have worked", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/no-lock-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 4: uses a configuration with a no lock strategy.
|
|
||||||
cmd = "echo Hello world"
|
|
||||||
if err := forest.run(cmd, strings.Fields("/bin/sh -c"), 30); err != nil {
|
|
||||||
t.Errorf("Command '%s' should have worked", cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnconv(t *testing.T) {
|
|
||||||
var file *gfile.File
|
|
||||||
|
|
||||||
workingDir := "test"
|
|
||||||
os.Mkdir(workingDir, 0666)
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
// case 1: uses an Markdown file type.
|
|
||||||
file = makeFile(workingDir, "file.md")
|
|
||||||
if _, err := Unconv(workingDir, file); err != nil {
|
|
||||||
t.Errorf("Converting '%s' to PDF should have worked", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses an HTML file type.
|
|
||||||
file = makeFile(workingDir, "file.html")
|
|
||||||
if _, err := Unconv(workingDir, file); err != nil {
|
|
||||||
t.Errorf("Converting '%s' to PDF should have worked", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: uses an Office file type.
|
|
||||||
file = makeFile(workingDir, "file.docx")
|
|
||||||
if _, err := Unconv(workingDir, file); err != nil {
|
|
||||||
t.Errorf("Converting '%s' to PDF should have worked", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 4: uses a PDF file type.
|
|
||||||
file = makeFile(workingDir, "file.pdf")
|
|
||||||
if _, err := Unconv(workingDir, file); err == nil {
|
|
||||||
t.Errorf("Converting '%s' to PDF should not have worked", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/timeout-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 5: uses a command with an unsuitable timeout.
|
|
||||||
file = makeFile(workingDir, "file.docx")
|
|
||||||
if _, err := Unconv(workingDir, makeFile(workingDir, "file.docx")); err == nil {
|
|
||||||
t.Errorf("Converting '%s' to PDF should have reached timeout", file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.RemoveAll(workingDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
|
||||||
workingDir := "test"
|
|
||||||
os.Mkdir(workingDir, 0666)
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
var filesPaths []string
|
|
||||||
path, _ := filepath.Abs("../../../_tests/file.pdf")
|
|
||||||
filesPaths = append(filesPaths, path)
|
|
||||||
filesPaths = append(filesPaths, path)
|
|
||||||
|
|
||||||
// case 1: simple merge.
|
|
||||||
if _, err := Merge(workingDir, filesPaths); err != nil {
|
|
||||||
t.Error("Merge should have worked")
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../../../_tests/configurations/timeout-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 2: uses a command with an unsuitable timeout.
|
|
||||||
if _, err := Merge(workingDir, filesPaths); err == nil {
|
|
||||||
t.Error("Merge should have reached timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.RemoveAll(workingDir)
|
|
||||||
}
|
|
||||||
170
app/handlers.go
170
app/handlers.go
@@ -1,170 +0,0 @@
|
|||||||
// Package app implements all functions on which a request will pass through.
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/context"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter"
|
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/logger"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/justinas/alice"
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetHandlersChain returns the handlers chaining
|
|
||||||
// thanks to the alice library.
|
|
||||||
func GetHandlersChain() http.Handler {
|
|
||||||
return alice.New(enforceContentLengthHandler, enforceContentTypeHandler, convertHandler).ThenFunc(serveHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestHasNoContentError struct{}
|
|
||||||
|
|
||||||
const requestHasNoContentErrorMessage = "request has not content"
|
|
||||||
|
|
||||||
func (e *requestHasNoContentError) Error() string {
|
|
||||||
return requestHasNoContentErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// enforeContentLengthHandler checks if the request has content.
|
|
||||||
func enforceContentLengthHandler(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.ContentLength == 0 {
|
|
||||||
e := &requestHasNoContentError{}
|
|
||||||
http.Error(w, e.Error(), http.StatusBadRequest)
|
|
||||||
logger.Error(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestID := uuid.NewV4().String()
|
|
||||||
r = context.WithRequestID(r, requestID)
|
|
||||||
logger.Infof("identified new request (%s) with %s", humanize.Bytes(uint64(r.ContentLength)), requestID)
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// enforceContentTypeHandler checks if the "Content-Type" entry
|
|
||||||
// from the request's header matches the allowed content type.
|
|
||||||
func enforceContentTypeHandler(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if err := ghttp.CheckAuthorizedContentType(r.Header); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertHandler is in charge of converting the file(s) from the request to PDF.
|
|
||||||
func convertHandler(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c, err := converter.NewConverter(r)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*converter.NoFileToConvertError); ok {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
} else {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Error(err)
|
|
||||||
|
|
||||||
if c != nil {
|
|
||||||
r = context.WithConverter(r, c)
|
|
||||||
cleanup(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r = context.WithConverter(r, c)
|
|
||||||
|
|
||||||
path, err := c.Convert()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
cleanup(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r = context.WithResultFilePath(r, path)
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// serveHandler simply serves the created PDF.
|
|
||||||
func serveHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
path, err := context.GetResultFilePath(r)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
cleanup(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
cleanup(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
resultFileInfo, err := reader.Stat()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
cleanup(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestID, err := context.GetRequestID(r)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debugf("serving result file %s for request %s...", path, requestID)
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", resultFileInfo.Name()))
|
|
||||||
w.Header().Set("Content-Type", "application/pdf")
|
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", resultFileInfo.Size()))
|
|
||||||
_, err := io.Copy(w, reader)
|
|
||||||
|
|
||||||
done <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = <-done
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
} else {
|
|
||||||
logger.Infof("result file %s (%s) sent for request %s", path, humanize.Bytes(uint64(resultFileInfo.Size())), requestID)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup removes all files created during the conversion.
|
|
||||||
func cleanup(r *http.Request) {
|
|
||||||
c, err := context.GetConverter(r)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Clear(); err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/context"
|
|
||||||
|
|
||||||
"github.com/justinas/alice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeRequest(filesPaths ...string) *http.Request {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
mpw := multipart.NewWriter(w)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var part io.Writer
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
if len(filesPaths) == 0 {
|
|
||||||
part, _ = mpw.CreateFormField("foo")
|
|
||||||
part.Write([]byte("bar"))
|
|
||||||
} else {
|
|
||||||
for _, filePath := range filesPaths {
|
|
||||||
file, _ := os.Open(filePath)
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileInfo, _ := file.Stat()
|
|
||||||
part, _ = mpw.CreateFormFile("files", fileInfo.Name())
|
|
||||||
io.Copy(part, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mpw.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", r)
|
|
||||||
req.Header.Set("Content-Type", mpw.FormDataContentType())
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func load(configurationFilePath string) {
|
|
||||||
config.Reset()
|
|
||||||
path, _ := filepath.Abs(configurationFilePath)
|
|
||||||
config.ParseFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fakeSuccessHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetHandlersChain(t *testing.T) {
|
|
||||||
// dumb test to improve code coverage...
|
|
||||||
if GetHandlersChain() == nil {
|
|
||||||
t.Errorf("Handler chains should not be nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestHasNoContentError(t *testing.T) {
|
|
||||||
err := &requestHasNoContentError{}
|
|
||||||
if err.Error() != requestHasNoContentErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got '%s' want '%s'", err.Error(), requestHasNoContentErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnforceContentLengthHandler(t *testing.T) {
|
|
||||||
var (
|
|
||||||
req *http.Request
|
|
||||||
rr *httptest.ResponseRecorder
|
|
||||||
)
|
|
||||||
|
|
||||||
h := alice.New(enforceContentLengthHandler).ThenFunc(fakeSuccessHandler)
|
|
||||||
|
|
||||||
// case 1: sends an empty request.
|
|
||||||
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusBadRequest {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: sends a real body.
|
|
||||||
path, _ := filepath.Abs("../_tests/file.docx")
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, makeRequest(path))
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnforceContentTypeHandler(t *testing.T) {
|
|
||||||
var (
|
|
||||||
req *http.Request
|
|
||||||
rr *httptest.ResponseRecorder
|
|
||||||
)
|
|
||||||
|
|
||||||
h := alice.New(enforceContentTypeHandler).ThenFunc(fakeSuccessHandler)
|
|
||||||
|
|
||||||
// case 1: sends a wrong content type.
|
|
||||||
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/pdf")
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusUnsupportedMediaType {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusUnsupportedMediaType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: sends a good content type.
|
|
||||||
path, _ := filepath.Abs("../_tests/file.docx")
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, makeRequest(path))
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("Handler returned wrong a status code: got '%v' want '%v'", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConvertHandler(t *testing.T) {
|
|
||||||
var (
|
|
||||||
req *http.Request
|
|
||||||
rr *httptest.ResponseRecorder
|
|
||||||
path string
|
|
||||||
oPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
h := alice.New(convertHandler).ThenFunc(fakeSuccessHandler)
|
|
||||||
|
|
||||||
// case 1: sends a request without body.
|
|
||||||
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: sends a request with no file.
|
|
||||||
req = makeRequest()
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusBadRequest {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../_tests/configurations/merge-timeout-gotenberg.yml")
|
|
||||||
|
|
||||||
// case 3: sends a request with two files and using an unsuitable timeout for merge commande.
|
|
||||||
path, _ = filepath.Abs("../_tests/file.pdf")
|
|
||||||
oPath, _ = filepath.Abs("../_tests/file.docx")
|
|
||||||
req = makeRequest(path, oPath)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
load("../_tests/configurations/gotenberg.yml")
|
|
||||||
|
|
||||||
// case 4: sends a request with two files.
|
|
||||||
path, _ = filepath.Abs("../_tests/file.pdf")
|
|
||||||
oPath, _ = filepath.Abs("../_tests/file.docx")
|
|
||||||
req = makeRequest(path, oPath)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 5: sends five requests (almost) simultany.
|
|
||||||
path, _ = filepath.Abs("../_tests/file.docx")
|
|
||||||
filesPaths := []string{
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(filesPaths); i++ {
|
|
||||||
go func(i int) {
|
|
||||||
req := makeRequest(filesPaths[i])
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeHandler(t *testing.T) {
|
|
||||||
var (
|
|
||||||
req *http.Request
|
|
||||||
rr *httptest.ResponseRecorder
|
|
||||||
)
|
|
||||||
|
|
||||||
h := alice.New().ThenFunc(serveHandler)
|
|
||||||
|
|
||||||
// case 1: sends a request without a result file path entry in its context.
|
|
||||||
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: sends a request with a wrong result file path entry in its context.
|
|
||||||
req = context.WithResultFilePath(httptest.NewRequest(http.MethodPost, "/", nil), "file")
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: sends a request with a correct result file path entry in its context.
|
|
||||||
path, _ := filepath.Abs("../_tests/file.pdf")
|
|
||||||
req = context.WithResultFilePath(httptest.NewRequest(http.MethodPost, "/", nil), path)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got '%v' want '%v'", status, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user