mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 14:21:39 +00:00
Compare commits
529 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6863ae2d6 | ||
|
|
36830250b5 | ||
|
|
4ca1c3537b | ||
|
|
eeab09eacb | ||
|
|
af16967257 | ||
|
|
75e2bf5a9a | ||
|
|
4db3bc409b | ||
|
|
32ccf414ea | ||
|
|
615e48fffc | ||
|
|
93bf3fce29 | ||
|
|
899601569a | ||
|
|
b1805b64a2 | ||
|
|
58190343b1 | ||
|
|
99d48b1939 | ||
|
|
82b66d53cb | ||
|
|
3200de56cc | ||
|
|
0a627d5c79 | ||
|
|
22399deb79 | ||
|
|
6a77617e3b | ||
|
|
2868ef99ae | ||
|
|
21557f9892 | ||
|
|
d2385ae62d | ||
|
|
a84efef389 | ||
|
|
310bcd1585 | ||
|
|
753f44deb2 | ||
|
|
df1f0f8f09 | ||
|
|
45e1b50674 | ||
|
|
0a2b048fb1 | ||
|
|
e3c5dca09d | ||
|
|
88339b7214 | ||
|
|
1f2bb18bc1 | ||
|
|
74977a6154 | ||
|
|
00413fe7a4 | ||
|
|
9bb9d331ad | ||
|
|
f022ffdff4 | ||
|
|
28dade2a34 | ||
|
|
7378b9d843 | ||
|
|
71075e95bf | ||
|
|
108990cf06 | ||
|
|
ebfdf4b052 | ||
|
|
dbf4073216 | ||
|
|
83214eaaf8 | ||
|
|
1100fdd456 | ||
|
|
481bfa5440 | ||
|
|
30282c7fbb | ||
|
|
382bc71b21 | ||
|
|
f3fba97652 | ||
|
|
7f51e35bd4 | ||
|
|
95beb8e62a | ||
|
|
1a9de867f9 | ||
|
|
b42946bbe1 | ||
|
|
40b2fd09ff | ||
|
|
a3d560a8a2 | ||
|
|
ed20fe252e | ||
|
|
375e36ff96 | ||
|
|
e7108b108e | ||
|
|
6d59daad19 | ||
|
|
21c693921b | ||
|
|
7bcd5fbed7 | ||
|
|
7104970e17 | ||
|
|
1a2950b580 | ||
|
|
085b24e1c5 | ||
|
|
8688ce6328 | ||
|
|
fbdfed81e7 | ||
|
|
94fe20607e | ||
|
|
6c62483e8e | ||
|
|
54689129c6 | ||
|
|
e9e8dd5a82 | ||
|
|
00e764b118 | ||
|
|
cee7eb970a | ||
|
|
daed17fac8 | ||
|
|
8708f4f93f | ||
|
|
c7c1bfbeba | ||
|
|
0418438b6f | ||
|
|
a2ea4d036e | ||
|
|
dc7a29908f | ||
|
|
794db5d2a4 | ||
|
|
e5f9db129b | ||
|
|
a6aecf4e9d | ||
|
|
b59bc4ec90 | ||
|
|
41920f7865 | ||
|
|
4630bf5681 | ||
|
|
1c78ebd20e | ||
|
|
80d17cfda3 | ||
|
|
a154007927 | ||
|
|
bd8274cc27 | ||
|
|
fb08991c05 | ||
|
|
7c1f06fdf7 | ||
|
|
93b38b9f95 | ||
|
|
7ffc97d301 | ||
|
|
280301f258 | ||
|
|
40daf38f80 | ||
|
|
d24925cd5f | ||
|
|
cd42d54b43 | ||
|
|
53d8ecb6bc | ||
|
|
98e87d0297 | ||
|
|
400b4af769 | ||
|
|
368701afb1 | ||
|
|
a501b89ecd | ||
|
|
91cddd72e5 | ||
|
|
8a1f0c9dbf | ||
|
|
e3e5318b4f | ||
|
|
b060664c9f | ||
|
|
83fbf0e8ac | ||
|
|
537a926618 | ||
|
|
f791a59b1d | ||
|
|
0b8e41f993 | ||
|
|
f540fa2a38 | ||
|
|
2d7bc2f34a | ||
|
|
c2dea0a4d7 | ||
|
|
42cbfbf8ed | ||
|
|
137e79b012 | ||
|
|
5849ed3ecc | ||
|
|
d3dc1e1197 | ||
|
|
c20f0bef44 | ||
|
|
c572b6b182 | ||
|
|
a1392dbf86 | ||
|
|
4e719bab5e | ||
|
|
34b51ea64a | ||
|
|
5a2a72f530 | ||
|
|
2ea80c41ab | ||
|
|
6f987958e8 | ||
|
|
ae4007aad5 | ||
|
|
c4401f8bd4 | ||
|
|
0e7472de50 | ||
|
|
e998c78609 | ||
|
|
c30b92cd38 | ||
|
|
2bf2d2aef7 | ||
|
|
cdc04b0803 | ||
|
|
5f5875acc1 | ||
|
|
d306c5e0a3 | ||
|
|
19a815cffe | ||
|
|
da0c559293 | ||
|
|
a2c91ef7b3 | ||
|
|
722b94ca32 | ||
|
|
299742fe03 | ||
|
|
3964cbf911 | ||
|
|
63e4947ad5 | ||
|
|
e3cb13a414 | ||
|
|
01fec79d78 | ||
|
|
a7043a1359 | ||
|
|
91a93ecd62 | ||
|
|
c52fdf6395 | ||
|
|
1d1dad4b30 | ||
|
|
f07a57e478 | ||
|
|
ebacd9b4b4 | ||
|
|
f010e59597 | ||
|
|
a184d7a8e0 | ||
|
|
807f54c549 | ||
|
|
24684abc1d | ||
|
|
1f1a49976c | ||
|
|
562fda3079 | ||
|
|
05642f3c14 | ||
|
|
251e2774aa | ||
|
|
2089589d34 | ||
|
|
c48b135c43 | ||
|
|
70121a6ebf | ||
|
|
c23e53585a | ||
|
|
89e964163e | ||
|
|
0357774ba6 | ||
|
|
93cf750249 | ||
|
|
b712f7a344 | ||
|
|
4159a5cbb8 | ||
|
|
2e78a291d4 | ||
|
|
3f1705c2a5 | ||
|
|
bb1f5f7059 | ||
|
|
75b7d0c419 | ||
|
|
41a6c11c55 | ||
|
|
57d908e369 | ||
|
|
64274fdb33 | ||
|
|
da919fd189 | ||
|
|
cfa25f12d3 | ||
|
|
05bc1c1263 | ||
|
|
939c79c37f | ||
|
|
d352ddeea1 | ||
|
|
72a683f2b1 | ||
|
|
784399f345 | ||
|
|
710be4371b | ||
|
|
eece358aec | ||
|
|
b43ada4f83 | ||
|
|
9030af4faf | ||
|
|
38b424b62e | ||
|
|
1d9bf0b1aa | ||
|
|
d3b7700c07 | ||
|
|
d9513e159f | ||
|
|
6ddfdf2514 | ||
|
|
478804bd5c | ||
|
|
b61165a753 | ||
|
|
b3814ae7be | ||
|
|
019c363a74 | ||
|
|
da5f80e704 | ||
|
|
b37b10e669 | ||
|
|
8ca92eda39 | ||
|
|
81dbbc36db | ||
|
|
7065101b87 | ||
|
|
00c302e545 | ||
|
|
703530ce7f | ||
|
|
7ac15042d8 | ||
|
|
a80ec52027 | ||
|
|
4da4132220 | ||
|
|
8682e66eb0 | ||
|
|
34bf205d37 | ||
|
|
d6c2c6a2c3 | ||
|
|
f45639e6e2 | ||
|
|
82968e29bf | ||
|
|
5d3d571545 | ||
|
|
6999c13877 | ||
|
|
82a551e88f | ||
|
|
1b1a0c876c | ||
|
|
b262c4a898 | ||
|
|
22d1055d82 | ||
|
|
fe38565a9a | ||
|
|
a25d14e83f | ||
|
|
15b21dd8d7 | ||
|
|
caedcde49b | ||
|
|
8091e23e00 | ||
|
|
08e1090b15 | ||
|
|
f76b5cb2eb | ||
|
|
edc4311dcb | ||
|
|
a613bff664 | ||
|
|
8f875d2a9c | ||
|
|
fb60e0b389 | ||
|
|
2199fb2828 | ||
|
|
b7d052a6b3 | ||
|
|
b333816dc8 | ||
|
|
90160da042 | ||
|
|
6f2ebf8d2d | ||
|
|
a65635365e | ||
|
|
0eee6979b0 | ||
|
|
ec796e9f84 | ||
|
|
aaed2a6d86 | ||
|
|
0ea7c500e1 | ||
|
|
d90c884cf2 | ||
|
|
93700c01a8 | ||
|
|
1df5662d4f | ||
|
|
338eeba944 | ||
|
|
9651e4abb1 | ||
|
|
ed1f3400ac | ||
|
|
e9d9353fbb | ||
|
|
00adf4ca46 | ||
|
|
870fc27c72 | ||
|
|
bd38b7479f | ||
|
|
a567599eae | ||
|
|
5e6f9353c2 | ||
|
|
7de1179b7e | ||
|
|
ea7c80c3a1 | ||
|
|
f252f757f1 | ||
|
|
b27c63d0d7 | ||
|
|
bcce1a4472 | ||
|
|
9d9655512d | ||
|
|
7af75f31e4 | ||
|
|
83f02c377f | ||
|
|
ce4f74bc61 | ||
|
|
66651d0eed | ||
|
|
ec0e143361 | ||
|
|
250e0188f7 | ||
|
|
3123e472fc | ||
|
|
c12f7f1123 | ||
|
|
7e706518c5 | ||
|
|
d8ca573983 | ||
|
|
2225625cd8 | ||
|
|
89f0f01fd2 | ||
|
|
a36282d114 | ||
|
|
a8c92b7f9a | ||
|
|
f505dac8f3 | ||
|
|
8e4730a3bd | ||
|
|
b094bb344b | ||
|
|
2685aa049d | ||
|
|
b738d57433 | ||
|
|
539b870754 | ||
|
|
abeb0998ea | ||
|
|
82faddd985 | ||
|
|
b8084c270e | ||
|
|
22c7da420c | ||
|
|
45a3c89b0b | ||
|
|
8fc9e6d1ee | ||
|
|
7f0b286d8e | ||
|
|
4f664df087 | ||
|
|
dff48e3146 | ||
|
|
0fefa19f80 | ||
|
|
88e07ddbaa | ||
|
|
44a3ef0d70 | ||
|
|
5e793f171f | ||
|
|
e9bc63bee8 | ||
|
|
5636876e42 | ||
|
|
f2f7f549b0 | ||
|
|
1fc6e4f781 | ||
|
|
d641458fb4 | ||
|
|
517d44fa3c | ||
|
|
80ee0bf9a8 | ||
|
|
0934b70414 | ||
|
|
f74168e2c7 | ||
|
|
bf4a6e6cde | ||
|
|
0e09675779 | ||
|
|
40e92ca3d2 | ||
|
|
e776919bfd | ||
|
|
84bfeffe46 | ||
|
|
1360abbecb | ||
|
|
2a13accfe4 | ||
|
|
e26dac3993 | ||
|
|
1b7a43e82b | ||
|
|
141aca9e25 | ||
|
|
4f99eb6f07 | ||
|
|
81075bb000 | ||
|
|
33057faaab | ||
|
|
28b831c6a2 | ||
|
|
ef4d3d2659 | ||
|
|
09c0c18fce | ||
|
|
ff80150216 | ||
|
|
a8203baa50 | ||
|
|
aa33dc83d4 | ||
|
|
4155e2bb64 | ||
|
|
9660cafa99 | ||
|
|
c55b9cfe96 | ||
|
|
78ea96767e | ||
|
|
d11d7a8ffc | ||
|
|
bec789d2fb | ||
|
|
0cda3fca31 | ||
|
|
4f8980184f | ||
|
|
ffa096d988 | ||
|
|
6e1b1ed9d5 | ||
|
|
48526b815e | ||
|
|
1ded893e7b | ||
|
|
c61bd01c0f | ||
|
|
f0adcc90c7 | ||
|
|
2abb13bb4c | ||
|
|
8f69c4c820 | ||
|
|
5652c52d96 | ||
|
|
ff29cc192e | ||
|
|
7428d0e734 | ||
|
|
caad9e999c | ||
|
|
24cb225381 | ||
|
|
2e3195c5ee | ||
|
|
bfa039a612 | ||
|
|
49f8988912 | ||
|
|
7e214dbe3b | ||
|
|
42ad12d8d8 | ||
|
|
842e6ef788 | ||
|
|
56b87039c2 | ||
|
|
1767a0889d | ||
|
|
3036366de5 | ||
|
|
a8f1031e0f | ||
|
|
054107c3b9 | ||
|
|
c478b22ab9 | ||
|
|
2f712499ea | ||
|
|
39b9622cdb | ||
|
|
59a3a68357 | ||
|
|
d920dbd79e | ||
|
|
49dd390c6b | ||
|
|
d20b4bc334 | ||
|
|
a973807e3e | ||
|
|
431b5f4f30 | ||
|
|
636799e567 | ||
|
|
d003e3fa1b | ||
|
|
07edbe6619 | ||
|
|
dc4a5a05fe | ||
|
|
a8c86eb53d | ||
|
|
8ef9a62dc9 | ||
|
|
29c9ed4135 | ||
|
|
0426a4ca0d | ||
|
|
af7bbe3cca | ||
|
|
3c55153752 | ||
|
|
a82ff4bb4e | ||
|
|
73759e9611 | ||
|
|
71d6d08f5a | ||
|
|
2f2be201a7 | ||
|
|
49aaca7172 | ||
|
|
f29c64984b | ||
|
|
a50329edf3 | ||
|
|
15d163abf4 | ||
|
|
852a116c9d | ||
|
|
e9c18d0c01 | ||
|
|
5e6d4ecb1c | ||
|
|
97635311db | ||
|
|
3c7ddfd236 | ||
|
|
e0aa0eb4a3 | ||
|
|
8eba9d252f | ||
|
|
5966680a31 | ||
|
|
eb1563b1e7 | ||
|
|
84f52668b7 | ||
|
|
cc60095344 | ||
|
|
5a1f237b30 | ||
|
|
934a671344 | ||
|
|
b81ea8e8c7 | ||
|
|
817920940e | ||
|
|
e0f7ebbcba | ||
|
|
8ce18960fe | ||
|
|
6a879927a7 | ||
|
|
9c22114aa5 | ||
|
|
add9bef046 | ||
|
|
64b6cfea93 | ||
|
|
c8e76d5727 | ||
|
|
4fda0b6aaa | ||
|
|
4634237879 | ||
|
|
5cc91fef53 | ||
|
|
4a3c408ec5 | ||
|
|
23f0c55053 | ||
|
|
e1cf21328a | ||
|
|
7ce5f982b3 | ||
|
|
8fb9439eff | ||
|
|
700e348dbd | ||
|
|
91a86a8663 | ||
|
|
293270c03d | ||
|
|
af13113161 | ||
|
|
59b9dca5ec | ||
|
|
bc5d4e8efb | ||
|
|
91111a62f6 | ||
|
|
5525324620 | ||
|
|
29bd632e45 | ||
|
|
369e3c7269 | ||
|
|
383fed7b3d | ||
|
|
849dd8d436 | ||
|
|
6340a35cf2 | ||
|
|
1b2ea1d4bd | ||
|
|
9087872bc2 | ||
|
|
d914e06f42 | ||
|
|
e12af0c870 | ||
|
|
b0b9e2a7de | ||
|
|
5b88d8b9ca | ||
|
|
b7010b099f | ||
|
|
5484d39d90 | ||
|
|
181a2f8949 | ||
|
|
cee0f97850 | ||
|
|
fd91388b7a | ||
|
|
beae08a99d | ||
|
|
aef614aeae | ||
|
|
c87bc39ad8 | ||
|
|
d0b78f8e81 | ||
|
|
f126cc1d6c | ||
|
|
2d8c3427c4 | ||
|
|
c2acd926af | ||
|
|
b904d77497 | ||
|
|
c093b92c0b | ||
|
|
734b8bfd40 | ||
|
|
e337d1f116 | ||
|
|
8434ac1e2f | ||
|
|
349cdbf582 | ||
|
|
693aeae9e5 | ||
|
|
e4ea8e156d | ||
|
|
ca2b7dd674 | ||
|
|
8a744aa7fc | ||
|
|
7f2beb4d80 | ||
|
|
103c421b31 | ||
|
|
0f4238e9a7 | ||
|
|
c9508d2dac | ||
|
|
5d293b4318 | ||
|
|
3f4b814c0b | ||
|
|
aca71d8db1 | ||
|
|
87dbe3c945 | ||
|
|
c254fc946f | ||
|
|
5a4718eae8 | ||
|
|
935c52f291 | ||
|
|
04fe93d3b8 | ||
|
|
22f279e309 | ||
|
|
a0cff87e5f | ||
|
|
943d327975 | ||
|
|
6c4aced95e | ||
|
|
ad80fd2a91 | ||
|
|
43d50734a4 | ||
|
|
52d6057365 | ||
|
|
8dd5a5dd8a | ||
|
|
4799b33e0e | ||
|
|
d25ae7de81 | ||
|
|
83cbbbf0b7 | ||
|
|
4794578688 | ||
|
|
446da392d9 | ||
|
|
07cbd4cdbb | ||
|
|
f17cbdc111 | ||
|
|
28c5d277a0 | ||
|
|
939840b702 | ||
|
|
78485ae4e9 | ||
|
|
cc47a93872 | ||
|
|
af122eeb5c | ||
|
|
ec118968ed | ||
|
|
2de416fe81 | ||
|
|
d10a3b91e3 | ||
|
|
cdadf68f30 | ||
|
|
900a123141 | ||
|
|
4b2f9488ce | ||
|
|
02d7f45988 | ||
|
|
cc34dbb88e | ||
|
|
5e9d99083c | ||
|
|
295bf74a1b | ||
|
|
7928437dc6 | ||
|
|
83283b7b6b | ||
|
|
09a289b4c4 | ||
|
|
6b940b9d01 | ||
|
|
cb492e0183 | ||
|
|
92799d57ae | ||
|
|
066100f218 | ||
|
|
cd4dd44004 | ||
|
|
40a2fdb7fd | ||
|
|
b60cf11668 | ||
|
|
0fa617c580 | ||
|
|
1cfa08612e | ||
|
|
2cebef9d4b | ||
|
|
c75313cdf4 | ||
|
|
ae1eaac037 | ||
|
|
01465a898a | ||
|
|
d6a7917ffd | ||
|
|
d9946088ab | ||
|
|
a2ad6a1037 | ||
|
|
34e240b40a | ||
|
|
ca1f33ade6 | ||
|
|
d8bddb1c21 | ||
|
|
64bab14483 | ||
|
|
2d1830f4fc | ||
|
|
ab00f2bd42 | ||
|
|
6d9505a4c0 | ||
|
|
40e3cb8ce5 | ||
|
|
4783ec6696 | ||
|
|
3bd746fe91 | ||
|
|
a91a82eecc | ||
|
|
a9564583cb | ||
|
|
f988c8879e | ||
|
|
825cad81a2 | ||
|
|
b9c0ea065a | ||
|
|
50ef633573 | ||
|
|
10202df7d7 | ||
|
|
a7815b41db | ||
|
|
cf467eb868 | ||
|
|
a3be19154f | ||
|
|
a7c19c689c | ||
|
|
1c78e3aac0 | ||
|
|
b9cc3d77b3 | ||
|
|
1feb81adf3 | ||
|
|
fd937758e6 | ||
|
|
fcc3d674c2 | ||
|
|
ce74264a01 |
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Question about using GAM
|
||||
about: Help with using GAM or running it for the first time
|
||||
title: Please use the GAM discussion group
|
||||
labels: invalid
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
If you need help with GAM, please do not file an issue here, it will be closed and ignored.
|
||||
|
||||
Please post your question to the GAM discussion group where other admins are ready and willing to help:
|
||||
|
||||
https://groups.google.com/g/google-apps-manager
|
||||
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
The issue tracker is for reporting product deficiencies. "How do I?" questions should be posted to the discussion forum at https://groups.google.com/group/google-apps-manager. When in doubt, start at the discussion forum and return here only when instructed to do so.
|
||||
|
||||
Please confirm the following:
|
||||
* I have upgraded to the latest GAM release from https://git.io/gamreleases and I still have this issue.
|
||||
* I am typing the command as described in the GAM Wiki at https://github.com/jay0lee/gam/wiki
|
||||
|
||||
Full steps to reproduce the issue:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Expected outcome (what are you trying to do?):
|
||||
|
||||
Actual outcome (what errors or bad behavior do you see instead?):
|
||||
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for GAM
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
BIN
.github/actions/creds.tar.gpg
vendored
Normal file
BIN
.github/actions/creds.tar.gpg
vendored
Normal file
Binary file not shown.
17
.github/actions/decrypt.sh
vendored
Normal file
17
.github/actions/decrypt.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
gpgfile="$1"
|
||||
echo "source file is ${gpgfile}"
|
||||
credsfile="$2"
|
||||
echo "target file is ${credsfile}"
|
||||
if [ -z ${PASSCODE+x} ]; then
|
||||
echo "PASSCODE is unset";
|
||||
else
|
||||
echo "PASSCODE is set";
|
||||
fi
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="${PASSCODE}" \
|
||||
--output "${credsfile}" "${gpgfile}"
|
||||
|
||||
tar xvvf "${credsfile}" --directory "${gampath}"
|
||||
ls -l "${gampath}"
|
||||
@@ -1,3 +1,6 @@
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
export python="python"
|
||||
export pip="pip"
|
||||
@@ -5,7 +8,7 @@ if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
echo "running tests with this version"
|
||||
else
|
||||
export whereibelong=$(pwd)
|
||||
echo "We are running on Ubuntu $TRAVIS_DIST $PLATFORM"
|
||||
echo "We are running on $ImageOS $ImageVersion"
|
||||
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
cpucount=$(nproc --all)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
@@ -32,8 +35,6 @@ else
|
||||
rm -rf python
|
||||
mkdir ssl
|
||||
mkdir python
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
echo "RUNNING: apt upgrade..."
|
||||
sudo apt-mark hold openssh-server
|
||||
sudo apt-get --yes upgrade
|
||||
@@ -93,7 +94,7 @@ else
|
||||
python=~/python/bin/python3
|
||||
pip=~/python/bin/pip3
|
||||
|
||||
if ([ "${TRAVIS_DIST}" == "trusty" ] || [ "${TRAVIS_DIST}" == "xenial" ]) && [ "${PLATFORM}" == "x86_64" ]; then
|
||||
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||
echo "Installing deps for StaticX..."
|
||||
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
|
||||
echo "Downloading PatchELF $PATCHELF_VERSION"
|
||||
@@ -108,11 +109,5 @@ else
|
||||
$pip install staticx
|
||||
fi
|
||||
|
||||
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT
|
||||
|
||||
cd $whereibelong
|
||||
fi
|
||||
|
||||
echo "Upgrading pip packages..."
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
34
.github/actions/linux-install.sh
vendored
Executable file
34
.github/actions/linux-install.sh
vendored
Executable file
@@ -0,0 +1,34 @@
|
||||
export distpath="dist/"
|
||||
export gampath="${distpath}gam"
|
||||
rm -rf $gampath
|
||||
#mkdir -p $gampath
|
||||
#export gampath=$(readlink -e $gampath)
|
||||
$pip install wheel
|
||||
$python -OO -m PyInstaller --clean --noupx --strip --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-glibc${this_glibc_ver}.tar.xz"
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
# tar will cd to dist and tar up gam/
|
||||
tar -C ${distpath} --create --file $GAM_ARCHIVE --xz gam
|
||||
echo "PyInstaller GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
||||
$python -OO -m staticx $gam $gam-staticx
|
||||
#strip $gam-staticx
|
||||
rm $gampath/gam
|
||||
mv $gam-staticx $gam
|
||||
chmod 755 $gam
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
|
||||
echo "Legacy StaticX GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
fi
|
||||
echo "GAM packages:"
|
||||
ls -l gam-*.tar.xz
|
||||
132
.github/actions/macos-before-install.sh
vendored
Executable file
132
.github/actions/macos-before-install.sh
vendored
Executable file
@@ -0,0 +1,132 @@
|
||||
mypath=$HOME
|
||||
whereibelong=$(pwd)
|
||||
cpucount=$(sysctl -n hw.ncpu)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
|
||||
#echo "Brew installing xz..."
|
||||
#brew install xz > /dev/null
|
||||
|
||||
#brew upgrade
|
||||
|
||||
brew install coreutils
|
||||
brew install bash
|
||||
|
||||
# prefer standard GNU tools like date over MacOS defaults
|
||||
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
|
||||
|
||||
date --version
|
||||
gdate --version
|
||||
bash --version
|
||||
|
||||
cd ~
|
||||
|
||||
# Use official Python.org version of Python which is backwards compatible
|
||||
# with older MacOS versions
|
||||
export pyfile=python-$BUILD_PYTHON_VERSION-macos11.pkg
|
||||
|
||||
wget https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$pyfile
|
||||
echo "installing Python $BUILD_PYTHON_VERSION..."
|
||||
sudo installer -pkg ./$pyfile -target /
|
||||
|
||||
# This fixes https://github.com/pyinstaller/pyinstaller/issues/5062
|
||||
codesign --remove-signature /Library/Frameworks/Python.framework/Versions/3.9/Python
|
||||
|
||||
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
|
||||
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
|
||||
#fi
|
||||
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
|
||||
|
||||
#brew install openssl@1.1
|
||||
#brew upgrade python
|
||||
|
||||
#export python=python3
|
||||
#export pip=pip3
|
||||
|
||||
#echo "Python location:"
|
||||
#which $python
|
||||
|
||||
cd ~
|
||||
|
||||
#export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
#export openssl=~/ssl/bin/openssl
|
||||
#export python=~/python/bin/python3
|
||||
#export pip=~/python/bin/pip3
|
||||
|
||||
export python=/usr/local/bin/python3
|
||||
export pip=/usr/local/bin/pip3
|
||||
SSLVER=$($openssl version)
|
||||
SSLRESULT=$?
|
||||
PYVER=$($python -V)
|
||||
PYRESULT=$?
|
||||
|
||||
brew install swig
|
||||
$pip install pyscard
|
||||
|
||||
#wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
|
||||
|
||||
#if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
|
||||
# echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
|
||||
# if [ $SSLRESULT -ne 0 ]; then
|
||||
# echo "sslresult -ne 0"
|
||||
# fi
|
||||
# if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
|
||||
# echo "sslver not equal to..."
|
||||
# fi
|
||||
# if [ $PYRESULT -ne 0 ]; then
|
||||
# echo "pyresult -ne 0"
|
||||
# fi
|
||||
# if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
|
||||
# echo "pyver not equal to..."
|
||||
# fi
|
||||
|
||||
# Start clean
|
||||
# rm -rf python
|
||||
# rm -rf ssl
|
||||
# mkdir python
|
||||
# mkdir ssl
|
||||
|
||||
# Compile latest OpenSSL
|
||||
# wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
# echo "Extracting OpenSSL..."
|
||||
# tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
# cd openssl-$BUILD_OPENSSL_VERSION
|
||||
# echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
# ./config shared --prefix=$HOME/ssl
|
||||
# echo "Running make for OpenSSL..."
|
||||
# make -j$cpucount -s
|
||||
# echo "Running make install for OpenSSL..."
|
||||
# make install > /dev/null
|
||||
# cd ~
|
||||
|
||||
# Compile latest Python
|
||||
# echo "Downloading Python $BUILD_PYTHON_VERSION..."
|
||||
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
# echo "Extracting Python..."
|
||||
# tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
# cd Python-$BUILD_PYTHON_VERSION
|
||||
# echo "Compiling Python $BUILD_PYTHON_VERSION..."
|
||||
# safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
|
||||
# unsafe_flags="--enable-optimizations --with-lto"
|
||||
# if [ ! -e Makefile ]; then
|
||||
# echo "running configure with safe and unsafe"
|
||||
# ./configure $safe_flags $unsafe_flags > /dev/null
|
||||
# fi
|
||||
# make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
|
||||
# RESULT=$?
|
||||
# echo "First make exited with $RESULT"
|
||||
# if [ $RESULT != 0 ]; then
|
||||
# echo "Trying Python compile again without unsafe flags..."
|
||||
# make clean
|
||||
# ./configure $safe_flags > /dev/null
|
||||
# make -j$cpucount -s
|
||||
# echo "Sticking with safe Python for now..."
|
||||
# fi
|
||||
# echo "Installing Python..."
|
||||
# make install > /dev/null
|
||||
# cd ~
|
||||
#fi
|
||||
|
||||
$python -V
|
||||
|
||||
cd $whereibelong
|
||||
|
||||
19
.github/actions/macos-install.sh
vendored
Executable file
19
.github/actions/macos-install.sh
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
echo "MacOS Version Info According to Python:"
|
||||
macver=$(python -c "import platform; print(platform.mac_ver()[0])")
|
||||
echo $macver
|
||||
echo "Xcode version:"
|
||||
xcodebuild -version
|
||||
export distpath="dist/"
|
||||
export gampath="${distpath}gam"
|
||||
rm -rf $gampath
|
||||
export specfile="gam.spec"
|
||||
$python -OO -m PyInstaller --clean --noupx --strip --distpath "${gampath}" --target-architecture $PLATFORM "${specfile}"
|
||||
export gam="${gampath}/gam"
|
||||
$gam version extended
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE "${gampath}"
|
||||
cp GamCommands.txt "${gampath}"
|
||||
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}.tar.xz"
|
||||
rm "${gampath}/lastupdatecheck.txt"
|
||||
# tar will cd to dist/ and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
57
.github/actions/windows-before-install.sh
vendored
Executable file
57
.github/actions/windows-before-install.sh
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export BITS="64"
|
||||
export PYTHONFILE_BITS="-amd64"
|
||||
export OPENSSL_BITS="-x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export BITS="32"
|
||||
export PYTHONFILE_BITS=""
|
||||
export OPENSSL_BITS=""
|
||||
export CHOCOPTIONS="--forcex86"
|
||||
fi
|
||||
echo "This is a ${BITS}-bit build for ${PLATFORM}"
|
||||
|
||||
export mypath=$(pwd)
|
||||
cd ~
|
||||
|
||||
export python="c:\python\python.exe"
|
||||
export pip="c:\python\scripts\pip.exe"
|
||||
|
||||
# pyscard needs swig, keep these two together
|
||||
choco install $CHOCOPTIONS swig
|
||||
$pip install pyscard
|
||||
|
||||
# Python
|
||||
#echo "Installing Python..."
|
||||
#export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
|
||||
#if [ ! -e $python_file ]; then
|
||||
# echo "Downloading $python_file..."
|
||||
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
|
||||
#fi
|
||||
#until ./${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python; do echo "trying python again..."; done
|
||||
#export python=/c/python/python.exe
|
||||
#export pip=/c/python/scripts/pip.exe
|
||||
#until [ -f $python ]; do sleep 1; done
|
||||
#export PATH=$PATH:/c/python/scripts
|
||||
|
||||
# OpenSSL
|
||||
#echo "Installing OpenSSL..."
|
||||
#export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
|
||||
#if [ ! -e $exefile ]; then
|
||||
# echo "Downloading $exefile..."
|
||||
# curl -O https://slproweb.com/download/$exefile
|
||||
#fi
|
||||
#until ./${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl; do echo "trying openssl again..."; done
|
||||
#until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
|
||||
#until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
|
||||
#if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
# cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
|
||||
# cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
|
||||
#fi
|
||||
|
||||
cd $mypath
|
||||
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
$python setup.py install
|
||||
echo "cd to $mypath"
|
||||
cd $mypath
|
||||
@@ -1,21 +1,27 @@
|
||||
cd src
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export WIX_BITS="x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export WIX_BITS="x86"
|
||||
fi
|
||||
echo "compiling GAM with pyinstaller..."
|
||||
export gampath="dist/gam"
|
||||
export distpath="dist/"
|
||||
export gampath="${distpath}gam"
|
||||
rm -rf $gampath
|
||||
mkdir -p $gampath
|
||||
export gampath=$(readlink -e $gampath)
|
||||
pyinstaller --clean --noupx -F --distpath $gampath gam.spec
|
||||
/c/python/scripts/pyinstaller --clean --noupx --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
echo "running compiled GAM..."
|
||||
$gam version
|
||||
export GAMVERSION=`$gam version simple`
|
||||
export GAMVERSION=$($gam version simple)
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
cp gam-setup.bat $gampath
|
||||
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM.zip
|
||||
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE $gampath -xr!.svn
|
||||
|
||||
cwd=$(pwd)
|
||||
cd "${distpath}"
|
||||
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam -xr!.svn
|
||||
mv "${GAM_ARCHIVE}" "${cwd}"
|
||||
cd "${cwd}"
|
||||
echo "Running WIX candle $WIX_BITS..."
|
||||
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/candle.exe -arch $WIX_BITS gam.wxs
|
||||
echo "Done with WIX candle..."
|
||||
373
.github/workflows/build.yml
vendored
Normal file
373
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
name: Build and test GAM
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '37 22 * * *'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: src
|
||||
|
||||
env:
|
||||
BUILD_PYTHON_VERSION: "3.10.0"
|
||||
MIN_PYTHON_VERSION: "3.10.0"
|
||||
BUILD_OPENSSL_VERSION: "1.1.1l"
|
||||
MIN_OPENSSL_VERSION: "1.1.1l"
|
||||
PATCHELF_VERSION: "0.13"
|
||||
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
|
||||
PYINSTALLER_VERSION: "1e6a8d53f150cf24b574c32085f3745cbd2afaa6"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-18.04
|
||||
jid: 1
|
||||
goal: "build"
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
jid: 2
|
||||
goal: "build"
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: macos-11.0
|
||||
jid: 3
|
||||
goal: "build"
|
||||
gamos: "macos"
|
||||
platform: "universal2"
|
||||
- os: windows-2019
|
||||
jid: 4
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
pyarch: "x64"
|
||||
platform: "x86_64"
|
||||
- os: windows-2019
|
||||
jid: 5
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
platform: "x86"
|
||||
pyarch: "x86"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.6"
|
||||
jid: 6
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.7"
|
||||
jid: 7
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.8"
|
||||
jid: 8
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: test
|
||||
python: "3.10.0"
|
||||
jid: 9
|
||||
gamos: linux
|
||||
platform: x86_64
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
if: matrix.goal != 'test'
|
||||
with:
|
||||
path: |
|
||||
~/python
|
||||
~/ssl
|
||||
key: ${{ matrix.os }}-${{ matrix.jid }}-20211014
|
||||
|
||||
- name: Set env variables
|
||||
env:
|
||||
GAMOS: ${{ matrix.gamos }}
|
||||
GOAL: ${{ matrix.goal }}
|
||||
JID: ${{ matrix.jid }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
run: |
|
||||
echo "GAMOS=${GAMOS}" >> $GITHUB_ENV
|
||||
echo "GOAL=${GOAL}" >> $GITHUB_ENV
|
||||
echo "JID=${JID}" >> $GITHUB_ENV
|
||||
echo "PLATFORM=${PLATFORM}" >> $GITHUB_ENV
|
||||
uname -a
|
||||
|
||||
- name: Use pre-compiled Python for testing
|
||||
if: matrix.python != ''
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
architecture: ${{ matrix.pyarch }}
|
||||
|
||||
- name: Install Python on Windows
|
||||
if: matrix.os == 'windows-2019'
|
||||
run: |
|
||||
if ( ${Env:PLATFORM} -eq "x86_64" )
|
||||
{
|
||||
Set-Variable -name py_arch -value "-amd64"
|
||||
}
|
||||
else
|
||||
{
|
||||
Set-Variable -name py_arch -value ""
|
||||
}
|
||||
Write-Output "py_arch: $py_arch"
|
||||
Set-Variable -name python_file -value "python-${Env:BUILD_PYTHON_VERSION}${py_arch}.exe"
|
||||
Write-Output "python_file: $python_file"
|
||||
Set-Variable -name python_url -value "https://www.python.org/ftp/python/${Env:BUILD_PYTHON_VERSION}/${python_file}"
|
||||
Write-Output "python_url: $python_url"
|
||||
Invoke-WebRequest -Uri $python_url -OutFile $python_file
|
||||
Start-Process -wait -FilePath $python_file -ArgumentList "/quiet","InstallAllUsers=0","TargetDir=c:\\python","AssociateFiles=1","PrependPath=1"
|
||||
shell: pwsh
|
||||
|
||||
- name: Set env variables for pre-compiled Python
|
||||
if: matrix.goal == 'test'
|
||||
run: |
|
||||
export python=$(which python3)
|
||||
export pip=$(which pip3)
|
||||
export gam="${python} -m gam"
|
||||
export gampath="$(readlink -e .)"
|
||||
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath"
|
||||
echo "python=${python}" >> $GITHUB_ENV
|
||||
echo "pip=${pip}" >> $GITHUB_ENV
|
||||
echo "gam=${gam}" >> $GITHUB_ENV
|
||||
echo "gampath=${gampath}" >> $GITHUB_ENV
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
$pip install --upgrade pip
|
||||
|
||||
- name: Build and install Python, OpenSSL and PyInstaller
|
||||
if: matrix.goal != 'test' && steps.cache-primes.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set +e
|
||||
source ../.github/actions/${GAMOS}-before-install.sh
|
||||
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
|
||||
echo "python=$python" >> $GITHUB_ENV
|
||||
echo "pip=$pip" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
|
||||
$pip install --upgrade pip
|
||||
$pip install wheel
|
||||
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
|
||||
echo "Downloading ${url}"
|
||||
curl -o pyinstaller.tar.gz --compressed "${url}"
|
||||
tar xf pyinstaller.tar.gz
|
||||
cd "pyinstaller-${PYINSTALLER_VERSION}/"
|
||||
if [ $GAMOS == "windows" ]; then
|
||||
# remove pre-compiled bootloaders so we fail if bootloader compile fails
|
||||
rm -rf PyInstaller/bootloader/*bit
|
||||
cd bootloader
|
||||
if [ "${PLATFORM}" == "x86" ]; then
|
||||
TARGETARCH="--target-arch=32bit"
|
||||
else
|
||||
TARGETARCH=""
|
||||
fi
|
||||
$python ./waf all $TARGETARCH
|
||||
cd ..
|
||||
fi
|
||||
$pip install .
|
||||
#$python setup.py install
|
||||
#$pip install pyinstaller
|
||||
|
||||
- name: Install pip requirements
|
||||
if: matrix.os != 'self-hosted'
|
||||
run: |
|
||||
set +e
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall
|
||||
|
||||
$pip install --upgrade -r requirements.txt
|
||||
|
||||
- name: Build GAM with PyInstaller
|
||||
if: matrix.goal != 'test'
|
||||
run: |
|
||||
set +e
|
||||
source ../.github/actions/${GAMOS}-install.sh
|
||||
ls -alRF $gampath
|
||||
echo "gampath=$gampath" >> $GITHUB_ENV
|
||||
echo "gam=$gam" >> $GITHUB_ENV
|
||||
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}\nGAMVERSION: ${GAMVERSION}"
|
||||
|
||||
- name: Basic Tests all jobs
|
||||
run: |
|
||||
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath\n"
|
||||
$python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
|
||||
touch "${gampath}/nobrowser.txt"
|
||||
$gam version extended
|
||||
export GAMVERSION=$($gam version simple)
|
||||
echo "GAM Version ${GAMVERSION}"
|
||||
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Basic Tests build jobs only
|
||||
if: matrix.goal != 'test'
|
||||
run: |
|
||||
$pip install packaging
|
||||
export vline=$($gam version | grep "Python ")
|
||||
export python_line=($vline)
|
||||
export this_python=${python_line[1]}
|
||||
$python tools/a_atleast_b.py "${this_python}" "${MIN_PYTHON_VERSION}"
|
||||
export vline=$($gam version extended | grep "OpenSSL ")
|
||||
export openssl_line=($vline)
|
||||
export this_openssl="${openssl_line[1]}"
|
||||
$python tools/a_atleast_b.py "${this_openssl}" "${MIN_OPENSSL_VERSION}"
|
||||
|
||||
|
||||
- name: Live API tests push only
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
env: # Or as an environment variable
|
||||
PASSCODE: ${{ secrets.PASSCODE }}
|
||||
run: |
|
||||
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
|
||||
export OAUTHFILE="oauth2.txt-gam-gha-${JID}"
|
||||
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
|
||||
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
|
||||
echo "gam_user=${gam_user}" >> $GITHUB_ENV
|
||||
$gam oauth info
|
||||
$gam info domain
|
||||
$gam oauth refresh
|
||||
$gam info user
|
||||
#$gam info user $gam_user grouptree
|
||||
export tstamp=$(date +%s%3N)
|
||||
export newbase=gha-test-$JID-$tstamp
|
||||
export newuser=$newbase@pdl.jaylee.us
|
||||
export newgroup=$newbase-group@pdl.jaylee.us
|
||||
export newalias=$newbase-alias@pdl.jaylee.us
|
||||
export newbuilding=$newbase-building
|
||||
export newresource=$newbase-resource
|
||||
export GAM_THREADS=5
|
||||
echo email > sample.csv;
|
||||
for i in {01..10}; do
|
||||
echo "${newbase}-bulkuser-$i" >> sample.csv;
|
||||
done
|
||||
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID
|
||||
$gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "GHA test message"
|
||||
$gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"
|
||||
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
|
||||
$gam update cigroup $newgroup memberrestriction 'member.type == 1 || member.customer_id == groupCustomerId()'
|
||||
$gam info cigroup $newgroup
|
||||
$gam user $newuser add license gsuitebusiness
|
||||
$gam update group $newgroup add owner $gam_user
|
||||
$gam update group $newgroup add member $newuser
|
||||
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
|
||||
$gam csv sample.csv gam user ~email add license gsuitebusiness
|
||||
$gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "GHA test message"
|
||||
$gam csv sample.csv gam update group $newgroup add member ~email
|
||||
$gam info group $newgroup
|
||||
$gam info cigroup $newgroup membertree
|
||||
$gam user $gam_user check serviceaccount
|
||||
# confirm mailbox is provisoned before continuing
|
||||
$gam user $newuser waitformailbox
|
||||
$gam user $newuser imap on
|
||||
$gam user $newuser show imap
|
||||
$gam user $newuser show delegates
|
||||
#$gam user $newuser add contactdelegate "${newbase}-bulkuser-01"
|
||||
#$gam user $newuser print contactdelegates
|
||||
export biohazard=$(echo -e '\xe2\x98\xa3')
|
||||
$gam user $newuser label "$biohazard unicode biohazard $biohazard"
|
||||
$gam user $newuser show labels
|
||||
$gam user $newuser show labels > labels.txt
|
||||
$gam user $gam_user importemail subject "GHA import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED
|
||||
$gam user $gam_user insertemail subject "GHA insert $newbase" file gam.py labels INBOX,UNREAD # yep body is gam code
|
||||
$gam user $gam_user sendemail subject "GHA send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us
|
||||
$gam user $gam_user draftemail subject "GHA draft $newbase" message "Draft message test"
|
||||
$gam csvfile sample.csv:email waitformailbox
|
||||
$gam user $newuser delegate to "${newbase}-bulkuser-01"
|
||||
$gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit
|
||||
$gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit
|
||||
$gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit
|
||||
$gam user $newuser delete label --ALL_LABELS--
|
||||
$gam create feature name Whiteboard-$newbase
|
||||
$gam create feature name VC-$newbase
|
||||
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
|
||||
$gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room
|
||||
$gam info resource $newresource
|
||||
$gam user $newuser show filelist
|
||||
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id # clear ACLs
|
||||
$gam calendar $gam_user update read domain
|
||||
$gam calendar $gam_user update freebusy default
|
||||
$gam calendar $gam_user add editor $newuser
|
||||
$gam calendar $gam_user showacl
|
||||
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id
|
||||
$gam calendar $gam_user addevent summary "GHA test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all
|
||||
$gam calendar $gam_user printevents after -0d
|
||||
matterid=uid:$($gam create vaultmatter name "GHA matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3)
|
||||
$gam create vaulthold matter $matterid name "GHA hold $newbase" corpus mail accounts $newuser
|
||||
$gam print vaultmatters matterstate open
|
||||
$gam print vaultholds matter $matterid
|
||||
$gam print vaultcount matter $matterid corpus mail everyone todrive
|
||||
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser
|
||||
$gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~
|
||||
$gam csv sample.csv gam user ~email add calendar id:$newresource
|
||||
$gam delete resource $newresource
|
||||
$gam delete feature Whiteboard-$newbase
|
||||
$gam delete feature VC-$newbase
|
||||
$gam delete building $newbuilding
|
||||
$gam delete group $newgroup
|
||||
$gam create alias $newalias user $newuser
|
||||
$gam whatis $newuser
|
||||
$gam user $gam_user show tokens
|
||||
$gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~
|
||||
$gam delete hold "GHA hold $newbase" matter $matterid
|
||||
$gam update matter $matterid action close
|
||||
$gam update matter $matterid action delete
|
||||
$gam delete user $newuser
|
||||
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
|
||||
$gam print mobile
|
||||
$gam print devices
|
||||
$gam print browsers
|
||||
export sn="$JID$JID$JID$JID-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"
|
||||
$gam create device serialnumber $sn devicetype android
|
||||
$gam print cros allfields nolists
|
||||
$gam report usageparameters customer
|
||||
$gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins
|
||||
$gam report customer todrive
|
||||
$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive
|
||||
$gam report admin start -3d todrive
|
||||
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
|
||||
$gam print userinvitations
|
||||
$gam print userinvitations | $gam csv - gam send userinvitation ~name
|
||||
export CUSTOMER_ID="C01wfv983"
|
||||
export GA_DOMAIN="pdl.jaylee.us"
|
||||
touch $gampath/enabledasa.txt
|
||||
echo "printer model count:"
|
||||
$gam print printermodels | wc -l
|
||||
#$gam print printers
|
||||
#$gam create printer displayname "${newbase}" uri ipp://localhost:631 driverless description "made by $(date)"
|
||||
rm $gampath/enabledasa.txt
|
||||
|
||||
- name: Upload to Google Drive, build only.
|
||||
if: github.event_name == 'push' && matrix.goal != 'test'
|
||||
run: |
|
||||
ls gam-$GAMVERSION-*
|
||||
for gamfile in gam-$GAMVERSION-*; do
|
||||
echo "Uploading file ${gamfile} to Google Drive..."
|
||||
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${GITHUB_SHA:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly)
|
||||
echo "file uploaded as ${fileid}, setting ACL..."
|
||||
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink
|
||||
done
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: github.event_name == 'push' && matrix.goal != 'test'
|
||||
with:
|
||||
name: gam-binaries
|
||||
path: |
|
||||
src/*.tar.xz
|
||||
src/*.zip
|
||||
src/*.msi
|
||||
253
.travis.yml
253
.travis.yml
@@ -1,253 +0,0 @@
|
||||
if: tag IS blank
|
||||
os: linux
|
||||
language: python
|
||||
dist: focal
|
||||
|
||||
env:
|
||||
global:
|
||||
- BUILD_PYTHON_VERSION=3.9.0
|
||||
- MIN_PYTHON_VERSION=3.9.0
|
||||
- BUILD_OPENSSL_VERSION=1.1.1h
|
||||
- MIN_OPENSSL_VERSION=1.1.1h
|
||||
- PATCHELF_VERSION=0.11
|
||||
- PYINSTALLER_COMMIT=7aa19839c171d898b5cf957739083c4bb901607e
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/python
|
||||
- $HOME/ssl
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: linux
|
||||
name: "Linux 64-bit Focal"
|
||||
dist: focal
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux 64-bit Bionic"
|
||||
dist: bionic
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux 64-bit Xenial"
|
||||
dist: xenial
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux ARM64 Focal"
|
||||
dist: focal
|
||||
language: shell
|
||||
arch: arm64
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
dist: bionic
|
||||
arch: arm64
|
||||
name: "Linux ARM64 Bionic"
|
||||
language: shell
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
dist: xenial
|
||||
arch: arm64
|
||||
name: "Linux ARM64 Xenial"
|
||||
language: shell
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
name: "Python 3.6 Source Testing"
|
||||
language: python
|
||||
python: 3.6
|
||||
- os: linux
|
||||
name: "Python 3.7 Source Testing"
|
||||
language: python
|
||||
python: 3.7
|
||||
- os: linux
|
||||
name: "Python 3.8 Source Testing"
|
||||
language: python
|
||||
python: 3.8
|
||||
- os: linux
|
||||
name: "Python 3.10 dev Source Testing"
|
||||
language: python
|
||||
python: 3.10-dev
|
||||
# - os: linux
|
||||
# name: "Python trunk nightly Source Testing"
|
||||
# language: python
|
||||
# python: nightly
|
||||
# - os: linux
|
||||
# name: "Python PyPi Source Testing"
|
||||
# language: python
|
||||
# python: pypy3
|
||||
- os: osx
|
||||
name: "MacOS 10.13"
|
||||
language: generic
|
||||
osx_image: xcode10.1
|
||||
- os: osx
|
||||
name: "MacOS 10.14"
|
||||
language: generic
|
||||
osx_image: xcode11.3
|
||||
- os: osx
|
||||
name: "MacOS 10.15"
|
||||
language: generic
|
||||
osx_image: xcode11.7
|
||||
- os: osx
|
||||
name: "MacOS 10.15 Universal Testing"
|
||||
language: generic
|
||||
osx_image: xcode12u
|
||||
- os: windows
|
||||
name: "Windows 64-bit"
|
||||
language: shell
|
||||
- os: windows
|
||||
name: "Windows 32-bit"
|
||||
language: shell
|
||||
|
||||
before_install:
|
||||
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
|
||||
export GAMOS="macos";
|
||||
else
|
||||
export GAMOS="${TRAVIS_OS_NAME}";
|
||||
fi
|
||||
- if [ "${TRAVIS_JOB_NAME}" == "Windows 32-bit" ]; then
|
||||
export PLATFORM="x86";
|
||||
elif [ "${TRAVIS_CPU_ARCH}" == "amd64" ]; then
|
||||
export PLATFORM="x86_64";
|
||||
else
|
||||
export PLATFORM="${TRAVIS_CPU_ARCH}";
|
||||
fi
|
||||
- source src/travis/${TRAVIS_OS_NAME}-before-install.sh
|
||||
|
||||
install:
|
||||
- source src/travis/${TRAVIS_OS_NAME}-install.sh
|
||||
|
||||
script:
|
||||
# Discover and run all Python unit tests. Buffer output so that it's not sent to the build log.
|
||||
- $python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
|
||||
- touch $gampath/nobrowser.txt
|
||||
- $gam version extended
|
||||
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
|
||||
# determine which Python version GAM is built with and ensure it's at least build version from above.
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; $python tools/a_atleast_b.py $this_python $MIN_PYTHON_VERSION; fi
|
||||
# determine which OpenSSL version GAM is built with and ensure it's at least build version from above.
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; $python tools/a_atleast_b.py $this_openssl $MIN_OPENSSL_VERSION; fi
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then $gam version extended | grep TLSv1\.[23]; fi # Builds should default TLS 1.2 or 1.3 to Google
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then GAM_TLS_MIN_VERSION=TLSv1_2 $gam version extended location tls-v1-0.badssl.com:1010; [[ $? == 3 ]]; fi # expect fail since server doesn't support our TLS version
|
||||
- export jid="$(cut -d'.' -f2 <<<"$TRAVIS_JOB_NUMBER")"
|
||||
- if [ "$TRAVIS_EVENT_TYPE" != "pull_request" ]; then export e2e=true; fi
|
||||
- if [ "$e2e" = true ]; then export gam_user=gam-travis-$jid@pdl.jaylee.us; fi
|
||||
- if [ "$e2e" = true ]; then openssl aes-256-cbc -K $encrypted_6294a53f809d_key -iv $encrypted_6294a53f809d_iv -in travis/creds.tar.enc -out travis/creds.tar -d; fi
|
||||
- if [ "$e2e" = true ]; then tar xvf travis/creds.tar -C $gampath; fi
|
||||
- if [ "$e2e" = true ]; then export OAUTHFILE=oauth2.txt-gam-travis-$jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam oauth info; fi
|
||||
- if [ "$e2e" = true ]; then $gam info domain; fi
|
||||
- if [ "$e2e" = true ]; then $gam oauth refresh; fi
|
||||
- if [ "$e2e" = true ]; then $gam info user; fi
|
||||
- if [ "$e2e" = true ]; then export tstamp=$(date +%s%3N);
|
||||
export newbase=travis-test-$jid-$tstamp;
|
||||
export newuser=$newbase@pdl.jaylee.us;
|
||||
export newgroup=$newbase-group@pdl.jaylee.us;
|
||||
export newalias=$newbase-alias@pdl.jaylee.us;
|
||||
export newbuilding=$newbase-building;
|
||||
export newresource=$newbase-resource;
|
||||
export GAM_THREADS=5; fi
|
||||
- if [ "$e2e" = true ]; then echo email > sample.csv;
|
||||
for i in {01..20};
|
||||
do echo $newbase-bulkuser-$i >> sample.csv;
|
||||
done; fi
|
||||
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com travis.jid $jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
|
||||
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
|
||||
- if [ "$e2e" = true ]; then $gam update group $newgroup add member $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam create user ~~email~~ firstname "Travis Bulk" lastname ~~email~~ travis.jid $jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add license gsuitebusiness; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "Travis test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update group $newgroup add member ~email; fi
|
||||
- if [ "$e2e" = true ]; then $gam info group $newgroup; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user check serviceaccount; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser imap on; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show imap; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $newuser delegate to ~email; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show delegates; fi
|
||||
- if [ "$e2d" = true ]; then export biohazard=$(echo -e '\xe2\x98\xa3'); fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser label "$biohazard unicode biohazard $biohazard"; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show labels; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show labels > labels.txt; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user importemail subject "Travis import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user insertemail subject "Travis insert $newbase" file gam.py labels INBOX,UNREAD; fi # yep body is gam code
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail subject "Travis send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user draftemail subject "Travis draft $newbase" message "Draft message test"; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser delete label --ALL_LABELS--; fi
|
||||
- if [ "$e2e" = true ]; then $gam create feature name Whiteboard-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam create feature name VC-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."; fi
|
||||
- if [ "$e2e" = true ]; then $gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room; fi
|
||||
- if [ "$e2e" = true ]; then $gam info resource $newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show filelist; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi # clear ACLs
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user update read domain; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user update freebusy default; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user add editor $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user showacl; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user addevent summary "Travis test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printevents after -0d; fi
|
||||
- if [ "$e2e" = true ]; then matterid=uid:$($gam create vaultmatter name "Travis matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3); fi
|
||||
- if [ "$e2e" = true ]; then $gam create vaulthold matter $matterid name "Travis hold $newbase" corpus mail accounts $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print vaultmatters matterstate open; fi
|
||||
- if [ "$e2e" = true ]; then $gam print vaultholds matter $matterid; fi
|
||||
- if [ "$e2e" = true ]; then $gam create vaultexport matter $matterid name "Travis export $newbase" corpus mail accounts $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add calendar id:$newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete resource $newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete feature Whiteboard-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete feature VC-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete building $newbuilding; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete group $newgroup; fi
|
||||
- if [ "$e2e" = true ]; then $gam create alias $newalias user $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam whatis $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user show tokens; fi
|
||||
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete hold "Travis hold $newbase" matter $matterid; fi
|
||||
- if [ "$e2e" = true ]; then $gam update matter $matterid action close; fi
|
||||
- if [ "$e2e" = true ]; then $gam update matter $matterid action delete; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete user $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print users query "travis.jid=$jid" | $gam csv - gam delete user ~primaryEmail; fi
|
||||
- if [ "$e2e" = true ]; then $gam print mobile; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices; fi
|
||||
- if [ "$e2e" = true ]; then export sn="$jid$jid$jid$jid$jid-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create device serialnumber $sn devicetype android; fi
|
||||
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usageparameters customer; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins; fi
|
||||
- if [ "$e2e" = true ]; then $gam report customer todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report admin start -3d todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices nopersonaldevices nodeviceusers filter "serial:$jid$jid$jid$jid$jid-" | $gam csv - gam delete device id ~name; fi
|
||||
- if ([ "$e2e" = true ] && [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]); then
|
||||
for gamfile in gam-$GAMVERSION-*; do
|
||||
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${TRAVIS_COMMIT:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly);
|
||||
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink;
|
||||
done;
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- export TRAVIS_TAG="preview"
|
||||
- unset LD_LIBRARY_PATH
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token:
|
||||
secure: bzambMcQwyv/o5c5GrKGCsZHgE5R85tg8sNFvPfpISz3+uosCjnBXas7wvCKzT75XUFi2ztfbYak6HdKf4sGnNHk0saEicB3slH+ghPyZbYzp76yvvduhFO2nWW3/F01tL+Yfqqt4/q8wFaWGjrC5km+6GLVyB4lWA/Uyu49qKnz02uSwyhBD/VFbO7DOQ65a1iWk9HngyMsu0Oi7HIbSjSLtxTHedNfOf3waW0NivTTxYXiYGX/MCu3GWhgIGj47a+H3A6FcQ/9QWvnKgnoixdgPBUz7kDb7ktsWwQsILPGStgH7iMuG49ZlXdEFmqwifBri2wvzmFEevBGZjHcupy1IGrNFRG+IUGKMotio+OkLHlLjuv7ZJtqCz/Vf5SNFgNyMSanx6jKEUJuYvndVg99IRXmYVwHFwPu5BAcJACpU6C0AfyGmmSqqwxCd46uXL62ynxNFpHuRfOqlDnmCTfZgjOciJSlDDpf+Xz9fF7+oCoeCi3mrcZVFjhd3tT6Oxw5HrsDtm0ZNld1cdLidaq8H6vOFgHMd0A9yNYZzTzXTvpmxzkXT4Zc7s+PYKN6z5fRZ+pJeckUjRXblvVEfs5HFSymavcOc5AkRwxpvOsTQMNmlnaJCBo5UNs0K/rVmRi5cFmaiwTcBCY0kTllOBJ4zWsfq8seiokWwNUNK2g=
|
||||
file_glob: true
|
||||
overwrite: true
|
||||
file: gam-$GAMVERSION-*
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
repo: jay0lee/GAM
|
||||
condition: $TRAVIS_JOB_NAME != *"Testing"
|
||||
35
README.md
35
README.md
@@ -1,21 +1,46 @@
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily. [](https://travis-ci.org/jay0lee/GAM)
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily.
|
||||
|
||||

|
||||
|
||||
# Quick Start
|
||||
|
||||
## Linux / MacOS
|
||||
|
||||
Open a terminal and run:
|
||||
```
|
||||
|
||||
```sh
|
||||
bash <(curl -s -S -L https://git.io/install-gam)
|
||||
```
|
||||
|
||||
this will download GAM, install it and start setup.
|
||||
|
||||
To install with `pip`, run
|
||||
|
||||
```sh
|
||||
pip install git+https://github.com/jay0lee/GAM.git#subdirectory=src
|
||||
```
|
||||
|
||||
This will only download and install GAM. To start setup, simply invoke the `gam` CLI.
|
||||
|
||||
## Windows
|
||||
|
||||
Download the MSI Installer from the [GitHub Releases] page. Install the MSI and you'll be prompted to setup GAM.
|
||||
|
||||
# Documentation
|
||||
|
||||
The GAM documentation is hosted in the [GitHub Wiki]
|
||||
|
||||
# Mailing List / Discussion group
|
||||
|
||||
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
|
||||
# IM Room
|
||||
[](https://gitter.im/jay0lee-GAM/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
# Chat Room
|
||||
|
||||
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
|
||||
|
||||
# Author
|
||||
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>. Please direct "how do I?" questions to [Google Groups].
|
||||
|
||||
GAM is maintained by [Jay Lee](mailto:jay0lee@gmail.com). Please direct "how do I?" questions to [Google Groups].
|
||||
|
||||
[GAM release]: https://git.io/gamreleases
|
||||
[GitHub Releases]: https://github.com/jay0lee/GAM/releases
|
||||
|
||||
@@ -73,17 +73,9 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<ProductID> ::=
|
||||
Google-Apps|
|
||||
Google-Chrome-Device-Management|
|
||||
Google-Coordinate|
|
||||
Google-Drive-storage|
|
||||
Google-Vault|
|
||||
101001|101005|101031
|
||||
<ProductID> ::=
|
||||
Google-Apps|
|
||||
Google-Chrome-Device-Management|
|
||||
Google-Coordinate|
|
||||
Google-Drive-storage|
|
||||
Google-Vault|
|
||||
101001|101005|101006|101031|101033|101034
|
||||
101001|101005|101031|101033|101034|101037
|
||||
<SKUID> ::=
|
||||
cloudidentity|identity|1010010001|
|
||||
cloudidentitypremium|identitypremium|1010050001|
|
||||
@@ -93,6 +85,13 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gams|postini|gsuitegams|gsuitepostini|gsuitemessagesecurity|Google-Apps-For-Postini|
|
||||
gal|gsl|lite|gsuitelite|Google-Apps-Lite|
|
||||
gau|gsb|unlimited|gsuitebusiness|Google-Apps-Unlimited|
|
||||
gwep|workspaceeducationplus|1010310008|
|
||||
gwepstaff|workspaceeducationplusstaff|1010310009|
|
||||
gwepstudent|workspaceeducationplusstudent|1010310010|
|
||||
gwes|workspaceeducationstandard|1010310005|
|
||||
gwesstaff|workspaceeducationstandardstaff|1010310006|
|
||||
gwesstudent|workspaceeducationstandardstudent|1010310007|
|
||||
gwetlu|workspaceeducationupgrade|1010370001|
|
||||
wsentplus|workspaceenterpriseplus|gae|gse|enterprise|gsuiteenterprise|1010020020|
|
||||
wsbizplus|workspacebusinessplus|1010020025|
|
||||
wsentstan|workspaceenterprisestandard|'1010020026|
|
||||
@@ -103,7 +102,6 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gsbau|businessarchived|gsuitebusinessarchived|
|
||||
gseau|enterprisearchived|gsuiteenterprisearchived|
|
||||
chrome|cdm|googlechromedevicemanagement|Google-Chrome-Device-Management|
|
||||
coordinate|googlecoordinate|Google-Coordinate|
|
||||
wsess|workspaceesentials|gsuiteessentials|essentials|d4e|driveenterprise|drive4enterprise|1010060001|
|
||||
wsentess|workspaceenterpriseessentials|1010060003|
|
||||
drive20gb|20gb|googledrivestorage20gb|Google-Drive-storage-20GB|
|
||||
@@ -116,7 +114,8 @@ If an item contains spaces, it should be surrounded by ".
|
||||
drive8tb|8tb|googledrivestorage8tb|Google-Drive-storage-8TB|
|
||||
drive16tb|16tb|googledrivestorage16tb|Google-Drive-storage-16TB|
|
||||
vault|googlevault|Google-Vault|
|
||||
vfe|googlevaultformeremployee|Google-Vault-Former-Employee
|
||||
vfe|googlevaultformeremployee|Google-Vault-Former-Employee|
|
||||
workspacefrontline|workspacefrontlineworker|1010020030
|
||||
|
||||
## Basic items built from primitives
|
||||
|
||||
@@ -149,13 +148,17 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<AccessToken> ::= <String>
|
||||
<ACLScope> ::= [user:]<EmailAddress>|group:<EmailAddress>|domain[:<DomainName>]|default
|
||||
<APIScopeURL> ::= <String>
|
||||
<APPID> ::= <String>
|
||||
<ASPID> ::= <String>
|
||||
<AssetTag> ::= <String>
|
||||
<BrowserTokenPermanentID> ::= <String>
|
||||
<BuildingID> ::= <String>|id:<String>
|
||||
<CalendarACLRole> ::= editor|freebusy|freebusyreader|owner|reader|writer
|
||||
<CalendarACLRuleID> ::= user:<EmailAddress>|group:<EmailAddress>|domain:<DomainName>|default
|
||||
<CalendarColorIndex> ::= <Number in range 1-24>
|
||||
<CalendarItem> ::= <EmailAddress>|<String>
|
||||
<ChatRoom> ::= <String>
|
||||
<ChatSpace> ::= <String>
|
||||
<ClientID> ::= <String>
|
||||
<ColorValue> ::= <ColorName>|<ColorHex>
|
||||
<CollaboratorItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
@@ -201,6 +204,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<MaximumNumberOfSeats> ::= <Number>
|
||||
<MobileID> ::= <String>
|
||||
<Name> ::= <String>
|
||||
<Namespace> ::= <String>
|
||||
<NotificationID> ::= <String>
|
||||
<NumberOfSeats> ::= <Number>
|
||||
<OrgUnitID> ::= <String>
|
||||
@@ -210,20 +214,20 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<Password> ::= <String>
|
||||
<PermissionID> ::= id:<String>|<EmailAddress>|anyone|anyonewithlink
|
||||
<PrinterID> ::= <String>
|
||||
<PrintJobAge> ::= <Number>[m|h|d]
|
||||
<PrintJobID> ::= <String>
|
||||
<PrintJobStatus> ::= done|error|held|in_progress|queued|submitted
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<QueryBrowser> ::= <String> See: https://support.google.com/chrome/a/answer/9681204#retrieve_all_chrome_devices_for_an_account
|
||||
<QueryBrowserToken> ::= <String> See: https://support.google.com/chrome/a/answer/9949706?ref_topic=9301744
|
||||
<QueryCalendar> ::= <String>
|
||||
<QueryContact> ::= <String> See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
|
||||
<QueryCrOS> ::= <String> See: https://support.google.com/chrome/a/answer/1698333?hl=en
|
||||
<QueryDriveFile> ::= <String> See: https://developers.google.com/drive/v2/web/search-parameters
|
||||
<QueryDynamicGroup> ::= <String> See: https://cloud.google.com/identity/docs/reference/rest/v1beta1/groups#dynamicgroupquery
|
||||
<QueryGmail> ::= <String> See: https://support.google.com/mail/answer/7190
|
||||
<QueryGroup> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
|
||||
<QueryMemberRestrictions> ::= <String> See: https://cloud.google.com/identity/docs/reference/rest/v1beta1/SecuritySettings#MemberRestriction
|
||||
<QueryMobile> ::= <String> See: https://support.google.com/a/answer/7549103
|
||||
<QueryPrinter> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#search
|
||||
<QueryPrintJob> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#parameters_3
|
||||
<QueryTeamDrive> ::= <String> See: https://developers.google.com/drive/api/v3/search-shareddrives
|
||||
<QueryUser> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
|
||||
<QueryVaultCorpus> ::= <String> See: https://developers.google.com/vault/reference/rest/v1/matters.holds#CorpusQuery
|
||||
<RequestID> ::= <String>
|
||||
@@ -332,6 +336,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
lastmodifyinguser|
|
||||
lastmodifyingusername|
|
||||
lastviewedbyme|lastviewedbymedate|lastviewedbymetime|lastviewedbyuser|
|
||||
linksharemetadata|
|
||||
md5|md5checksum|md5sum|
|
||||
mime|mimetype|
|
||||
modifiedbyme|modifiedbymedate|modifiedbymetime|modifiedbyuser|
|
||||
@@ -344,11 +349,13 @@ If an item contains spaces, it should be surrounded by ".
|
||||
parents|
|
||||
permissions|
|
||||
quotabytesused|quotaused|
|
||||
resourcekey|
|
||||
restricted|
|
||||
shareable|
|
||||
shared|
|
||||
sharedwithmedate|sharedwithmetime|
|
||||
sharinguser|
|
||||
shortcutdetails|
|
||||
size|
|
||||
spaces|
|
||||
starred|
|
||||
@@ -492,9 +499,6 @@ If an item contains spaces, it should be surrounded by ".
|
||||
description|id|inherit|name|orgunitpath|parent|parentid|inherit
|
||||
<OrgUnitFieldNameList> ::= "<OrgUnitFieldName>(,<OrgUnitFieldName>)*"
|
||||
|
||||
<PrintJobOrderByFieldName> ::=
|
||||
create_time|status|title
|
||||
|
||||
<ResourceFieldName> ::=
|
||||
buildingid|
|
||||
capacity|
|
||||
@@ -568,6 +572,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
|
||||
<ACLList> ::= "<ACLScope>(,<ACLScope>)*"
|
||||
<APIScopeURLList> ::= "<APIScopeURL>(,<APIScopeURL>)*"
|
||||
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
|
||||
<AssetTagList> ::= "<AssetTag>(,<AssetTa>g)*"
|
||||
<CalendarList> ::= "<CalendarItem>(,<CalendarItem>)*"
|
||||
<ChatRoomList> ::= "<ChatRoom>(,<ChatRoom>)*"
|
||||
<CollaboratorItemList> ::= "<CollaboratorItem>(,<CollaboratorItem>)*"
|
||||
@@ -593,13 +598,12 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
|
||||
<MatterItemList> ::= "<MatterItem>(,<MatterItem>)*"
|
||||
<MembersFieldNameList> ::= "<MembersFieldName>(,<MembersFieldName>)*"
|
||||
<MobileList> ::= "<MobileId>(,<MobileId>)*"
|
||||
<NamespaceList> ::= "<Namespace>(,<Namespace)*"
|
||||
<OrgUnitList> ::= "<OrgUnitPath>(,<OrgUnitPath>)*"
|
||||
<PrinterIDList> ::= "<PrinterID>(,<PrinterID>)*"
|
||||
<PrinterIDList> ::= "<PrinterID>)(,<PrinterID>)*"
|
||||
<ProductIDList> ::= "(<ProductID>|SKUID>)(,<ProductID>|SKUID>)*"
|
||||
<PrintJobIDList> ::= "<PrintJobID>(,<PrintJobID>)*"
|
||||
<QueryCrOSList> ::= "<QueryCrOS>(,<QueryCrOS>)*"
|
||||
<QueryMobileList> ::= "<QueryMobile>(,<QueryMobile>)*"
|
||||
<QueryPrinterList> ::= "<QueryPrinter>(,<QueryPrinter>)*"
|
||||
<QueryUserList> ::= "<QueryUser>(,<QueryUser>)*"
|
||||
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
|
||||
<SKUIDList> ="<SKUID>(,<SKUID>)*"
|
||||
@@ -653,7 +657,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
|
||||
## Item attributes
|
||||
|
||||
<BuildingAttributes> ::=
|
||||
<BuildingAttribute> ::=
|
||||
(description <String>)|
|
||||
(floors <FloorNameList>)|
|
||||
(id <String>)|
|
||||
@@ -661,7 +665,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(longitude <Float>)|
|
||||
(name <String>)
|
||||
|
||||
<CalendarAttributes> ::=
|
||||
<CalendarAttribute> ::=
|
||||
(selected <Boolean>)|(hidden <Boolean>)|(summary <String>)|(colorindex|colorid <CalendarColorIndex>)|(backgroundcolor <ColorValue>)|(foregroundcolor <ColorValue>)|
|
||||
(reminder clear|(email|sms|pop <Number>))|
|
||||
(notification clear|(email|sms eventcreation|eventchange|eventcancellation|eventresponse|agenda))
|
||||
@@ -669,7 +673,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
<CalendarSettings> ::=
|
||||
(summary <String>)|(description <String>)|(location <String>)|(timezone <TimeZone>)
|
||||
|
||||
<CourseAttributes> ::=
|
||||
<CourseAttribute> ::=
|
||||
(description <String>)|
|
||||
(heading <String>)|
|
||||
(name <String>)|
|
||||
@@ -678,33 +682,41 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(state|status <CourseState>)|
|
||||
(owner|ownerid|teacher <UserItem>)
|
||||
|
||||
<CrOSAttributes> ::=
|
||||
<CrOSAttribute> ::=
|
||||
(asset|assetid|tag <String>)|
|
||||
(location <String>)|
|
||||
(notes <String>)|
|
||||
(org|ou <OrgUnitPath>)|
|
||||
(user <Name>)
|
||||
|
||||
<DriveFileAddAttributes> ::=
|
||||
(localfile <FileName>)|
|
||||
<CIGroupAttribute> ::=
|
||||
(description <String>)|
|
||||
(name <String>)
|
||||
|
||||
<DriveFileAddAttribute> ::=
|
||||
(localfile <FileName>|-)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
|
||||
(contentrestrictions readonly false)|
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
copyrequireswriterpermission|
|
||||
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(shortcut <DriveFileID>)
|
||||
<DriveFileUpdateAttributes> ::=
|
||||
(localfile <FileName>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|
|
||||
(securityupdate <Boolean>)|
|
||||
(shortcut <DriveFileID>)|
|
||||
writerscantshare|writerscanshare
|
||||
<DriveFileUpdateAttribute> ::=
|
||||
(localfile <FileName>|-)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
|
||||
(contentrestrictions readonly false)|
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
(copyrequireswriterpermission <Boolean>)|
|
||||
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(shortcut <DriveFileID>)
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|
|
||||
(securityupdate <Boolean>)|
|
||||
(shortcut <DriveFileID>)|
|
||||
writerscantshare|writerscanshare
|
||||
<GroupSettingsAttribute> ::=
|
||||
(allowexternalmembers <Boolean>)|
|
||||
(allowwebposting <Boolean>)|
|
||||
@@ -712,6 +724,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(customfootertext <String>)|
|
||||
(customreplyto <EmailAddress>)|
|
||||
(defaultmessagedenynotificationtext <String>)|
|
||||
(defaultsender default_self|group)|
|
||||
(description <String>)|
|
||||
(enablecollaborativeinbox|collaborative <Boolean>)|
|
||||
(includeinglobaladdresslist|gal <Boolean>)|
|
||||
@@ -773,13 +786,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
<MobileAction> ::=
|
||||
admin_remote_wipe|wipe|admin_account_wipe|accountwipe|wipeaccount|approve|block|cancel_remote_wipe_then_activate|cancel_remote_wipe_then_block
|
||||
|
||||
<PrinterAttributes> ::= (currentquota <Number>)|(dailyquota <Number>)|
|
||||
(defaultdisplayname <String>)|(description <String>)|(displayname <String>)|(firmware <String>)|(gcpversion <String>)|
|
||||
(istosaccepted <Boolean>)|(manufacturer <String>)|(model <String>)|(name <String>)|(ownerid <EmailAddress>)|(proxy <String>)|(public <Boolean>)|
|
||||
(quotaenabled <Boolean>)|(status <Number>)|(type <String>)|(uuid <String>)|
|
||||
(setupurl <URL>)|(supporturl <URL>)|(updateurl <URL>)
|
||||
|
||||
<ResourceAttributes> ::=
|
||||
<ResourceAttribute> ::=
|
||||
(buildingid <BuildingID>)|
|
||||
(capacity <Number>)|
|
||||
(category other|room|conference_room|category_unknown)|
|
||||
@@ -795,7 +802,6 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
field <FieldName> (type bool|date|double|email|int64|phone|string) [multivalued|multivalue] [indexed] [restricted] [range <Number> <Number>] endfield
|
||||
|
||||
<UserBasicAttribute> ::=
|
||||
(agreed2terms|agreedtoterms <Boolean>)|
|
||||
(changepassword|changepasswordatnextlogin <Boolean>)|
|
||||
(base64-md5|base64-sha1|crypt|sha|sha1|sha-1|md5|nohash)|
|
||||
(customerid <String>)|
|
||||
@@ -813,7 +819,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(recoveryphone <string>)|
|
||||
(suspended <Boolean>)|
|
||||
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]] <String>)
|
||||
<UserMultiAttributes> ::=
|
||||
<UserMultiAttribute> ::=
|
||||
(address clear|(type home|other|work|(custom <String>) [unstructured|formatted <String>] [pobox <String>] [extendedaddress <String>] [streetaddress <String>]
|
||||
[locality <String>] [region <String>] [postalcode <String>] [country <String>] [countrycode <String>] notprimary|primary))|
|
||||
(otheremail clear|(home|other|work|<String> <String>))|
|
||||
@@ -848,6 +854,8 @@ An argument containing instances of ~~xxx~~ has xxx replaced by the value of fie
|
||||
Example: gam csv Users.csv gam update user "~primaryEmail" address type work unstructured "~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~"
|
||||
Each user (~primaryEmail, e.g. foo@bar.com) would have their work address updated
|
||||
|
||||
gam create gcpfolder <String>
|
||||
|
||||
gam create project [<EmailAddress>] [<ProjectID>]
|
||||
gam create project [admin <EmailAddress>] [project <ProjectID>] [parent <String>]
|
||||
gam use project [<EmailAddress>] [<ProjectID>]
|
||||
@@ -872,7 +880,7 @@ gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
|
||||
|
||||
gam whatis <EmailItem>
|
||||
|
||||
<ResoldCustomerAttributes> ::=
|
||||
<ResoldCustomerAttribute> ::=
|
||||
(email|alternateemail <EmailAddress>)|
|
||||
(contact|contactname <String>)|
|
||||
(phone|phonenumber <String>)|
|
||||
@@ -885,7 +893,7 @@ gam whatis <EmailItem>
|
||||
(zipcode|postal|postalcode <String>)|
|
||||
(country|countrycode <String>)
|
||||
|
||||
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttributes>+
|
||||
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttribute>+
|
||||
gam update resoldcustomer <CustomerID> [customer_auth_token <String>] <ResoldCustomerAttribues>+
|
||||
gam info resoldcustomer <CustomerID>
|
||||
|
||||
@@ -901,23 +909,27 @@ gam delete resoldsubscription <CustomerID> <SKUID> cancel|downgrade|transfer_to_
|
||||
gam info resoldsubscriptions <CustomerID> [customer_auth_token <String>]
|
||||
|
||||
<ActivityApplicationName> ::=
|
||||
access|accesstransparency|
|
||||
access_transparency|
|
||||
admin|
|
||||
calendar|calendars|
|
||||
calendar|
|
||||
chat|
|
||||
drive|doc|docs|
|
||||
enterprisegroups|groupsenterprise|
|
||||
chrome|
|
||||
context_aware_access|
|
||||
data_studio|
|
||||
drive|
|
||||
gcp|
|
||||
google+|gplus|
|
||||
group|groups|
|
||||
hangoutsmeet|meet|
|
||||
gplus|
|
||||
groups|
|
||||
groups_enterprise|
|
||||
jamboard|
|
||||
login|logins|
|
||||
keep|
|
||||
login|
|
||||
meet|
|
||||
mobile|
|
||||
oauthtoken|token|tokens|
|
||||
rules|
|
||||
saml|
|
||||
useraccounts
|
||||
token|
|
||||
user_accounts
|
||||
|
||||
<ReportsApp> ::=
|
||||
accounts|
|
||||
@@ -956,6 +968,7 @@ gam report <ActivityApplicationName> [todrive]
|
||||
[(user all|<UserItem>)|(orgunit|org|ou <OrgUnitPath>)]
|
||||
[start <Time>] [end <Time>]
|
||||
[filter|filters <String>] [event <String>] [ip <String>]
|
||||
[groupidfilter <String>]
|
||||
|
||||
gam create admin <UserItem> <RoleItem> customer|(org_unit <OrgUnitItem>)
|
||||
gam delete admin <RoleAssignmentId>
|
||||
@@ -976,7 +989,7 @@ gam delete domainalias|aliasdomain <DomainAlias>
|
||||
gam info domainalias|aliasdomain <DomainAlias>
|
||||
gam print domainaliases|aliasdomains [todrive]
|
||||
|
||||
<CustomerAttributes> ::=
|
||||
<CustomerAttribute> ::=
|
||||
(primary <DomainName>)|
|
||||
(adminsecondaryemail|alternateemail <EmailAddress>)|
|
||||
(contact|contactname <String>)|
|
||||
@@ -991,12 +1004,14 @@ gam print domainaliases|aliasdomains [todrive]
|
||||
(zipcode|postal|postalcode <String>)|
|
||||
(country|countrycode <String>)
|
||||
|
||||
gam update customer <CustomerAttributes>*
|
||||
gam update customer <CustomerAttribute>*
|
||||
|
||||
gam info customer
|
||||
|
||||
<DataTransferService> ::=
|
||||
calendar|
|
||||
currents|
|
||||
datastudio|"google data studio"|
|
||||
googledrive|gdrive|drive|"drive and docs"
|
||||
<DataTransferServiceList> ::= "<DataTransferService>(,<DataTransferService>)*"
|
||||
|
||||
@@ -1013,15 +1028,15 @@ gam delete org|ou <OrgUnitPath>
|
||||
gam info org|ou <OrgUnitPath> [nousers|notsuspended|suspended] [children|child]
|
||||
gam print orgs|ous [todrive] [toplevelonly] [from_parent <OrgUnitPath>] [allfields|(fields <OrgUnitFieldNameList>)]
|
||||
|
||||
gam create alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress>
|
||||
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress>
|
||||
gam create alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
|
||||
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
|
||||
gam delete alias|nickname [user|group|target] <UniqueID>|<EmailAddress>
|
||||
gam info alias|nickname <EmailAddress>
|
||||
gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query <QueryUser>)|(queries <QueryUserList)]
|
||||
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> del|delete <CalendarACLRole> <EmailAddress>|(domain [<DomainName>])|default
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>) [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domainx|default
|
||||
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
|
||||
gam calendar <CalendarItem> showacl
|
||||
gam calendar <CalendarItem> printacl [todrive]
|
||||
@@ -1034,7 +1049,7 @@ The following attributes are equivalent:
|
||||
sendnotifications false - sendupdates none
|
||||
sendnotifications true - sendupdates all
|
||||
|
||||
<EventAttributes> ::=
|
||||
<EventAttribute> ::=
|
||||
anyonecanaddself|
|
||||
(attendee <EmailAddress>)|
|
||||
available|
|
||||
@@ -1060,8 +1075,8 @@ The following attributes are equivalent:
|
||||
(timezone <Timezone>)|
|
||||
(visibility default|public|prvate)
|
||||
|
||||
<EventUpdateAttributes> ::=
|
||||
<EventAttributes>|
|
||||
<EventUpdateAttribute> ::=
|
||||
<EventAttribute>|
|
||||
(removeattendee <EmailAddress>)|
|
||||
(replacedescription <RegularExpression> <String>)
|
||||
|
||||
@@ -1076,10 +1091,10 @@ The following attributes are equivalent:
|
||||
<EventDisplayProperty> ::=
|
||||
(timezone <TimeZone>)
|
||||
|
||||
gam calendar <CalendarItem> addevent [id <String>] <EventAttributes>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> addevent [id <String>] <EventAttribute>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> deleteevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> moveevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttributes>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttribute>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> wipe
|
||||
gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProperty>* [todrive]
|
||||
|
||||
@@ -1091,6 +1106,94 @@ gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProp
|
||||
|
||||
gam calendar <CalendarItem> modify <CalendarSettings>+
|
||||
|
||||
<BrowserAttribute> ::=
|
||||
(assetid <String>)|
|
||||
(location <String>)|
|
||||
(notes <String>)|
|
||||
(user <String>
|
||||
|
||||
<BrowserFieldName> ::=
|
||||
annotatedAssetId|
|
||||
annotatedLocation|
|
||||
annotatedNotes|
|
||||
annotatedUser|
|
||||
browsers|
|
||||
browserVersions|
|
||||
deviceId|
|
||||
extensionCount|
|
||||
installedBrowserVersion|
|
||||
lastActivityTime|
|
||||
lastDeviceUser|
|
||||
lastDeviceUsers|
|
||||
lastPolicyFetchTime|
|
||||
lastRegistrationTime|
|
||||
lastStatusReportTime|
|
||||
machineName|
|
||||
machinePolicies|
|
||||
orgUnitPath|
|
||||
osArchitecture|
|
||||
osPlatform|
|
||||
osPlatformVersion|
|
||||
osVersion|
|
||||
orgUnitPath|
|
||||
policyCount|
|
||||
safeBrowsingClickThroughCount|
|
||||
serialNumber|
|
||||
virtualDeviceId
|
||||
<BrowserFieldNameList> ::= "<BrowseFieldName>(,<BrowserFieldName>)*"
|
||||
|
||||
gam move browsers ou|org|orgunit <OrgUnitPath>
|
||||
((ids <DeviceIDList>) |
|
||||
(query <QueryBrowser>) |
|
||||
(file <FileName>) |
|
||||
(csvfile <FileName>:<FieldName>))
|
||||
[batchsize <Integer>]
|
||||
gam update browser <DeviceID> <BrowserAttibute>+
|
||||
|
||||
gam info browser <DeviceID>
|
||||
[basic|full]
|
||||
[fields <BrowserFieldNameList>]
|
||||
|
||||
gam print browsers [todrive]
|
||||
[ou|org|orgunit <OrgUnitPath>] [query <QueryBrowser>]
|
||||
[projection basic|full]
|
||||
[fields <BrowserFieldNameList>]
|
||||
[sortheaders]
|
||||
|
||||
gam create browsertoken
|
||||
[ou|org|orgunit <OrgUnitPath>] [expire|expires <Time>]
|
||||
gam revoke browsertoken <BrowserTokenPermanentID>
|
||||
|
||||
<BrowserTokenFieldName> ::=
|
||||
createTime|
|
||||
creatorId|
|
||||
customerId|
|
||||
expireTime|
|
||||
orgUnitPath|
|
||||
revokeTime|
|
||||
revokerId|
|
||||
state|
|
||||
token|
|
||||
tokenPermanentId
|
||||
<BrowserTokenFieldNameList> ::= "<BrowseTokenFieldName>(,<BrowserTokenFieldName>)*"
|
||||
|
||||
gam show browsertokens
|
||||
[query <QueryBrowserToken>]
|
||||
[fields <BrowserTokenFieldNameList>]
|
||||
|
||||
gam print browsertokens [todrive]
|
||||
[query <QueryBrowserToken>]
|
||||
[fields <BrowserTokenFieldNameList>]
|
||||
[sortheaders]
|
||||
|
||||
gam print chatspaces [todrive]
|
||||
gam print chatmembers space <ChatSpace> [todrive]
|
||||
gam create chatmessage space <ChatSpace> [thread <String>]
|
||||
(text <String>)|(textfile <FileName> [charset <CharSet>])
|
||||
gam delete chatmessage name <String>
|
||||
gam update chatmessage name <String>
|
||||
(text <String>)|(textfile <FileName> [charset <CharSet>])
|
||||
|
||||
<CrOSAction> ::=
|
||||
deprovision_same_model_replace|
|
||||
deprovision_different_model_replace|
|
||||
@@ -1099,7 +1202,19 @@ gam calendar <CalendarItem> modify <CalendarSettings>+
|
||||
disable|
|
||||
reenable
|
||||
|
||||
gam update cros <CrOSEntity> (<CrOSAttributes>+)|(action <CrOSAction> [acknowledge_device_touch_requirement])
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
|
||||
<CrOSCommand>
|
||||
wipe_users|
|
||||
remote_powerwash|
|
||||
reboot|
|
||||
set_volume <0-100>|
|
||||
take_a_screenshot
|
||||
|
||||
gam issuecommand cros <CrOSEntity> command <CrOSCommand> [times_to_check_status <0-1000+>] [doit]
|
||||
gam getcommand cros <CrOSEntity> commandid <CommandID> [times_to_check_status <0-1000+>]
|
||||
|
||||
gam update cros <CrOSEntity> <CrOSAttribute>+
|
||||
gam info cros <CrOSEntity> [nolists] [listlimit <Number>] [start <Date>] [end <Date>]
|
||||
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [downloadfile latest|<Time>] [targetfolder <FilePath>]
|
||||
|
||||
@@ -1147,6 +1262,69 @@ The listlimit <Number> argument limits the number of recent users, time ranges a
|
||||
The start <Date> and end <Date> arguments filter the time ranges.
|
||||
Delimiter defaults to comma.
|
||||
|
||||
gam print chromeapps [todrive]
|
||||
[ou|org|orgunit <OrgUnitItem>]
|
||||
[filter <String>]
|
||||
[orderby appname|apptype|installtype|numberofpermissions|totalinstallcount]
|
||||
|
||||
gam print chromeappdevices [todrive]
|
||||
appid <AppID> apptype extension|app|theme|hostedapp|androidapp
|
||||
[ou|org|orgunit <OrgUnitItem>]
|
||||
[start <Date>] [end <Date>]
|
||||
[orderby deviceid|machine]
|
||||
|
||||
gam print chromeversions [todrive]
|
||||
[ou|org|orgunit <OrgUnitItem>]
|
||||
[start <Date>] [end <Date>] [recentfirst]
|
||||
|
||||
<ChromePlatformType>> ::=
|
||||
all'|
|
||||
android'|
|
||||
ios'|
|
||||
lacros'|
|
||||
linux'|
|
||||
mac'|
|
||||
macarm64'|
|
||||
sebview'|
|
||||
win'|
|
||||
win64'
|
||||
<ChromeChannelType> ::=
|
||||
beta'|
|
||||
canary'|
|
||||
canaryasan'|
|
||||
dev'|
|
||||
stable'
|
||||
<ChromeVersionsOrderByFieldName> ::=
|
||||
channel|
|
||||
name|
|
||||
platform|
|
||||
version|
|
||||
<ChromeReleasesOrderByFieldName> ::=
|
||||
channel|
|
||||
endtime|
|
||||
fraction|
|
||||
name|
|
||||
platform|
|
||||
starttime|
|
||||
version
|
||||
|
||||
gam print chromehistory platforms [todrive]
|
||||
gam print chromehistory channels [todrive]
|
||||
[platform <ChromePlatformType>]
|
||||
gam print chromehistory versions [todrive]
|
||||
[platform <ChromePlatformType>] [channel <ChromeChannelType>]
|
||||
[filter <String>]
|
||||
(orderby <ChromeVersionsOrderByFieldName> [ascending|descending])*
|
||||
gam print chromehistory releases [todrive]
|
||||
[platform <ChromePlatformType>] [channel <ChromeChannelType>] [version <String>]
|
||||
[filter <String>]
|
||||
(orderby <ChromeReleasessOrderByFieldName> [ascending|descending])*
|
||||
|
||||
gam delete chromepolicy <SchemaName>+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam update chromepolicy (<SchemaName> (<Field> <Value>)+)+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam show chromepolicy ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)] [namespace <NamespaceList>]
|
||||
gam show chromeschema [filter <String>]
|
||||
|
||||
<DeviceID> ::= devices/<String>
|
||||
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
|
||||
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
|
||||
@@ -1158,11 +1336,6 @@ gam info device [id] <DeviceID>
|
||||
gam delete device [id] <DeviceID>
|
||||
gam cancelwipe device [id] <DeviceID>
|
||||
gam wipe device [id] <DeviceID>
|
||||
gam approve deviceuser [id] <DeviceUserID>
|
||||
gam block deviceuser [id] <DeviceUserID>
|
||||
gam delete deviceuser [id] <DeviceUserID>
|
||||
gam cancelwipe deviceuser [id] <DeviceUserID>
|
||||
gam wipe deviceuser [id] <DeviceUserID>
|
||||
gam print devices [todrive] [filter|query <QueryDevice>]
|
||||
[orderby <DeviceOrderByFieldName> [ascending|descending]]
|
||||
[company|personal|nocompanydevices|nopersonaldevices]
|
||||
@@ -1175,14 +1348,68 @@ gam sync devices [filter|query <QueryDevice>]
|
||||
[unassigned_missing_action delete|wipe|donothing]
|
||||
[assigned_missing_action delete|wipe|donothing]
|
||||
|
||||
gam approve deviceuser [id] <DeviceUserID>
|
||||
gam block deviceuser [id] <DeviceUserID>
|
||||
gam delete deviceuser [id] <DeviceUserID>
|
||||
gam cancelwipe deviceuser [id] <DeviceUserID>
|
||||
gam wipe deviceuser [id] <DeviceUserID>
|
||||
|
||||
gam info deviceuserstate [id] <DeviceUserID> [clientid <String>]
|
||||
gam update deviceuserstate [id] <DeviceUserID> [clientid <String>]
|
||||
[customid <String>] [assettags clear|<AssetTagList>]
|
||||
[compliantstate|compliancestate compliant|noncompliant] [managedstate clear|managed|unmanaged]
|
||||
[healthscore very_poor|poor|neutral|good|very_good] [scorereason clear|<String>]
|
||||
(customvalue (bool <Boolean>)|(number <Integer>)|(string <String>))*
|
||||
|
||||
gam update mobile <MobileID>|query:<QueryMobile> action <MobileAction> [doit] [if_users|match_users <UserTypeEntity>]
|
||||
gam delete mobile <MobileID>
|
||||
gam info mobile <MobileID>
|
||||
gam print mobile [todrive] [(query <QueryMobile>)|(queries <QueryMobileList>)] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
|
||||
fields <MobileFieldNameList>] [delimiter <Character>] [appslimit <Number>] [listlimit <Number>]
|
||||
|
||||
gam create group <EmailAddress> <GroupAttributes>*
|
||||
gam update group <GroupItem> [email <EmailAddress>] <GroupAttributes>*
|
||||
<PrinterAttribute> ::=
|
||||
(description <String>)|
|
||||
(displayname <String>)|
|
||||
(makeandmodel <String>)|
|
||||
(ou|org|orgunit|orgunitid <OrgUnitItem>)|
|
||||
(ownerid <EmailAddress>)|
|
||||
(uri <String>)|
|
||||
(driverless|usedriverlessconfig)
|
||||
|
||||
gam create printer <PrinterAttribute>+
|
||||
gam update printer <PrinterID> <PrinterAttribute>+
|
||||
gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)
|
||||
|
||||
gam info printer <PrinterID>
|
||||
gam print printers [todrive] [filter <String>]
|
||||
gam print printermodels [todrive] [filter <String>]
|
||||
|
||||
gam create cigroup <EmailAddress> <CIGroupAttribute>*
|
||||
[makeowner] [alias|aliases <AliasList>] [dynamic <QueryDynamicGroup>]
|
||||
gam update cigroup <GroupItem> [email <EmailAddress>] <CIGroupAttribute>*
|
||||
[security] [dynamic <QueryDynamicGroup>]
|
||||
[memberrestrictions <QueryMemberRestrictions>]
|
||||
gam update cigroup <GroupItem> add [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
|
||||
gam delete cigroup <GroupItem>
|
||||
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate] [membertree] [nosecurity|nosecuritysettings]
|
||||
|
||||
gam print cigroups [todrive]
|
||||
[enterprisemember <UserItem>]
|
||||
[members|memberscount] [managers|managerscount] [owners|ownerscount]
|
||||
[memberrestrictions]
|
||||
[delimiter <Character>] [sortheaders]
|
||||
|
||||
gam info cimember <UserItem> <GroupItem>
|
||||
gam print cigroup-members|cigroups-members [todrive]
|
||||
[(enterprisemember <UserItem>)|(cigroup <GroupItem>)]
|
||||
[roles <GroupRoleList>]
|
||||
|
||||
gam create group <EmailAddress> <GroupAttribute>* [verifynotinvitable]
|
||||
gam update group <GroupItem> [email <EmailAddress>] <GroupAttribute>* [verifynotinvitable]
|
||||
gam update group <GroupItem> add [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
|
||||
gam update group <GroupItem> delete|remove [owner|manager|member] <UserTypeEntity>
|
||||
gam update group <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
|
||||
@@ -1202,11 +1429,20 @@ gam print group-members|groups-members [todrive]
|
||||
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
|
||||
[includederivedmembership]
|
||||
|
||||
gam send userinvitation <EmailAddress>
|
||||
gam cancel userinvitation <EmailAddress>
|
||||
gam check userinvitation|isinvitable <EmailAddress>
|
||||
gam info userinvitation <EmailAddress>
|
||||
gam print userinvitations [todrive]
|
||||
[state notyetsent|invited|accepted|declined]]
|
||||
[orderby email|updatetime [ascending|descending]]
|
||||
gam <UserTypeEntity> check isinvitable [todrive]
|
||||
|
||||
gam print licenses [todrive] [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite] [countsonly]
|
||||
gam show license|licenses|licence|licences [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite]
|
||||
|
||||
gam create building <Name> <BuildingAttributes>*
|
||||
gam update building <BuildIngID> <BuildingAttributes>*
|
||||
gam create building <Name> <BuildingAttribute>*
|
||||
gam update building <BuildIngID> <BuildingAttribute>*
|
||||
gam delete building <BuildingID>
|
||||
gam info building <BuildingID>
|
||||
gam print buildings [todrive]
|
||||
@@ -1216,8 +1452,8 @@ gam update feature <Name> name <Name>
|
||||
gam delete feature <Name>
|
||||
gam print features [todrive]
|
||||
|
||||
gam create resource <ResourceID> <Name> <ResourceAttributes>*
|
||||
gam update resource <ResourceID> <ResourceAttributes>*
|
||||
gam create resource <ResourceID> <Name> <ResourceAttribute>*
|
||||
gam update resource <ResourceID> <ResourceAttribute>*
|
||||
gam delete resource <ResourceID>
|
||||
gam info resource <ResourceID>
|
||||
gam print resources [todrive] [allfields] <ResourceFieldName>* [query <String>]
|
||||
@@ -1229,11 +1465,11 @@ gam info schema <SchemaName>
|
||||
gam show schema|schemas
|
||||
gam print schema|schemas
|
||||
|
||||
gam create user <EmailAddress> <UserAttributes>*
|
||||
gam update user <UserItem> <UserAttributes>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
|
||||
gam create user <EmailAddress> <UserAttribute>* [verifynotinvitable]
|
||||
gam update user <UserItem> <UserAttribute>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>] [verifynotinvitable]
|
||||
gam delete user <UserItem>
|
||||
gam undelete user <UserItem> [org|ou <OrgUnitPath>]
|
||||
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>]
|
||||
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>] [grouptree]
|
||||
|
||||
Print fields for selected users; use domain, query/queries and deleted_only to select users to print;
|
||||
if none of these options are specified, all users are printed.
|
||||
@@ -1251,8 +1487,8 @@ gam create verify|verification <DomainName>
|
||||
gam update verify|verification <DomainName> cname|txt|text|site|file
|
||||
gam info verify|verification
|
||||
|
||||
gam create course [id|alias <CourseAlias>] <CourseAttributes>*
|
||||
gam update course <CourseID> <CourseAttributes>+
|
||||
gam create course [id|alias <CourseAlias>] <CourseAttribute>*
|
||||
gam update course <CourseID> <CourseAttribute>+
|
||||
gam delete course <CourseID>
|
||||
gam info course <CourseID>
|
||||
gam print courses [todrive] [teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]
|
||||
@@ -1272,32 +1508,6 @@ gam show guardian|guardians [invitedguardian <EmailAddress>] [student <StudentIt
|
||||
gam print guardian|guardians [todrive] [invitedguardian <EmailAddress>] [student <StudentItem>] [invitations [states <GuardianStateList>]] [<UserTypeEntity>]
|
||||
gam cancel guardianinvitation|guardianinvitations <GuardianInvitationID> <StudentItem>
|
||||
|
||||
gam update printer <PrinterID> <PrinterAttributes>+
|
||||
gam delete printer <PrinterID>
|
||||
gam info printer <PrinterID> [everything]
|
||||
gam print printers [todrive] [(query <QueryPrinter>)|(queries <QueryPrinterList>)] [type <String>] [status <String>] [extrafields <String>]
|
||||
|
||||
gam printer <PrinterID> add user|manager|owner <EmailAddress>|[domain:]<DomainName>|public [notify]
|
||||
gam printer <PrinterID> delete <EmailAddress>|[domain:]<DomainName>|public
|
||||
gam printer <PrinterID> showacl
|
||||
gam printjob <PrintJobID> cancel
|
||||
gam printjob <PrintJobID> delete
|
||||
gam printjob <PrintJobID> resubmit <PrinterID>
|
||||
|
||||
gam printjob <PrinterID>|any fetch
|
||||
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
|
||||
[status <PrintJobStatus>]
|
||||
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
|
||||
[owner|user <EmailAddress>]
|
||||
[limit <Number>] [drivedir|(targetfolder <FilePath>)]
|
||||
gam printjob <PrinterID> submit <FileName>|<URL> [name|title <String>] (tag <String>)*
|
||||
gam print printjobs [todrive] [printer|printerid <PrinterID>]
|
||||
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
|
||||
[status <PrintJobStatus>]
|
||||
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
|
||||
[owner|user <EmailAddress>]
|
||||
[limit <Number>]
|
||||
|
||||
gam create vaultexport|export matter <MatterItem> [name <name>] corpus <drive|mail|groups|hangouts_chat>
|
||||
(accounts <EmailAddressList>) | (orgunit|ou <OrgUnitPath>) | (teamdrives <TeamDriveList>) | (rooms <ChatRoomList>) | everyone
|
||||
[scope <all_data|held_data|unprocessed_data>]
|
||||
@@ -1333,6 +1543,18 @@ gam undelete vaultmatter|matter <MatterItem>
|
||||
gam info vaultmatter|matter <MatterItem>
|
||||
gam print vaultmatters|matters [todrive] [basic|full] [matterstate open|closed|deleted]
|
||||
|
||||
gam print vaultcounts [todrive]
|
||||
matter <MatterItem> corpus mail|groups
|
||||
(accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone
|
||||
[scope <all_data|held_data|unprocessed_data>]
|
||||
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>]
|
||||
[excludedrafts <Boolean>]
|
||||
[wait <Integer>]
|
||||
|
||||
gam print vaultcounts [todrive]
|
||||
matter <MatterItem> operation <String>
|
||||
[wait <Integer>]
|
||||
|
||||
gam <UserTypeEntity> delete|del asp|asps|applicationspecificpasswords all|<ASPIDList>
|
||||
gam <UserTypeEntity> show asps|asp|applicationspecificpasswords
|
||||
|
||||
@@ -1340,8 +1562,8 @@ gam <UserTypeEntity> update backupcodes|backupcode|verificationcodes
|
||||
gam <UserTypeEntity> delete|del backupcodes|backupcode|verificationcodes
|
||||
gam <UserTypeEntity> show backupcodes|backupcode|verificationcodes
|
||||
|
||||
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttributes>*
|
||||
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttributes>+
|
||||
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttribute>*
|
||||
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttribute>+
|
||||
gam <UserTypeEntity> delete|del calendar <CalendarItem>
|
||||
gam <UserTypeEntity> show calendars
|
||||
gam <UserTypeEntity> info calendar <CalendarItem>|primary
|
||||
@@ -1360,8 +1582,8 @@ gam <UserTypeEntity> show fileinfo <DriveFileID> [allfields|<DriveFieldName>*]
|
||||
gam <UserTypeEntity> show filerevisions <DriveFileID>
|
||||
gam <UserTypeEntity> show filetree [anyowner] (orderby <DriveOrderByFieldName> [ascending|descending])*
|
||||
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttributes>* [csv] [todrive] [returnidonly]
|
||||
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttributes>*
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttribute>* [csv] [todrive] [returnidonly]
|
||||
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttribute>*
|
||||
gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>)
|
||||
[revision <Number>] [(format <FileFormatList>)|(csvsheet <String>)]
|
||||
[targetfolder <FilePath>] [targetname -|<FileName>] [overwrite] [showprogress]
|
||||
@@ -1398,7 +1620,7 @@ gam <UserTypeEntity> show tokens|token [clientid <ClientID>]
|
||||
gam <UserTypeEntity> print tokens|token [todrive] [clientid <ClientID>]
|
||||
gam print tokens|token [todrive] [clientid <ClientID>] [<UserTypeEntity>]
|
||||
|
||||
gam <UserTypeEntity> update user <UserAttributes>
|
||||
gam <UserTypeEntity> update user <UserAttribute>
|
||||
|
||||
gam <UserTypeEntity> deprovision|deprov
|
||||
|
||||
@@ -1409,6 +1631,7 @@ gam <UserTypeEntity> update labelsettings <LabelName> [name <Name>] [messagelist
|
||||
gam <UserTypeEntity> update label|labels [search <RegularExpression>] [replace <LabelReplacement>] [merge]
|
||||
gam <UserTypeEntity> delete|del label|labels <LabelName>|regex:<RegularExpression>|--ALL_LABELS--
|
||||
gam <UserTypeEntity> show labels|label [onlyuser] [showcounts]
|
||||
gam <UserTypeEntity> print labels|label [todrive] [onlyuser] [showcounts]
|
||||
|
||||
gam <UserTypeEntity> delete messages query <QueryGmail> [doit] [max_to_delete|max_to_process <Number>]
|
||||
gam <UserTypeEntity> modify messages query <QueryGmail> [doit] [max_to_modify|max_to_process <Number>] (addlabel <LabelName>)* (removelabel <LabelName>)*
|
||||
@@ -1432,12 +1655,17 @@ gam <UserTypeEntity> sendemail [recipient|to <EmailAddress>] [from <EmailAddress
|
||||
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
(header <String> <String>)*
|
||||
|
||||
gam <UserTypeEntity> create|add delegate|delegates <EmailAddress>
|
||||
gam <UserTypeEntity> delegate|delegates to <EmailAddress>
|
||||
gam <UserTypeEntity> delete|del delegate|delegates <EmailAddress>
|
||||
gam <UserTypeEntity> create|add delegate|delegates [convertalias] <EmailAddress>
|
||||
gam <UserTypeEntity> delegate|delegates to [convertalias] <EmailAddress>
|
||||
gam <UserTypeEntity> delete|del delegate|delegates [convertalias] <EmailAddress>
|
||||
gam <UserTypeEntity> show delegates|delegate [csv]
|
||||
gam <UserTypeEntity> print delegates [todrive]
|
||||
|
||||
gam <UserTypeEntity> create|add contactdelegate <EmailAddress>
|
||||
gam <UserTypeEntity> delete|del contactdelegate <EmailAddress>
|
||||
gam <UserTypeEntity> show contactdelegates [csv]
|
||||
gam <UserTypeEntity> print contactdelegates [todrive]
|
||||
|
||||
gam <UserTypeEntity> [create|add] filter [from <EmailAddress>] [to <EmailAddress>] [subject <String>] [haswords|query <List>] [nowords|negatedquery <List>] [musthaveattachment|hasattachment] [excludechats] [size larger|smaller <ByteCount>]
|
||||
[label <LabelID>] [important|notimportant] [star] [trash] [markread] [archive] [neverspam] [forward <EmailAddress>]
|
||||
gam <UserTypeEntity> delete filters <FilterIDEntity>
|
||||
@@ -1494,8 +1722,8 @@ gam <UserTypeEntity> update teamdrive <TeamDriveID> [asadmin] [name <Name>]
|
||||
(<TeamDriveRestrictionsSubfieldName> <Boolean>)*
|
||||
gam <UserTypeEntity> delete teamdrive <TeamDriveID>
|
||||
gam <UserTypeEntity> show teamdriveinfo <TeamDriveID> [asadmin]
|
||||
gam <UserTypeEntity> show teamdrives [asadmin]
|
||||
gam <UserTypeEntity> print teamdrives [todrive] [asadmin]
|
||||
gam <UserTypeEntity> show teamdrives [query <QueryTeamDrive>] [asadmin]
|
||||
gam <UserTypeEntity> print teamdrives [query <QueryTeamDrive>] [todrive] [asadmin]
|
||||
gam <UserTypeEntity> show teamdrivethemes
|
||||
|
||||
gam <UserTypeEntity> vacation <FalseValues>
|
||||
|
||||
594
src/cbcm-v1.1beta1.json
Normal file
594
src/cbcm-v1.1beta1.json
Normal file
@@ -0,0 +1,594 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers": {
|
||||
"description": "View and manage your Chrome browsers registered with Cloud Management"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly": {
|
||||
"description": "View your Chrome browsers registered with Cloud Management"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "cbcm",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://support.google.com/chrome/a/answer/9681204",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "cbcm:v1.1beta1",
|
||||
"kind": "discovery#restDescription",
|
||||
"mtlsRootUrl": "https://admin.mtls.googleapis.com/",
|
||||
"name": "cbcm",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Jay Lee",
|
||||
"packagePath": "cbcm",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"chromebrowsers": {
|
||||
"methods": {
|
||||
"delete": {
|
||||
"description": "Deletes a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "cbcm.chromebrowsers.delete",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"description": "Retrieves a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.get",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browsers in a domain.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"maxResults": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orderBy": {
|
||||
"description": "property to use for sorting results.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"description": "Whether to return results in ascending or descending order. Must be used with the orderBy parameter.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowsers"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"moveChromeBrowsersToOu": {
|
||||
"description": "Move Chrome Browsers Device between Organization Units",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.chromebrowsers.moveChromeBrowsersToOu",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"request": {
|
||||
"$ref": "MoveChromeBrowsersRequest"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"update": {
|
||||
"description": "Updates a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "PUT",
|
||||
"id": "cbcm.chromebrowsers.update",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "BASIC or FULL",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"request": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"enrollmentTokens": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browser entollment tokens in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.enrollmentTokens.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"response": {
|
||||
"$ref": "EnrollmentTokens"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"create": {
|
||||
"description": "Creates a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.create",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"request": {
|
||||
"$ref": "CreateEnrollmentTokenRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"revoke": {
|
||||
"description": "Revokes a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.revoke",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"tokenPermanentId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Unique identifier for an enrollment token.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": "20201203",
|
||||
"rootUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"schemas": {
|
||||
"ChromeBrowser": {
|
||||
"id": "ChromeBrowser",
|
||||
"properties": {
|
||||
"annotatedAssetId": {
|
||||
"description": "Asset identifier as annotated by the administrator or specified during enrollment.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedLocation": {
|
||||
"description": "Address or location of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedNotes": {
|
||||
"description": "Notes about this device as annotated by the administrator",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedUser": {
|
||||
"description": "User of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.update"
|
||||
]
|
||||
},
|
||||
"description": "The unique ID of the device.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ChromeBrowsers": {
|
||||
"id": "ChromeBrowsers",
|
||||
"properties": {
|
||||
"browsers": {
|
||||
"description": "List of Chrome browser objects.",
|
||||
"items": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeosdevices",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentToken": {
|
||||
"id": "EnrollmentToken",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentToken",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenId": {
|
||||
"description": "Enrollment Token ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Enrollment Token Permanent ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"customerId": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"creatorId": {
|
||||
"description": "Creator ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"description": "Creation Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokerId": {
|
||||
"description": "Revoker ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokeTime": {
|
||||
"description": "Revoke Time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentTokens": {
|
||||
"id": "EnrollmentTokens",
|
||||
"properties": {
|
||||
"chrome_enrollment_tokens": {
|
||||
"description": "List of Chrome browser enrollment token objects.",
|
||||
"items": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentTokens",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CreateEnrollmentTokenRequest": {
|
||||
"id": "CreateEnrollmentTokenRequest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"expire_time": {
|
||||
"description": "Expiration Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"token_type": {
|
||||
"id": "token_type",
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.enrollmentTokens.create"
|
||||
]
|
||||
},
|
||||
"description": "CHROME_BROWSER.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MoveChromeBrowsersRequest": {
|
||||
"id": "MoveChromeBrowsersRequest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "Destination organization unit to move devices to. Full path of the organizational unit or its ID prefixed with id:",
|
||||
"type": "string"
|
||||
},
|
||||
"resource_ids": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Admin SDK API",
|
||||
"version": "cbcm_v1.1beta1"
|
||||
}
|
||||
249
src/contactdelegation-v1.json
Normal file
249
src/contactdelegation-v1.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation": {
|
||||
"description": "View and manage your Contact Delegation"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly": {
|
||||
"description": "View your Contact Delegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "contactdelegation",
|
||||
"description": "The Contact Delegation API allows Admins to delegate access of one user's, called the delegator, contacts to another user, called the delegate.",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://developers.google.com/admin-sdk/contact-delegation",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "contactdelegation:v1",
|
||||
"kind": "discovery#restDescription",
|
||||
"name": "contactdelegation",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"packagePath": "admin",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"delegates": {
|
||||
"methods": {
|
||||
"create": {
|
||||
"description": "Creates a contact delegations",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "POST",
|
||||
"id": "contactdelegations.delegates.create",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"request": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a contact delegation.",
|
||||
"flatPath": "users/{user}/delegates/{delegate}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "contactdelegations.delegates.delete",
|
||||
"parameterOrder": [
|
||||
"user",
|
||||
"delegate"
|
||||
],
|
||||
"parameters": {
|
||||
"delegate": {
|
||||
"description": "Email address of the delegate",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Lists contact delegates for a user",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "GET",
|
||||
"id": "contactdelegations.delegates.list",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"pageSize": {
|
||||
"description": "Determines how many delegates are returned in each response. ",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates",
|
||||
"response": {
|
||||
"$ref": "Delegates"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation",
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rootUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"schemas": {
|
||||
"Delegate": {
|
||||
"description": "JSON template for a delegate.",
|
||||
"id": "Delegate",
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email of the delegate.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Delegates": {
|
||||
"id": "Delegates",
|
||||
"properties": {
|
||||
"delegates": {
|
||||
"description": "List of delegates.",
|
||||
"items": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Contact Delegation API",
|
||||
"version": "v1",
|
||||
"version_module": true
|
||||
}
|
||||
@@ -28,8 +28,8 @@ upgrade_only=false
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
gam_glibc_vers="2.31 2.27 2.23"
|
||||
gam_macos_vers="10.15.6 10.14.6 10.13.6"
|
||||
gam_glibc_vers="2.31 2.27"
|
||||
#gam_macos_vers="10.15.6 10.14.6 10.13.6"
|
||||
|
||||
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
|
||||
do
|
||||
@@ -128,19 +128,12 @@ case $gamos in
|
||||
this_macos_ver=$osversion
|
||||
fi
|
||||
echo "You are running MacOS $this_macos_ver"
|
||||
use_macos_ver=""
|
||||
for gam_macos_ver in $gam_macos_vers; do
|
||||
if version_gt $this_macos_ver $gam_macos_ver; then
|
||||
use_macos_ver="MacOS$gam_macos_ver"
|
||||
echo_green "Using GAM compiled on $use_macos_ver"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$use_macos_ver" == "" ]; then
|
||||
echo_red "Sorry, you need to be running at least MacOS $gam_macos_ver to run GAM"
|
||||
exit
|
||||
fi
|
||||
gamfile="macos-x86_64-$use_macos_ver.tar.xz"
|
||||
gamfile="macos-universal2.tar.xz"
|
||||
;;
|
||||
MINGW64_NT*)
|
||||
gamos="windows"
|
||||
echo "You are running Windows"
|
||||
gamfile="-windows-x86_64.zip"
|
||||
;;
|
||||
*)
|
||||
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're runnning on $gamos. Exiting."
|
||||
@@ -154,8 +147,14 @@ else
|
||||
release_url="https://api.github.com/repos/jay0lee/GAM/releases/tags/v$gamversion"
|
||||
fi
|
||||
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release..."
|
||||
release_json=$(curl -s $release_url 2>&1 /dev/null)
|
||||
if [ -z ${GHCLIENT+x} ]; then
|
||||
check_type="unauthenticated"
|
||||
else
|
||||
check_type="authenticated"
|
||||
fi
|
||||
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
|
||||
release_json=$(curl -s $GHCLIENT $release_url 2>&1 /dev/null)
|
||||
|
||||
echo_yellow "Getting file and download URL..."
|
||||
# Python is sadly the nearest to universal way to safely handle JSON with Bash
|
||||
@@ -222,14 +221,18 @@ temp_archive_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
|
||||
# Clean up after ourselves even if we are killed with CTRL-C
|
||||
trap "rm -rf $temp_archive_dir" EXIT
|
||||
|
||||
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir."
|
||||
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir ($check_type)..."
|
||||
# Save archive to temp w/o losing our path
|
||||
(cd $temp_archive_dir && curl -O -L $browser_download_url)
|
||||
(cd $temp_archive_dir && curl -O -L $GHCLIENT $browser_download_url)
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
echo_yellow "Extracting archive to $target_dir"
|
||||
tar xf $temp_archive_dir/$name -C "$target_dir"
|
||||
if [[ "${name}" == *.tar.xz ]]; then
|
||||
tar xf $temp_archive_dir/$name -C "$target_dir"
|
||||
else
|
||||
unzip "${temp_archive_dir}/${name}" -d "${target_dir}"
|
||||
fi
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."
|
||||
|
||||
@@ -8,4 +8,4 @@ from gam.__main__ import main
|
||||
|
||||
# Run from command line
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
main()
|
||||
|
||||
14
src/gam.spec
14
src/gam.spec
@@ -5,16 +5,21 @@ import sys
|
||||
import importlib
|
||||
from PyInstaller.utils.hooks import copy_metadata
|
||||
|
||||
sys.modules['FixTk'] = None
|
||||
|
||||
# dynamically determine where httplib2/cacerts.txt lives
|
||||
proot = os.path.dirname(importlib.import_module('httplib2').__file__)
|
||||
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
|
||||
|
||||
extra_files += copy_metadata('google-api-python-client')
|
||||
extra_files += [('cbcm-v1.1beta1.json', '.')]
|
||||
extra_files += [('contactdelegation-v1.json', '.')]
|
||||
extra_files += [('versionhistory-v1.json', '.')]
|
||||
|
||||
hidden_imports = [
|
||||
'gam.auth.yubikey',
|
||||
]
|
||||
|
||||
a = Analysis(['gam/__main__.py'],
|
||||
hiddenimports=[],
|
||||
hiddenimports=hidden_imports,
|
||||
hookspath=None,
|
||||
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
|
||||
datas=extra_files,
|
||||
@@ -27,6 +32,7 @@ for d in a.datas:
|
||||
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
@@ -36,4 +42,4 @@ exe = EXE(pyz,
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
console=True )
|
||||
console=True)
|
||||
|
||||
1086
src/gam/__init__.py
1086
src/gam/__init__.py
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ from gam import controlflow
|
||||
import gam
|
||||
|
||||
|
||||
def main(argv):
|
||||
def main():
|
||||
freeze_support()
|
||||
if sys.platform == 'darwin':
|
||||
# https://bugs.python.org/issue33725 in Python 3.8.0 seems
|
||||
@@ -47,4 +47,4 @@ def main(argv):
|
||||
|
||||
# Run from command line
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
main()
|
||||
|
||||
@@ -5,6 +5,9 @@ import os
|
||||
|
||||
from google.auth.jwt import Credentials as JWTCredentials
|
||||
|
||||
import gam
|
||||
from gam import utils
|
||||
|
||||
from gam.auth import oauth
|
||||
from gam.var import _FN_OAUTH2_TXT
|
||||
from gam.var import _FN_OAUTH2SERVICE_JSON
|
||||
@@ -13,6 +16,7 @@ from gam.var import GC_OAUTH2SERVICE_JSON
|
||||
from gam.var import GC_ENABLE_DASA
|
||||
from gam.var import GC_Values
|
||||
|
||||
yubikey = utils.LazyLoader('yubikey', globals(), 'gam.auth.yubikey')
|
||||
# TODO: Move logic that determines file name into this module. We should be able
|
||||
# to discover the file location without accessing a private member or waiting
|
||||
# for a global initialization.
|
||||
@@ -36,10 +40,17 @@ def get_admin_credentials(api=None):
|
||||
with open(credential_file, 'r') as f:
|
||||
creds_data = json.load(f)
|
||||
# Validate that enable DASA matches content of authorization file
|
||||
if GC_Values[GC_ENABLE_DASA] and 'private_key' in creds_data:
|
||||
if GC_Values[GC_ENABLE_DASA] and 'private_key_id' in creds_data:
|
||||
audience = f'https://{api}.googleapis.com/'
|
||||
return JWTCredentials.from_service_account_info(creds_data,
|
||||
audience=audience)
|
||||
key_type = creds_data.get('key_type', 'default')
|
||||
if key_type == 'default':
|
||||
return JWTCredentials.from_service_account_info(creds_data,
|
||||
audience=audience)
|
||||
elif key_type == 'yubikey':
|
||||
yksigner = yubikey.YubiKey(creds_data)
|
||||
return JWTCredentials._from_signer_and_info(yksigner,
|
||||
creds_data,
|
||||
audience=audience)
|
||||
elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data:
|
||||
return oauth.Credentials.from_credentials_file(credential_file)
|
||||
else:
|
||||
|
||||
157
src/gam/auth/yubikey.py
Normal file
157
src/gam/auth/yubikey.py
Normal file
@@ -0,0 +1,157 @@
|
||||
from base64 import b64encode
|
||||
import datetime
|
||||
from secrets import SystemRandom
|
||||
import string
|
||||
import sys
|
||||
from threading import Timer
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from smartcard.Exceptions import CardConnectionException
|
||||
from ykman.device import connect_to_device
|
||||
from ykman.piv import generate_self_signed_certificate, \
|
||||
generate_chuid
|
||||
from yubikit.piv import DEFAULT_MANAGEMENT_KEY, \
|
||||
InvalidPinError, \
|
||||
KEY_TYPE, \
|
||||
MANAGEMENT_KEY_TYPE, \
|
||||
PIN_POLICY, \
|
||||
PivSession, \
|
||||
OBJECT_ID, \
|
||||
SLOT, \
|
||||
TOUCH_POLICY
|
||||
from yubikit.core.smartcard import ApduError
|
||||
from gam import controlflow
|
||||
|
||||
class YubiKey():
|
||||
|
||||
def __init__(self, service_account_info=None):
|
||||
self.key_type = None
|
||||
self.slot = None
|
||||
self.serial_number = None
|
||||
self.pin = None
|
||||
self.key_id = None
|
||||
if service_account_info:
|
||||
key_type = service_account_info.get('yubikey_key_type', 'RSA2048')
|
||||
try:
|
||||
self.key_type = getattr(KEY_TYPE, key_type.upper())
|
||||
except AttributeError:
|
||||
controlflow.system_error_exit(6, f'{key_type} is not a valid value for yubikey_key_type')
|
||||
slot = service_account_info.get('yubikey_slot', 'AUTHENTICATION')
|
||||
try:
|
||||
self.slot = getattr(SLOT, slot.upper())
|
||||
except AttributeError:
|
||||
controlflow.system_error_exit(6, f'{slot} is not a valid value for yubikey_slot')
|
||||
self.serial_number = service_account_info.get('yubikey_serial_number')
|
||||
self.pin = service_account_info.get('yubikey_pin')
|
||||
self.key_id = service_account_info.get('private_key_id')
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
conn, _, _ = connect_to_device(self.serial_number)
|
||||
except CardConnectionException as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
return conn
|
||||
|
||||
def get_certificate(self):
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
controlflow.system_error_exit(7, f'YubiKey - {err}')
|
||||
try:
|
||||
cert = session.get_certificate(self.slot)
|
||||
except ApduError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
cert_pem = cert.public_bytes(
|
||||
serialization.Encoding.PEM).decode()
|
||||
publicKeyData = b64encode(cert_pem.encode())
|
||||
if isinstance(publicKeyData, bytes):
|
||||
publicKeyData = publicKeyData.decode()
|
||||
return publicKeyData
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
|
||||
|
||||
def get_serial_number(self):
|
||||
try:
|
||||
_, _, info = connect_to_device(self.serial_number)
|
||||
return info.serial
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
|
||||
def reset_piv(self):
|
||||
'''Resets YubiKey PIV app and generates new key for GAM to use.'''
|
||||
reply = str(input('This will wipe all PIV keys and configuration from your YubiKey. Are you sure? (y/N) ').lower().strip())
|
||||
if reply != 'y':
|
||||
sys.exit(1)
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
piv = PivSession(conn)
|
||||
piv.reset()
|
||||
rnd = SystemRandom()
|
||||
pin_puk_chars = string.ascii_letters + string.digits + string.punctuation
|
||||
new_puk = ''.join(rnd.choice(pin_puk_chars) for _ in range(8))
|
||||
new_pin = ''.join(rnd.choice(pin_puk_chars) for _ in range(8))
|
||||
piv.change_puk('12345678', new_puk)
|
||||
piv.change_pin('123456', new_pin)
|
||||
print(f'PIN set to: {new_pin}')
|
||||
piv.authenticate(MANAGEMENT_KEY_TYPE.TDES,
|
||||
DEFAULT_MANAGEMENT_KEY)
|
||||
|
||||
piv.verify_pin(new_pin)
|
||||
print('YubiKey is generating a non-exportable private key...')
|
||||
pubkey = piv.generate_key(SLOT.AUTHENTICATION,
|
||||
KEY_TYPE.RSA2048,
|
||||
PIN_POLICY.ALWAYS,
|
||||
TOUCH_POLICY.NEVER)
|
||||
now = datetime.datetime.utcnow()
|
||||
valid_to = now + datetime.timedelta(days=36500)
|
||||
subject = 'CN=GAM Created Key'
|
||||
piv.authenticate(MANAGEMENT_KEY_TYPE.TDES,
|
||||
DEFAULT_MANAGEMENT_KEY)
|
||||
piv.verify_pin(new_pin)
|
||||
cert = generate_self_signed_certificate(piv,
|
||||
SLOT.AUTHENTICATION,
|
||||
pubkey,
|
||||
subject,
|
||||
now,
|
||||
valid_to)
|
||||
piv.put_certificate(SLOT.AUTHENTICATION,
|
||||
cert)
|
||||
piv.put_object(OBJECT_ID.CHUID,
|
||||
generate_chuid())
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(8, f'YubiKey - {err}')
|
||||
|
||||
|
||||
def sign(self, message):
|
||||
if 'mplock' in globals():
|
||||
mplock.acquire()
|
||||
try:
|
||||
conn = self._connect()
|
||||
with conn:
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
controlflow.system_error_exit(7, f'YubiKey - {err}')
|
||||
try:
|
||||
signed = session.sign(slot=self.slot,
|
||||
key_type=self.key_type,
|
||||
message=message,
|
||||
hash_algorithm=hashes.SHA256(),
|
||||
padding=padding.PKCS1v15())
|
||||
except ApduError as err:
|
||||
controlflow.system_error_exit(8, f'YubiKey - {err}')
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
if 'mplock' in globals():
|
||||
mplock.release()
|
||||
return signed
|
||||
@@ -65,9 +65,12 @@ def csv_field_error_exit(field_name, field_names):
|
||||
','.join(field_names)))
|
||||
|
||||
|
||||
def invalid_json_exit(file_name):
|
||||
"""Raises a sysyem exit when invalid JSON content is encountered."""
|
||||
system_error_exit(17, MESSAGE_INVALID_JSON.format(file_name))
|
||||
def invalid_json_exit(file_name, err=None):
|
||||
"""Raises a system exit when invalid JSON content is encountered."""
|
||||
err_msg = MESSAGE_INVALID_JSON.format(file_name)
|
||||
if err:
|
||||
err_msg += f'\n\n{err}'
|
||||
system_error_exit(17, err_msg)
|
||||
|
||||
|
||||
def wait_on_failure(current_attempt_num,
|
||||
|
||||
@@ -154,39 +154,66 @@ def write_csv_file(csvRows, titles, list_type, todrive):
|
||||
return True
|
||||
return False
|
||||
|
||||
if GC_Values[GC_CSV_ROW_FILTER]:
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
|
||||
if column not in titles:
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row filter column "{column}" is not in output columns\n'
|
||||
)
|
||||
continue
|
||||
if filterVal[0] == 'regex':
|
||||
csvRows = [
|
||||
row for row in csvRows
|
||||
if filterVal[1].search(str(row.get(column, '')))
|
||||
]
|
||||
elif filterVal[0] == 'notregex':
|
||||
csvRows = [
|
||||
row for row in csvRows
|
||||
if not filterVal[1].search(str(row.get(column, '')))
|
||||
]
|
||||
elif filterVal[0] in ['date', 'time']:
|
||||
csvRows = [
|
||||
row for row in csvRows if rowDateTimeFilterMatch(
|
||||
filterVal[0] == 'date', row.get(column, ''),
|
||||
filterVal[1], filterVal[2])
|
||||
]
|
||||
elif filterVal[0] == 'count':
|
||||
csvRows = [
|
||||
row for row in csvRows if rowCountFilterMatch(
|
||||
row.get(column, 0), filterVal[1], filterVal[2])
|
||||
]
|
||||
else: #boolean
|
||||
csvRows = [
|
||||
row for row in csvRows if rowBooleanFilterMatch(
|
||||
row.get(column, False), filterVal[1])
|
||||
]
|
||||
def rowFilterMatch(filters, columns, row):
|
||||
for c, filterVal in iter(filters.items()):
|
||||
for column in columns[c]:
|
||||
if filterVal[1] == 'regex':
|
||||
if not filterVal[2].search(str(row.get(column, ''))):
|
||||
return False
|
||||
elif filterVal[1] == 'notregex':
|
||||
if filterVal[2].search(str(row.get(column, ''))):
|
||||
return False
|
||||
elif filterVal[1] in ['date', 'time']:
|
||||
if not rowDateTimeFilterMatch(
|
||||
filterVal[1] == 'date', row.get(column, ''),
|
||||
filterVal[2], filterVal[3]):
|
||||
return False
|
||||
elif filterVal[1] == 'count':
|
||||
if not rowCountFilterMatch(
|
||||
row.get(column, 0), filterVal[2], filterVal[3]):
|
||||
return False
|
||||
else: #boolean
|
||||
if not rowBooleanFilterMatch(
|
||||
row.get(column, False), filterVal[2]):
|
||||
return False
|
||||
return True
|
||||
|
||||
if GC_Values[GC_CSV_ROW_FILTER] or GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
||||
if GC_Values[GC_CSV_ROW_FILTER]:
|
||||
keepColumns = {}
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
|
||||
columns = [t for t in titles if filterVal[0].match(t)]
|
||||
if columns:
|
||||
keepColumns[column] = columns
|
||||
else:
|
||||
keepColumns[column] = [None]
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row filter column pattern "{column}" does not match any output columns\n'
|
||||
)
|
||||
else:
|
||||
keepColumns = None
|
||||
if GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
||||
dropColumns = {}
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_DROP_FILTER].items()):
|
||||
columns = [t for t in titles if filterVal[0].match(t)]
|
||||
if columns:
|
||||
dropColumns[column] = columns
|
||||
else:
|
||||
dropColumns[column] = [None]
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row drop filter column pattern "{column}" does not match any output columns\n'
|
||||
)
|
||||
else:
|
||||
dropColumns = None
|
||||
rows = []
|
||||
for row in csvRows:
|
||||
if (((keepColumns is None) or
|
||||
rowFilterMatch(GC_Values[GC_CSV_ROW_FILTER], keepColumns, row)) and
|
||||
((dropColumns is None) or
|
||||
not rowFilterMatch(GC_Values[GC_CSV_ROW_DROP_FILTER], dropColumns, row))):
|
||||
rows.append(row)
|
||||
csvRows = rows
|
||||
|
||||
if GC_Values[GC_CSV_HEADER_FILTER] or GC_Values[GC_CSV_HEADER_DROP_FILTER]:
|
||||
if GC_Values[GC_CSV_HEADER_DROP_FILTER]:
|
||||
titles = [
|
||||
@@ -256,7 +283,8 @@ and follow recommend steps to authorize GAM for Drive access.''')
|
||||
if GC_Values[GC_NO_BROWSER]:
|
||||
msg_txt = f'Drive file uploaded to:\n {file_url}'
|
||||
msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}'
|
||||
gam.send_email(msg_subj, msg_txt)
|
||||
if not GC_Values[GC_NO_TDEMAIL]:
|
||||
gam.send_email(msg_subj, msg_txt)
|
||||
print(msg_txt)
|
||||
else:
|
||||
webbrowser.open(file_url)
|
||||
@@ -288,7 +316,7 @@ def print_json(object_value, spacing=''):
|
||||
sys.stdout.write(f' {spacing}{i+1}) ')
|
||||
print_json(a_value, f' {spacing}')
|
||||
elif isinstance(object_value, dict):
|
||||
for key in ['kind', 'etag', 'etags']:
|
||||
for key in ['kind', 'etag', 'etags', '@type']:
|
||||
object_value.pop(key, None)
|
||||
for another_object, another_value in object_value.items():
|
||||
sys.stdout.write(f' {spacing}{another_object}: ')
|
||||
|
||||
@@ -218,6 +218,61 @@ def got_total_items_first_last_msg(items):
|
||||
return f'Got {TOTAL_ITEMS_MARKER} {items}: {FIRST_ITEM_MARKER} - {LAST_ITEM_MARKER}' + '\n'
|
||||
|
||||
|
||||
def process_page(page, items, all_items, total_items, page_message, message_attribute):
|
||||
"""Process one page of a Google service function response.
|
||||
|
||||
Append a list of items to the aggregate list of items
|
||||
|
||||
Args:
|
||||
page: list of items
|
||||
items: see get_all_pages
|
||||
all_items: aggregate list of items
|
||||
total_items: length of all_items
|
||||
page_message: see get_all_pages
|
||||
message_attribute: get_all_pages
|
||||
Returns:
|
||||
The page token and total number of items
|
||||
"""
|
||||
if page:
|
||||
page_token = page.get('nextPageToken')
|
||||
page_items = page.get(items, [])
|
||||
num_page_items = len(page_items)
|
||||
total_items += num_page_items
|
||||
if all_items is not None:
|
||||
all_items.extend(page_items)
|
||||
else:
|
||||
page_token = None
|
||||
num_page_items = 0
|
||||
|
||||
# Show a paging message to the user that indicates paging progress
|
||||
if page_message:
|
||||
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
|
||||
str(total_items))
|
||||
if message_attribute:
|
||||
first_item = page_items[0] if num_page_items > 0 else {}
|
||||
last_item = page_items[-1] if num_page_items > 1 else first_item
|
||||
if isinstance(message_attribute, str):
|
||||
first_item = str(first_item.get(message_attribute, ''))
|
||||
last_item = str(last_item.get(message_attribute, ''))
|
||||
else:
|
||||
for attr in message_attribute:
|
||||
first_item = first_item.get(attr, {})
|
||||
last_item = last_item.get(attr, {})
|
||||
first_item = str(first_item)
|
||||
last_item = str(last_item)
|
||||
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
|
||||
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
|
||||
sys.stderr.write('\r')
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(show_message)
|
||||
return (page_token, total_items)
|
||||
|
||||
def finalize_page_message(page_message):
|
||||
""" Issue final page_message """
|
||||
if page_message and (page_message[-1] != '\n'):
|
||||
sys.stderr.write('\r\n')
|
||||
sys.stderr.flush()
|
||||
|
||||
def get_all_pages(service,
|
||||
function,
|
||||
items='items',
|
||||
@@ -226,6 +281,7 @@ def get_all_pages(service,
|
||||
soft_errors=False,
|
||||
throw_reasons=None,
|
||||
retry_reasons=None,
|
||||
page_args_in_body=False,
|
||||
**kwargs):
|
||||
"""Aggregates and returns all pages of a Google service function response.
|
||||
|
||||
@@ -256,15 +312,22 @@ def get_all_pages(service,
|
||||
retry_reasons: A list of Google HTTP error reason strings indicating which
|
||||
error should be retried, using exponential backoff techniques, when the
|
||||
error reason is encountered.
|
||||
page_args_in_body: Some APIs like Chrome Policy want pageToken and pageSize
|
||||
in the body.
|
||||
**kwargs: Additional params to pass to the request method.
|
||||
|
||||
Returns:
|
||||
A list of all items received from all paged responses.
|
||||
"""
|
||||
if 'maxResults' not in kwargs and 'pageSize' not in kwargs:
|
||||
if page_args_in_body:
|
||||
kwargs.setdefault('body', {})
|
||||
if 'maxResults' not in kwargs and 'pageSize' not in kwargs and 'pageSize' not in kwargs.get('body', {}):
|
||||
page_key = _get_max_page_size_for_api_call(service, function, **kwargs)
|
||||
if page_key:
|
||||
kwargs.update(page_key)
|
||||
if page_args_in_body:
|
||||
kwargs['body'].update(page_key)
|
||||
else:
|
||||
kwargs.update(page_key)
|
||||
all_items = []
|
||||
page_token = None
|
||||
total_items = 0
|
||||
@@ -274,46 +337,15 @@ def get_all_pages(service,
|
||||
soft_errors=soft_errors,
|
||||
throw_reasons=throw_reasons,
|
||||
retry_reasons=retry_reasons,
|
||||
pageToken=page_token,
|
||||
**kwargs)
|
||||
if page:
|
||||
page_token = page.get('nextPageToken')
|
||||
page_items = page.get(items, [])
|
||||
num_page_items = len(page_items)
|
||||
total_items += num_page_items
|
||||
all_items.extend(page_items)
|
||||
else:
|
||||
page_token = None
|
||||
num_page_items = 0
|
||||
|
||||
# Show a paging message to the user that indicates paging progress
|
||||
if page_message:
|
||||
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
|
||||
str(total_items))
|
||||
if message_attribute:
|
||||
first_item = page_items[0] if num_page_items > 0 else {}
|
||||
last_item = page_items[-1] if num_page_items > 1 else first_item
|
||||
if type(message_attribute) is str:
|
||||
first_item = str(first_item.get(message_attribute, ''))
|
||||
last_item = str(last_item.get(message_attribute, ''))
|
||||
else:
|
||||
for attr in message_attribute:
|
||||
first_item = first_item.get(attr, {})
|
||||
last_item = last_item.get(attr, {})
|
||||
first_item = str(first_item)
|
||||
last_item = str(last_item)
|
||||
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
|
||||
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
|
||||
sys.stderr.write('\r')
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(show_message)
|
||||
|
||||
page_token, total_items = process_page(page, items, all_items, total_items, page_message, message_attribute)
|
||||
if not page_token:
|
||||
# End the paging status message and return all items.
|
||||
if page_message and (page_message[-1] != '\n'):
|
||||
sys.stderr.write('\r\n')
|
||||
sys.stderr.flush()
|
||||
finalize_page_message(page_message)
|
||||
return all_items
|
||||
if page_args_in_body:
|
||||
kwargs['body']['pageToken'] = page_token
|
||||
else:
|
||||
kwargs['pageToken'] = page_token
|
||||
|
||||
|
||||
# TODO: Make this private once all execution related items that use this method
|
||||
|
||||
@@ -8,6 +8,7 @@ from unittest.mock import patch
|
||||
from gam import SetGlobalVariables
|
||||
import gam.gapi as gapi
|
||||
from gam.gapi import errors
|
||||
import httplib2
|
||||
|
||||
|
||||
def create_http_error(status, reason, message):
|
||||
@@ -21,10 +22,10 @@ def create_http_error(status, reason, message):
|
||||
Returns:
|
||||
googleapiclient.errors.HttpError
|
||||
"""
|
||||
response = {
|
||||
response = httplib2.Response({
|
||||
'status': status,
|
||||
'content-type': 'application/json',
|
||||
}
|
||||
})
|
||||
content = {
|
||||
'error': {
|
||||
'code': status,
|
||||
|
||||
@@ -154,7 +154,11 @@ def delACL():
|
||||
gapi.call(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId)
|
||||
else:
|
||||
body = {'role': 'none'}
|
||||
_getCalendarACLScope(5, body)
|
||||
i = 4
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in CALENDAR_ACL_ROLES_MAP:
|
||||
i += 1
|
||||
_getCalendarACLScope(i, body)
|
||||
print(f'Calendar: {calendarId}, Delete ACL: {formatACLScope(body)}')
|
||||
gapi.call(cal.acl(),
|
||||
'insert',
|
||||
@@ -824,7 +828,7 @@ def _showCalendar(userCalendar, j, jcount):
|
||||
print(f' Color ID: {userCalendar["colorId"]}, ' \
|
||||
f'Background Color: {userCalendar["backgroundColor"]}, ' \
|
||||
f'Foreground Color: {userCalendar["foregroundColor"]}')
|
||||
print(f' Default Reminders:')
|
||||
print(' Default Reminders:')
|
||||
for reminder in userCalendar.get('defaultReminders', []):
|
||||
print(f' Method: {reminder["method"]}, ' \
|
||||
f'Minutes: {reminder["minutes"]}')
|
||||
|
||||
303
src/gam/gapi/cbcm.py
Normal file
303
src/gam/gapi/cbcm.py
Normal file
@@ -0,0 +1,303 @@
|
||||
"""Chrome Browser Cloud Management API calls"""
|
||||
|
||||
import csv
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer id without C prefix'''
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id[0] == 'C':
|
||||
customer_id = customer_id[1:]
|
||||
return customer_id
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('cbcm')
|
||||
|
||||
|
||||
def delete():
|
||||
cbcm = build()
|
||||
device_id = sys.argv[3]
|
||||
customer_id = _get_customerid()
|
||||
gapi.call(cbcm.chromebrowsers(), 'delete', deviceId=device_id,
|
||||
customer=customer_id)
|
||||
print(f'Deleted browser {device_id}')
|
||||
|
||||
|
||||
def info():
|
||||
cbcm = build()
|
||||
device_id = sys.argv[3]
|
||||
projection = 'BASIC'
|
||||
fields = None
|
||||
customer_id = _get_customerid()
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['basic', 'full']:
|
||||
projection = myarg.upper()
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam info browser')
|
||||
browser = gapi.call(cbcm.chromebrowsers(), 'get',
|
||||
customer=customer_id,
|
||||
fields=fields, deviceId=device_id,
|
||||
projection=projection)
|
||||
display.print_json(browser)
|
||||
|
||||
|
||||
def move():
|
||||
cbcm = build()
|
||||
body = {'resource_ids': []}
|
||||
customer_id = _get_customerid()
|
||||
i = 3
|
||||
resource_ids = []
|
||||
batch_size = 600
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'ids':
|
||||
resource_ids.extend(sys.argv[i + 1].split(','))
|
||||
i += 2
|
||||
elif myarg == 'query':
|
||||
query = sys.argv[i + 1]
|
||||
page_message = gapi.got_total_items_msg('Browsers', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
|
||||
'browsers', page_message=page_message,
|
||||
customer=customer_id,
|
||||
query=query, projection='BASIC',
|
||||
fields='browsers(deviceId),nextPageToken')
|
||||
ids = [browser['deviceId'] for browser in browsers]
|
||||
resource_ids.extend(ids)
|
||||
i += 2
|
||||
elif myarg == 'file':
|
||||
with fileutils.open_file(sys.argv[i+1], strip_utf_bom=True) as filed:
|
||||
for row in filed:
|
||||
rid = row.strip()
|
||||
if rid:
|
||||
resource_ids.append(rid)
|
||||
i += 2
|
||||
elif myarg == 'csvfile':
|
||||
drive, fname_column = os.path.splitdrive(sys.argv[i+1])
|
||||
if fname_column.find(':') == -1:
|
||||
controlflow.system_error_exit(
|
||||
2, 'Expected csvfile FileName:FieldName')
|
||||
(filename, column) = fname_column.split(':')
|
||||
with fileutils.open_file(drive + filename) as filed:
|
||||
input_file = csv.DictReader(filed, restval='')
|
||||
if column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(column,
|
||||
input_file.fieldnames)
|
||||
for row in input_file:
|
||||
rid = row[column].strip()
|
||||
if rid:
|
||||
resource_ids.append(rid)
|
||||
i += 2
|
||||
elif myarg in ['ou', 'orgunit', 'org']:
|
||||
org_unit = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
body['org_unit_path'] = org_unit
|
||||
i += 2
|
||||
elif myarg == 'batchsize':
|
||||
batch_size = int(sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam move browsers')
|
||||
if 'org_unit_path' not in body:
|
||||
controlflow.missing_argument_exit('ou', 'gam move browsers')
|
||||
elif not resource_ids:
|
||||
controlflow.missing_argument_exit('query or ids',
|
||||
'gam move browsers')
|
||||
# split moves into max 600 devices per batch
|
||||
for chunk in range(0, len(resource_ids), batch_size):
|
||||
body['resource_ids'] = resource_ids[chunk:chunk + batch_size]
|
||||
print(f' moving {len(body["resource_ids"])} browsers to ' \
|
||||
f'{body["org_unit_path"]}')
|
||||
gapi.call(cbcm.chromebrowsers(), 'moveChromeBrowsersToOu',
|
||||
customer=customer_id, body=body)
|
||||
|
||||
|
||||
def print_():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
projection = 'BASIC'
|
||||
orgUnitPath = query = None
|
||||
fields = None
|
||||
titles = []
|
||||
csv_rows = []
|
||||
todrive = False
|
||||
sort_headers = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'query':
|
||||
query = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1], pathOnly=True, absolutePath=True)
|
||||
i += 2
|
||||
elif myarg == 'projection':
|
||||
projection = sys.argv[i + 1].upper()
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'sortheaders':
|
||||
sort_headers = True
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i + 1].replace(',', ' ').split()
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print browsers')
|
||||
if fields:
|
||||
fields.append('deviceId')
|
||||
fields = f'browsers({",".join(set(fields))}),nextPageToken'
|
||||
page_message = gapi.got_total_items_msg('Browsers', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
|
||||
'browsers', page_message=page_message,
|
||||
customer=customer_id,
|
||||
orgUnitPath=orgUnitPath, query=query, projection=projection,
|
||||
fields=fields)
|
||||
for browser in browsers:
|
||||
browser = utils.flatten_json(browser)
|
||||
for a_key in browser:
|
||||
if a_key not in titles:
|
||||
titles.append(a_key)
|
||||
csv_rows.append(browser)
|
||||
if sort_headers:
|
||||
display.sort_csv_titles(['deviceId',], titles)
|
||||
display.write_csv_file(csv_rows, titles, 'Browsers', todrive)
|
||||
|
||||
|
||||
attributes = {
|
||||
'assetid': 'annotatedAssetId',
|
||||
'location': 'annotatedLocation',
|
||||
'notes': 'annotatedNotes',
|
||||
'user': 'annotatedUser'
|
||||
}
|
||||
attribute_fields = ','.join(list(attributes.values()))
|
||||
|
||||
def update():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
device_id = sys.argv[3]
|
||||
body = {'deviceId': device_id}
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in attributes:
|
||||
body[attributes[myarg]] = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam update browser')
|
||||
browser = gapi.call(cbcm.chromebrowsers(), 'get', deviceId=device_id,
|
||||
customer=customer_id,
|
||||
projection='BASIC', fields=attribute_fields)
|
||||
browser.update(body)
|
||||
result = gapi.call(cbcm.chromebrowsers(), 'update', deviceId=device_id,
|
||||
customer=customer_id, body=browser,
|
||||
projection='BASIC', fields="deviceId")
|
||||
print(f'Updated browser {result["deviceId"]}')
|
||||
|
||||
|
||||
def createtoken():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
body = {'token_type': 'CHROME_BROWSER'}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'orgunit', 'org']:
|
||||
body['org_unit_path'] = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['expire', 'expires']:
|
||||
body['expire_time'] = utils.get_time_or_delta_from_now(sys.argv[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam create browsertoken')
|
||||
browser = gapi.call(cbcm.enrollmentTokens(), 'create',
|
||||
customer=customer_id, body=body)
|
||||
print(f'Created browser enrollment token {browser["token"]}')
|
||||
|
||||
|
||||
def revoketoken():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
token_permanent_id = sys.argv[3]
|
||||
gapi.call(cbcm.enrollmentTokens(), 'revoke', tokenPermanentId=token_permanent_id,
|
||||
customer=customer_id)
|
||||
print(f'Deleted browser enrollment token {token_permanent_id}')
|
||||
|
||||
|
||||
def printshowtokens(csvFormat):
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
query = None
|
||||
fields = None
|
||||
if csvFormat:
|
||||
titles = ['token']
|
||||
csv_rows = []
|
||||
todrive = False
|
||||
sort_headers = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'query':
|
||||
query = sys.argv[i+1]
|
||||
i += 2
|
||||
elif csvFormat and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif csvFormat and myarg == 'sortheaders':
|
||||
sort_headers = True
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i + 1].replace(',', ' ').split()
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
f"gam {['show', 'print'][csvFormat]} browsertokens")
|
||||
if fields:
|
||||
fields.append('token')
|
||||
fields = f'chromeEnrollmentTokens({",".join(set(fields))}),nextPageToken'
|
||||
page_message = gapi.got_total_items_msg('Chrome Browser Enrollment Tokens', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.enrollmentTokens(), 'list',
|
||||
'chromeEnrollmentTokens', page_message=page_message,
|
||||
customer=customer_id,
|
||||
query=query, fields=fields)
|
||||
if not csvFormat:
|
||||
count = len(browsers)
|
||||
print(f'Show {count} Chrome Browser Enrollment Tokens')
|
||||
i = 0
|
||||
for browser in browsers:
|
||||
i += 1
|
||||
print(f' Chrome Browser Enrollment Token: {browser["token"]}{gam.currentCount(i, count)}')
|
||||
browser.pop('kind', None)
|
||||
for field in browser:
|
||||
print(f' {field}: {browser[field]}')
|
||||
else:
|
||||
for browser in browsers:
|
||||
browser = utils.flatten_json(browser)
|
||||
for a_key in browser:
|
||||
if a_key not in titles:
|
||||
titles.append(a_key)
|
||||
csv_rows.append(browser)
|
||||
if sort_headers:
|
||||
display.sort_csv_titles(['token',], titles)
|
||||
display.write_csv_file(csv_rows, titles, 'Chrome Browser Enrollment Tokens', todrive)
|
||||
207
src/gam/gapi/chat.py
Normal file
207
src/gam/gapi/chat.py
Normal file
@@ -0,0 +1,207 @@
|
||||
import sys
|
||||
|
||||
import googleapiclient.errors
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
|
||||
# Chat scope isn't in discovery doc so need to manually set
|
||||
CHAT_SCOPES = ['https://www.googleapis.com/auth/chat.bot']
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIServiceObject('chat',
|
||||
act_as=None,
|
||||
scopes=CHAT_SCOPES)
|
||||
|
||||
|
||||
THROW_REASONS = [
|
||||
gapi_errors.ErrorReason.FOUR_O_FOUR, # Chat API not configured
|
||||
]
|
||||
|
||||
def _chat_error_handler(chat, err):
|
||||
if err.status_code == 404:
|
||||
project_id = chat._http.credentials.project_id
|
||||
url = f'https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat?project={project_id}'
|
||||
print('ERROR: you need to configure Google Chat for your API project. Please go to:')
|
||||
print()
|
||||
print(f' {url}')
|
||||
print()
|
||||
print('and complete all fields.')
|
||||
else:
|
||||
raise err
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_spaces():
|
||||
chat = build()
|
||||
todrive = False
|
||||
i =3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam print chatspaces')
|
||||
try:
|
||||
spaces = gapi.get_all_pages(chat.spaces(), 'list', 'spaces', throw_reasons=THROW_REASONS)
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
_chat_error_handler(chat, err)
|
||||
if not spaces:
|
||||
print('Bot not added to any Chat rooms or users yet.')
|
||||
else:
|
||||
display.write_csv_file(spaces, spaces[0].keys(), 'Chat Spaces', todrive)
|
||||
|
||||
|
||||
def print_members():
|
||||
chat = build()
|
||||
space = None
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'space':
|
||||
space = sys.argv[i+1]
|
||||
if space[:7] != 'spaces/':
|
||||
space = f'spaces/{space}'
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, "gam print chatmembers")
|
||||
if not space:
|
||||
controlflow.system_error_exit(2,
|
||||
'space <ChatSpace> is required.')
|
||||
try:
|
||||
results = gapi.get_all_pages(chat.spaces().members(), 'list', 'memberships', parent=space)
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
_chat_error_handler(chat, err)
|
||||
members = []
|
||||
titles = []
|
||||
for result in results:
|
||||
member = utils.flatten_json(result)
|
||||
for key in member:
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
members.append(utils.flatten_json(result))
|
||||
display.write_csv_file(members, titles, 'Chat Members', todrive)
|
||||
|
||||
|
||||
def create_message():
|
||||
chat = build()
|
||||
body = {}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'text':
|
||||
body['text'] = sys.argv[i+1].replace('\\r', '\r').replace('\\n', '\n')
|
||||
i += 2
|
||||
elif myarg == 'textfile':
|
||||
filename = sys.argv[i + 1]
|
||||
i, encoding = gam.getCharSet(i + 2)
|
||||
body['text'] = fileutils.read_file(filename, encoding=encoding)
|
||||
elif myarg == 'space':
|
||||
space = sys.argv[i+1]
|
||||
if space[:7] != 'spaces/':
|
||||
space = f'spaces/{space}'
|
||||
i += 2
|
||||
elif myarg == 'thread':
|
||||
body['thread'] = {'name': sys.argv[i+1]}
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, "gam create chat")
|
||||
if not space:
|
||||
controlflow.system_error_exit(2,
|
||||
'space <ChatSpace> is required.')
|
||||
if 'text' not in body:
|
||||
controlflow.system_error_exit(2,
|
||||
'text <String> or textfile <FileName> is required.')
|
||||
if len(body['text']) > 4096:
|
||||
body['text'] = body['text'][:4095]
|
||||
print('WARNING: trimmed message longer than 4k to be 4k in length.')
|
||||
try:
|
||||
resp = gapi.call(chat.spaces().messages(),
|
||||
'create',
|
||||
parent=space,
|
||||
body=body,
|
||||
throw_reasons=THROW_REASONS)
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
_chat_error_handler(chat, err)
|
||||
if 'thread' in body:
|
||||
print(f'responded to thread {resp["thread"]["name"]}')
|
||||
else:
|
||||
print(f'started new thread {resp["thread"]["name"]}')
|
||||
print(f'message {resp["name"]}')
|
||||
|
||||
def delete_message():
|
||||
chat = build()
|
||||
name = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'name':
|
||||
name = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, "gam delete chat")
|
||||
if not name:
|
||||
controlflow.system_error_exit(2,
|
||||
'name <String> is required.')
|
||||
try:
|
||||
gapi.call(chat.spaces().messages(),
|
||||
'delete',
|
||||
name=name)
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
_chat_error_handler(chat, err)
|
||||
|
||||
|
||||
def update_message():
|
||||
chat = build()
|
||||
body = {}
|
||||
name = None
|
||||
updateMask = 'text'
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'text':
|
||||
body['text'] = sys.argv[i+1].replace('\\r', '\r').replace('\\n', '\n')
|
||||
i += 2
|
||||
elif myarg == 'textfile':
|
||||
filename = sys.argv[i + 1]
|
||||
i, encoding = gam.getCharSet(i + 2)
|
||||
body['text'] = fileutils.read_file(filename, encoding=encoding)
|
||||
elif myarg == 'name':
|
||||
name = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, "gam update chat")
|
||||
if not name:
|
||||
controlflow.system_error_exit(2,
|
||||
'name <String> is required.')
|
||||
if 'text' not in body:
|
||||
controlflow.system_error_exit(2,
|
||||
'text <String> or textfile <FileName> is required.')
|
||||
if len(body['text']) > 4096:
|
||||
body['text'] = body['text'][:4095]
|
||||
print('WARNING: trimmed message longer than 4k to be 4k in length.')
|
||||
try:
|
||||
resp = gapi.call(chat.spaces().messages(),
|
||||
'update',
|
||||
name=name,
|
||||
updateMask=updateMask,
|
||||
body=body)
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
_chat_error_handler(chat, err)
|
||||
if 'thread' in body:
|
||||
print(f'updated response to thread {resp["thread"]["name"]}')
|
||||
else:
|
||||
print(f'updated message on thread {resp["thread"]["name"]}')
|
||||
print(f'message {resp["name"]}')
|
||||
229
src/gam/gapi/chromehistory.py
Normal file
229
src/gam/gapi/chromehistory.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""Chrome Version History API calls"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam import utils
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObjectNoAuthentication('versionhistory')
|
||||
|
||||
|
||||
CHROME_HISTORY_ENTITY_CHOICES = {
|
||||
'platforms',
|
||||
'channels',
|
||||
'versions',
|
||||
'releases',
|
||||
}
|
||||
|
||||
CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP = {
|
||||
'versions': {
|
||||
'channel': 'channel',
|
||||
'name': 'name',
|
||||
'platform': 'platform',
|
||||
'version': 'version'
|
||||
},
|
||||
'releases': {
|
||||
'channel': 'channel',
|
||||
'endtime': 'endtime',
|
||||
'fraction': 'fraction',
|
||||
'name': 'name',
|
||||
'platform': 'platform',
|
||||
'starttime': 'starttime',
|
||||
'version': 'version'
|
||||
}
|
||||
}
|
||||
|
||||
CHROME_VERSIONHISTORY_TITLES = {
|
||||
'platforms': ['platform'],
|
||||
'channels': ['channel', 'platform'],
|
||||
'versions': ['version', 'channel', 'platform',
|
||||
'major_version', 'minor_version', 'build', 'patch'],
|
||||
'releases': ['version', 'channel', 'platform',
|
||||
'major_version', 'minor_version', 'build', 'patch',
|
||||
'fraction', 'serving.startTime','serving.endTime']
|
||||
}
|
||||
|
||||
def get_relative_milestone(channel='stable', minus=0):
|
||||
'''
|
||||
takes a channel and minus like stable and -1.
|
||||
returns current given milestone number
|
||||
'''
|
||||
cv = build()
|
||||
parent = f'chrome/platforms/all/channels/{channel}/versions/all'
|
||||
releases = gapi.get_all_pages(cv.platforms().channels().versions().releases(),
|
||||
'list',
|
||||
'releases',
|
||||
parent=parent,
|
||||
fields='releases/version,nextPageToken')
|
||||
milestones = []
|
||||
# Note that milestones are usually sequential but some numbers
|
||||
# may be skipped. For example, there was no Chrome 82 stable.
|
||||
# Thus we need to do more than find the latest version and subtract.
|
||||
for release in releases:
|
||||
milestone = release.get('version').split('.')[0]
|
||||
if milestone not in milestones:
|
||||
milestones.append(milestone)
|
||||
milestones.sort(reverse=True)
|
||||
try:
|
||||
return milestones[minus]
|
||||
except IndexError:
|
||||
return ''
|
||||
|
||||
def get_platform_map(cv=None):
|
||||
'''returns dict mapping of platform choices'''
|
||||
if cv is None:
|
||||
cv = build()
|
||||
result = gapi.get_all_pages(cv.platforms(),
|
||||
'list',
|
||||
'platforms',
|
||||
parent='chrome')
|
||||
platforms = [p.get('platformType', '').lower() for p in result]
|
||||
platform_map = {'all': 'all'}
|
||||
for cplatform in platforms:
|
||||
key = cplatform.replace('_', '')
|
||||
platform_map[key] = cplatform
|
||||
return platform_map
|
||||
|
||||
|
||||
def get_channel_map(cv=None):
|
||||
'''returns dict mapping of channel choices'''
|
||||
if cv is None:
|
||||
cv = build()
|
||||
result = gapi.get_all_pages(cv.platforms().channels(),
|
||||
'list',
|
||||
'channels',
|
||||
parent='chrome/platforms/all')
|
||||
channels = [c.get('channelType', '').lower() for c in result]
|
||||
channels = list(set(channels))
|
||||
channel_map = {'all': 'all'}
|
||||
for channel in channels:
|
||||
key = channel.replace('_', '')
|
||||
channel_map[key] = channel
|
||||
return channel_map
|
||||
|
||||
def printHistory():
|
||||
cv = build()
|
||||
entityType = sys.argv[3].lower().replace('_', '')
|
||||
if entityType not in CHROME_HISTORY_ENTITY_CHOICES:
|
||||
msg = f'{entityType} is not a valid argument to "gam print chromehistory"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
todrive = False
|
||||
csvRows = []
|
||||
cplatform = 'all'
|
||||
channel = 'all'
|
||||
version = 'all'
|
||||
kwargs = {}
|
||||
orderByList = []
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif entityType != 'platforms' and myarg == 'platform':
|
||||
cplatform = sys.argv[i + 1].lower().replace('_', '')
|
||||
platform_map = get_platform_map(cv)
|
||||
if cplatform not in platform_map:
|
||||
controlflow.expected_argument_exit('platform',
|
||||
', '.join(platform_map),
|
||||
cplatform)
|
||||
cplatform = platform_map[cplatform]
|
||||
i += 2
|
||||
elif entityType in {'versions', 'releases'} and myarg == 'channel':
|
||||
channel = sys.argv[i + 1].lower().replace('_', '')
|
||||
channel_map = get_channel_map(cv)
|
||||
if channel not in channel_map:
|
||||
controlflow.expected_argument_exit('channel',
|
||||
', '.join(channel_map),
|
||||
channel)
|
||||
channel = channel_map[channel]
|
||||
i += 2
|
||||
elif entityType == 'releases' and myarg == 'version':
|
||||
version = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif entityType in {'versions', 'releases'} and myarg == 'orderby':
|
||||
fieldName = sys.argv[i + 1].lower().replace('_', '')
|
||||
i += 2
|
||||
if fieldName in CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType]:
|
||||
fieldName = CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType][fieldName]
|
||||
orderBy = ''
|
||||
if i < len(sys.argv):
|
||||
orderBy = sys.argv[i].lower()
|
||||
if orderBy in SORTORDER_CHOICES_MAP:
|
||||
orderBy = SORTORDER_CHOICES_MAP[orderBy]
|
||||
i += 1
|
||||
if orderBy != 'DESCENDING':
|
||||
orderByList.append(fieldName)
|
||||
else:
|
||||
orderByList.append(f'{fieldName} desc')
|
||||
else:
|
||||
controlflow.expected_argument_exit('orderby',
|
||||
', '.join(CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType]),
|
||||
fieldName)
|
||||
elif entityType in {'versions', 'releases'} and myarg == 'filter':
|
||||
kwargs['filter'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromehistory {entityType}"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if orderByList:
|
||||
kwargs['orderBy'] = ','.join(orderByList)
|
||||
if entityType == 'platforms':
|
||||
svc = cv.platforms()
|
||||
parent = 'chrome'
|
||||
elif entityType == 'channels':
|
||||
svc = cv.platforms().channels()
|
||||
parent = f'chrome/platforms/{cplatform}'
|
||||
elif entityType == 'versions':
|
||||
svc = cv.platforms().channels().versions()
|
||||
parent = f'chrome/platforms/{cplatform}/channels/{channel}'
|
||||
else: #elif entityType == 'releases'
|
||||
svc = cv.platforms().channels().versions().releases()
|
||||
parent = f'chrome/platforms/{cplatform}/channels/{channel}/versions/{version}'
|
||||
reportTitle = f'Chrome Version History {entityType.capitalize()}'
|
||||
page_message = gapi.got_total_items_msg(reportTitle, '...\n')
|
||||
gam.printGettingAllItems(reportTitle, None)
|
||||
citems = gapi.get_all_pages(svc, 'list', entityType,
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
fields=f'nextPageToken,{entityType}',
|
||||
**kwargs)
|
||||
for citem in citems:
|
||||
for key in list(citem):
|
||||
if key.endswith('Type'):
|
||||
newkey = key[:-4]
|
||||
citem[newkey] = citem.pop(key)
|
||||
if 'channel' in citem:
|
||||
citem['channel'] = citem['channel'].lower()
|
||||
else:
|
||||
channel_match = re.search(r"\/channels\/([^/]*)", citem['name'])
|
||||
if channel_match:
|
||||
try:
|
||||
citem['channel'] = channel_match.group(1)
|
||||
except IndexError:
|
||||
pass
|
||||
if 'platform' in citem:
|
||||
citem['platform'] = citem['platform'].lower()
|
||||
else:
|
||||
platform_match = re.search(r"\/platforms\/([^/]*)", citem['name'])
|
||||
if platform_match:
|
||||
try:
|
||||
citem['platform'] = platform_match.group(1)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if citem.get('version', '').count('.') == 3:
|
||||
citem['major_version'], \
|
||||
citem['minor_version'], \
|
||||
citem['build'], \
|
||||
citem['patch'] = citem['version'].split('.')
|
||||
citem.pop('name')
|
||||
csvRows.append(utils.flatten_json(citem))
|
||||
display.write_csv_file(csvRows, CHROME_VERSIONHISTORY_TITLES[entityType], reportTitle, todrive)
|
||||
265
src/gam/gapi/chromemanagement.py
Normal file
265
src/gam/gapi/chromemanagement.py
Normal file
@@ -0,0 +1,265 @@
|
||||
"""Chrome Management API calls"""
|
||||
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
|
||||
from gam.var import CROS_START_ARGUMENTS, CROS_END_ARGUMENTS
|
||||
from gam.var import YYYYMMDD_FORMAT
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam.gapi.directory.cros import _getFilterDate
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
|
||||
def _get_orgunit(orgunit):
|
||||
if orgunit.startswith('orgunits/'):
|
||||
return orgunit
|
||||
_, orgunitid = gapi_directory_orgunits.getOrgUnitId(orgunit)
|
||||
return f'{orgunitid[3:]}'
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('chromemanagement')
|
||||
|
||||
|
||||
CHROME_APPS_ORDERBY_CHOICE_MAP = {
|
||||
'appname': 'app_name',
|
||||
'apptype': 'appType',
|
||||
'installtype': 'install_type',
|
||||
'numberofpermissions': 'number_of_permissions',
|
||||
'totalinstallcount': 'total_install_count',
|
||||
}
|
||||
CHROME_APPS_TITLES = [
|
||||
'appId', 'displayName',
|
||||
'browserDeviceCount', 'osUserCount',
|
||||
'appType', 'description',
|
||||
'appInstallType', 'appSource',
|
||||
'disabled', 'homepageUri',
|
||||
'permissions'
|
||||
]
|
||||
|
||||
def printApps():
|
||||
cm = build()
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = CHROME_APPS_TITLES
|
||||
csvRows = []
|
||||
orgunit = None
|
||||
pfilter = None
|
||||
orderBy = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'filter':
|
||||
pfilter = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'orderby':
|
||||
orderBy = sys.argv[i + 1].lower().replace('_', '')
|
||||
if orderBy not in CHROME_APPS_ORDERBY_CHOICE_MAP:
|
||||
controlflow.expected_argument_exit('orderby',
|
||||
', '.join(CHROME_APPS_ORDERBY_CHOICE_MAP),
|
||||
orderBy)
|
||||
orderBy = CHROME_APPS_ORDERBY_CHOICE_MAP[orderBy]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromeapps"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if orgunit:
|
||||
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
|
||||
titles.append('orgUnitPath')
|
||||
else:
|
||||
orgUnitPath = '/'
|
||||
gam.printGettingAllItems('Chrome Installed Applications', pfilter)
|
||||
page_message = gapi.got_total_items_msg('Chrome Installed Applications', '...\n')
|
||||
apps = gapi.get_all_pages(cm.customers().reports(),
|
||||
'countInstalledApps',
|
||||
'installedApps',
|
||||
page_message=page_message,
|
||||
customer=customer, orgUnitId=orgunit,
|
||||
filter=pfilter, orderBy=orderBy)
|
||||
for app in apps:
|
||||
if orgunit:
|
||||
app['orgUnitPath'] = orgUnitPath
|
||||
if 'permissions'in app:
|
||||
app['permissions'] = ' '.join(app['permissions'])
|
||||
csvRows.append(app)
|
||||
display.write_csv_file(csvRows, titles, 'Chrome Installed Applications', todrive)
|
||||
|
||||
|
||||
CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP = {
|
||||
'extension': 'EXTENSION',
|
||||
'app': 'APP',
|
||||
'theme': 'THEME',
|
||||
'hostedapp': 'HOSTED_APP',
|
||||
'androidapp': 'ANDROID_APP',
|
||||
}
|
||||
CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP = {
|
||||
'deviceid': 'deviceId',
|
||||
'machine': 'machine',
|
||||
}
|
||||
CHROME_APP_DEVICES_TITLES = [
|
||||
'appId', 'appType', 'deviceId', 'machine'
|
||||
]
|
||||
|
||||
def printAppDevices():
|
||||
cm = build()
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = CHROME_APP_DEVICES_TITLES
|
||||
csvRows = []
|
||||
orgunit = None
|
||||
appId = None
|
||||
appType = None
|
||||
startDate = None
|
||||
endDate = None
|
||||
pfilter = None
|
||||
orderBy = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
appId = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'apptype':
|
||||
appType = sys.argv[i + 1].lower().replace('_', '')
|
||||
if appType not in CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP:
|
||||
controlflow.expected_argument_exit('orderby',
|
||||
', '.join(CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP),
|
||||
appType)
|
||||
appType = CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP[appType]
|
||||
i += 2
|
||||
elif myarg in CROS_START_ARGUMENTS:
|
||||
startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
|
||||
i += 2
|
||||
elif myarg in CROS_END_ARGUMENTS:
|
||||
endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
|
||||
i += 2
|
||||
elif myarg == 'orderby':
|
||||
orderBy = sys.argv[i + 1].lower().replace('_', '')
|
||||
if orderBy not in CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP:
|
||||
controlflow.expected_argument_exit('orderby',
|
||||
', '.join(CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP),
|
||||
orderBy)
|
||||
orderBy = CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP[orderBy]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromeappdevices"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not appId:
|
||||
controlflow.system_error_exit(3, 'You must specify an appid')
|
||||
if not appType:
|
||||
controlflow.system_error_exit(3, 'You must specify an apptype')
|
||||
if endDate:
|
||||
pfilter = f'last_active_date<={endDate}'
|
||||
if startDate:
|
||||
if pfilter:
|
||||
pfilter += ' AND '
|
||||
else:
|
||||
pfilter = ''
|
||||
pfilter += f'last_active_date>={startDate}'
|
||||
if orgunit:
|
||||
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
|
||||
titles.append('orgUnitPath')
|
||||
else:
|
||||
orgUnitPath = '/'
|
||||
gam.printGettingAllItems('Chrome Installed Application Devices', pfilter)
|
||||
page_message = gapi.got_total_items_msg('Chrome Installed Application Devices', '...\n')
|
||||
devices = gapi.get_all_pages(cm.customers().reports(),
|
||||
'findInstalledAppDevices',
|
||||
'devices',
|
||||
page_message=page_message,
|
||||
appId=appId, appType=appType,
|
||||
customer=customer, orgUnitId=orgunit,
|
||||
filter=pfilter, orderBy=orderBy)
|
||||
for device in devices:
|
||||
if orgunit:
|
||||
device['orgUnitPath'] = orgUnitPath
|
||||
device['appId'] = appId
|
||||
device['appType'] = appType
|
||||
csvRows.append(device)
|
||||
display.write_csv_file(csvRows, titles, 'Chrome Installed Application Devices', todrive)
|
||||
|
||||
|
||||
CHROME_VERSIONS_TITLES = [
|
||||
'version', 'count', 'channel', 'deviceOsVersion', 'system'
|
||||
]
|
||||
def printVersions():
|
||||
cm = build()
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = CHROME_VERSIONS_TITLES
|
||||
csvRows = []
|
||||
orgunit = None
|
||||
startDate = None
|
||||
endDate = None
|
||||
pfilter = None
|
||||
reverse = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in CROS_START_ARGUMENTS:
|
||||
startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
|
||||
i += 2
|
||||
elif myarg in CROS_END_ARGUMENTS:
|
||||
endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
|
||||
i += 2
|
||||
elif myarg == 'recentfirst':
|
||||
reverse = True
|
||||
i += 1
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromeversions"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if endDate:
|
||||
pfilter = f'last_active_date<={endDate}'
|
||||
if startDate:
|
||||
if pfilter:
|
||||
pfilter += ' AND '
|
||||
else:
|
||||
pfilter = ''
|
||||
pfilter += f'last_active_date>={startDate}'
|
||||
if orgunit:
|
||||
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
|
||||
titles.append('orgUnitPath')
|
||||
else:
|
||||
orgUnitPath = '/'
|
||||
gam.printGettingAllItems('Chrome Versions', pfilter)
|
||||
page_message = gapi.got_total_items_msg('Chrome Versions', '...\n')
|
||||
versions = gapi.get_all_pages(cm.customers().reports(),
|
||||
'countChromeVersions',
|
||||
'browserVersions',
|
||||
page_message=page_message,
|
||||
customer=customer, orgUnitId=orgunit, filter=pfilter)
|
||||
for version in sorted(versions, key=lambda k: k.get('version', 'Unknown'), reverse=reverse):
|
||||
if orgunit:
|
||||
version['orgUnitPath'] = orgUnitPath
|
||||
if 'version' not in version:
|
||||
version['version'] = 'Unknown'
|
||||
csvRows.append(version)
|
||||
display.write_csv_file(csvRows, titles, 'Chrome Versions', todrive)
|
||||
447
src/gam/gapi/chromepolicy.py
Normal file
447
src/gam/gapi/chromepolicy.py
Normal file
@@ -0,0 +1,447 @@
|
||||
"""Chrome Browser Cloud Management API calls"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import googleapiclient.errors
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
|
||||
from gam import controlflow
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import chromehistory as gapi_chromehistory
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
|
||||
def _get_orgunit(orgunit):
|
||||
if orgunit.startswith('orgunits/'):
|
||||
return orgunit
|
||||
_, orgunitid = gapi_directory_orgunits.getOrgUnitId(orgunit)
|
||||
return f'orgunits/{orgunitid[3:]}'
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('chromepolicy')
|
||||
|
||||
|
||||
def printshow_policies():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
body = {}
|
||||
namespaces = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'namespace':
|
||||
namespaces.extend(sys.argv[i+1].replace(',', ' ').split())
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromepolicy"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
body['policyTargetKey'] = {'targetResource': orgunit}
|
||||
if printer_id:
|
||||
body['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
if not namespaces:
|
||||
namespaces = ['chrome.printers']
|
||||
elif app_id:
|
||||
body['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
if not namespaces:
|
||||
namespaces = ['chrome.users.apps',
|
||||
'chrome.devices.managedGuest.apps',
|
||||
'chrome.devices.kiosk.apps']
|
||||
elif not namespaces:
|
||||
namespaces = [
|
||||
'chrome.users',
|
||||
'chrome.users.apps',
|
||||
'chrome.devices',
|
||||
'chrome.devices.kiosk',
|
||||
'chrome.devices.managedGuest',
|
||||
]
|
||||
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_O,]
|
||||
orgunitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit[9:], None)
|
||||
print(f'Organizational Unit: {orgunitPath}')
|
||||
for namespace in namespaces:
|
||||
spacing = ' '
|
||||
body['policySchemaFilter'] = f'{namespace}.*'
|
||||
body['pageToken'] = None
|
||||
try:
|
||||
policies = gapi.get_all_pages(svc.customers().policies(), 'resolve',
|
||||
items='resolvedPolicies',
|
||||
throw_reasons=throw_reasons,
|
||||
customer=customer,
|
||||
body=body,
|
||||
page_args_in_body=True)
|
||||
except googleapiclient.errors.HttpError:
|
||||
policies = []
|
||||
# sort policies first by app/printer id then by schema name
|
||||
policies = sorted(policies,
|
||||
key=lambda k: (
|
||||
list(k.get('targetKey', {}).get('additionalTargetKeys', {}).values()),
|
||||
k.get('value', {}).get('policySchema', '')))
|
||||
printed_ids = []
|
||||
for policy in policies:
|
||||
print()
|
||||
name = policy.get('value', {}).get('policySchema', '')
|
||||
for key, val in policy['targetKey'].get('additionalTargetKeys', {}).items():
|
||||
additional_id = f'{key} - {val}'
|
||||
if additional_id not in printed_ids:
|
||||
print(f' {additional_id}')
|
||||
printed_ids.append(additional_id)
|
||||
spacing = ' '
|
||||
print(f'{spacing}{name}')
|
||||
values = policy.get('value', {}).get('value', {})
|
||||
for setting, value in values.items():
|
||||
# Handle TYPE_MESSAGE fields with durations, values, counts and timeOfDay as special cases
|
||||
schema = CHROME_SCHEMA_TYPE_MESSAGE.get(name, {}).get(setting.lower())
|
||||
if schema and setting == schema['casedField']:
|
||||
vtype = schema['type']
|
||||
if vtype in {'duration', 'value'}:
|
||||
value = value.get(vtype, '')
|
||||
if value:
|
||||
if value.endswith('s'):
|
||||
value = value[:-1]
|
||||
value = int(value) // schema['scale']
|
||||
elif vtype == 'count':
|
||||
pass
|
||||
else: ##timeOfDay
|
||||
hours = value.get(vtype, {}).get('hours', 0)
|
||||
minutes = value.get(vtype, {}).get('minutes', 0)
|
||||
value = f'{hours:02}:{minutes:02}'
|
||||
elif isinstance(value, str) and value.find('_ENUM_') != -1:
|
||||
value = value.split('_ENUM_')[-1]
|
||||
print(f'{spacing}{setting}: {value}')
|
||||
|
||||
|
||||
def build_schemas(svc=None, sfilter=None):
|
||||
if not svc:
|
||||
svc = build()
|
||||
parent = _get_customerid()
|
||||
schemas = gapi.get_all_pages(svc.customers().policySchemas(), 'list',
|
||||
items='policySchemas', parent=parent, filter=sfilter)
|
||||
schema_objects = {}
|
||||
for schema in schemas:
|
||||
schema_name = schema.get('name', '').split('/')[-1]
|
||||
schema_dict = {
|
||||
'name': schema_name,
|
||||
'description': schema.get('policyDescription', ''),
|
||||
'settings': {},
|
||||
}
|
||||
field_descriptions = schema.get('fieldDescriptions', [])
|
||||
for mtype in schema.get('definition', {}).get('messageType', {}):
|
||||
for setting in mtype.get('field', {}):
|
||||
setting_name = setting.get('name', '')
|
||||
setting_dict = {
|
||||
'name': setting_name,
|
||||
'constraints': None,
|
||||
'descriptions': [],
|
||||
'type': setting.get('type'),
|
||||
}
|
||||
if setting_dict['type'] == 'TYPE_STRING' and \
|
||||
setting.get('label') == 'LABEL_REPEATED':
|
||||
setting_dict['type'] = 'TYPE_LIST'
|
||||
if setting_dict['type'] == 'TYPE_ENUM':
|
||||
type_name = setting['typeName']
|
||||
for an_enum in schema['definition']['enumType']:
|
||||
if an_enum['name'] == type_name:
|
||||
setting_dict['enums'] = [enum['name'] for enum in an_enum['value']]
|
||||
setting_dict['enum_prefix'] = utils.commonprefix(setting_dict['enums'])
|
||||
prefix_len = len(setting_dict['enum_prefix'])
|
||||
setting_dict['enums'] = [enum[prefix_len:] for enum \
|
||||
in setting_dict['enums'] \
|
||||
if not enum.endswith('UNSPECIFIED')]
|
||||
setting_dict['descriptions'] = ['']*len(setting_dict['enums'])
|
||||
if field_descriptions:
|
||||
for i, an in enumerate(setting_dict['enums']):
|
||||
for fdesc in field_descriptions:
|
||||
if fdesc.get('field') == setting_name:
|
||||
for d in fdesc.get('knownValueDescriptions', []):
|
||||
if d['value'][prefix_len:] == an:
|
||||
setting_dict['descriptions'][i] = d['description']
|
||||
break
|
||||
break
|
||||
break
|
||||
elif setting_dict['type'] == 'TYPE_MESSAGE':
|
||||
continue
|
||||
else:
|
||||
setting_dict['enums'] = None
|
||||
for fdesc in schema.get('fieldDescriptions', []):
|
||||
if fdesc.get('field') == setting_name:
|
||||
if 'knownValueDescriptions' in fdesc:
|
||||
setting_dict['descriptions'] = fdesc['knownValueDescriptions']
|
||||
elif 'description' in fdesc:
|
||||
setting_dict['descriptions'] = [fdesc['description']]
|
||||
schema_dict['settings'][setting_name.lower()] = setting_dict
|
||||
schema_objects[schema_name.lower()] = schema_dict
|
||||
return schema_objects
|
||||
|
||||
|
||||
def printshow_schemas():
|
||||
svc = build()
|
||||
sfilter = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'filter':
|
||||
sfilter = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromeschema"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
schemas = build_schemas(svc, sfilter)
|
||||
for _, value in sorted(iter(schemas.items())):
|
||||
print(f'{value.get("name")}: {value.get("description")}')
|
||||
for val in value['settings'].values():
|
||||
vtype = val.get('type')
|
||||
print(f' {val.get("name")}: {vtype}')
|
||||
if vtype == 'TYPE_ENUM':
|
||||
enums = val.get('enums', [])
|
||||
descriptions = val.get('descriptions', [])
|
||||
for i in range(len(val.get('enums', []))):
|
||||
print(f' {enums[i]}: {descriptions[i]}')
|
||||
elif vtype == 'TYPE_BOOL':
|
||||
pvs = val.get('descriptions')
|
||||
for pvi in pvs:
|
||||
if isinstance(pvi, dict):
|
||||
pvalue = pvi.get('value')
|
||||
pdescription = pvi.get('description')
|
||||
print(f' {pvalue}: {pdescription}')
|
||||
elif isinstance(pvi, list):
|
||||
print(f' {pvi[0]}')
|
||||
else:
|
||||
description = val.get('descriptions')
|
||||
if len(description) > 0:
|
||||
print(f' {description[0]}')
|
||||
print()
|
||||
|
||||
|
||||
def delete_policy():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
schemas = build_schemas(svc)
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
i = 3
|
||||
body = {'requests': []}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in schemas:
|
||||
body['requests'].append({'policySchema': schemas[myarg]['name']})
|
||||
i += 1
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam delete chromepolicy"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
for request in body['requests']:
|
||||
request['policyTargetKey'] = {'targetResource': orgunit}
|
||||
if printer_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
elif app_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
gapi.call(svc.customers().policies().orgunits(), 'batchInherit', customer=customer, body=body)
|
||||
|
||||
|
||||
CHROME_SCHEMA_TYPE_MESSAGE = {
|
||||
'chrome.users.AutoUpdateCheckPeriodNew': {
|
||||
'autoupdatecheckperiodminutesnew':
|
||||
{'casedField': 'autoUpdateCheckPeriodMinutesNew',
|
||||
'type': 'duration', 'minVal': 1, 'maxVal': 720, 'scale': 60}},
|
||||
'chrome.users.BrowserSwitcherDelayDuration':
|
||||
{'browserswitcherdelayduration':
|
||||
{'casedField': 'browserSwitcherDelayDuration',
|
||||
'type': 'duration', 'minVal': 0, 'maxVal': 30, 'scale': 1}},
|
||||
'chrome.users.FetchKeepaliveDurationSecondsOnShutdown':
|
||||
{'fetchkeepalivedurationsecondsonshutdown':
|
||||
{'casedField': 'fetchKeepaliveDurationSecondsOnShutdown',
|
||||
'type': 'duration', 'minVal': 0, 'maxVal': 5, 'scale': 1}},
|
||||
'chrome.users.MaxInvalidationFetchDelay':
|
||||
{'maxinvalidationfetchdelay':
|
||||
{'casedField': 'maxInvalidationFetchDelay',
|
||||
'type': 'duration', 'minVal': 1, 'maxVal': 30, 'scale': 1, 'default': 10}},
|
||||
'chrome.users.PrintingMaxSheetsAllowed':
|
||||
{'printingmaxsheetsallowednullable':
|
||||
{'casedField': 'printingMaxSheetsAllowedNullable',
|
||||
'type': 'value', 'minVal': 1, 'maxVal': None, 'scale': 1}},
|
||||
'chrome.users.PrintJobHistoryExpirationPeriodNew':
|
||||
{'printjobhistoryexpirationperioddaysnew':
|
||||
{'casedField': 'printJobHistoryExpirationPeriodDaysNew',
|
||||
'type': 'duration', 'minVal': -1, 'maxVal': None, 'scale': 86400}},
|
||||
'chrome.users.SecurityTokenSessionSettings':
|
||||
{'securitytokensessionnotificationseconds':
|
||||
{'casedField': 'securityTokenSessionNotificationSeconds',
|
||||
'type': 'duration', 'minVal': 0, 'maxVal': 9999, 'scale': 1}},
|
||||
'chrome.users.SessionLength':
|
||||
{'sessiondurationlimit':
|
||||
{'casedField': 'sessionDurationLimit',
|
||||
'type': 'duration', 'minVal': 1, 'maxVal': 1440, 'scale': 60}},
|
||||
'chrome.users.UpdatesSuppressed':
|
||||
{'updatessuppresseddurationmin':
|
||||
{'casedField': 'updatesSuppressedDurationMin',
|
||||
'type': 'count', 'minVal': 1, 'maxVal': 1440, 'scale': 1},
|
||||
'updatessuppressedstarttime':
|
||||
{'casedField': 'updatesSuppressedStartTime',
|
||||
'type': 'timeOfDay'}},
|
||||
}
|
||||
|
||||
|
||||
def update_policy():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
schemas = build_schemas(svc)
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
i = 3
|
||||
body = {'requests': []}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in schemas:
|
||||
schemaName = schemas[myarg]['name']
|
||||
body['requests'].append({'policyValue': {'policySchema': schemaName,
|
||||
'value': {}},
|
||||
'updateMask': ''})
|
||||
i += 1
|
||||
while i < len(sys.argv):
|
||||
field = sys.argv[i].lower()
|
||||
if field in ['ou', 'org', 'orgunit', 'printerid', 'appid'] or '.' in field:
|
||||
break # field is actually a new policy, orgunit or app/printer id
|
||||
# Handle TYPE_MESSAGE fields with durations, values, counts and timeOfDay as special cases
|
||||
schema = CHROME_SCHEMA_TYPE_MESSAGE.get(schemaName, {}).get(field)
|
||||
if schema:
|
||||
i += 1
|
||||
casedField = schema['casedField']
|
||||
vtype = schema['type']
|
||||
if vtype != 'timeOfDay':
|
||||
if 'default' not in schema:
|
||||
value = gam.getInteger(sys.argv[i], casedField,
|
||||
minVal=schema['minVal'], maxVal=schema['maxVal'])*schema['scale']
|
||||
i += 1
|
||||
elif i < len(sys.argv) and sys.argv[i].isdigit():
|
||||
value = gam.getInteger(sys.argv[i], casedField,
|
||||
minVal=schema['minVal'], maxVal=schema['maxVal'])*schema['scale']
|
||||
i += 1
|
||||
else: # Handle empty value for fields with default
|
||||
value = schema['default']*schema['scale']
|
||||
if i < len(sys.argv) and not sys.argv[i]:
|
||||
i += 1
|
||||
else:
|
||||
value = utils.get_hhmm(sys.argv[i])
|
||||
i += 1
|
||||
if vtype == 'duration':
|
||||
body['requests'][-1]['policyValue']['value'][casedField] = {vtype: f'{value}s'}
|
||||
elif vtype == 'value':
|
||||
body['requests'][-1]['policyValue']['value'][casedField] = {vtype: value}
|
||||
elif vtype == 'count':
|
||||
body['requests'][-1]['policyValue']['value'][casedField] = value
|
||||
else: ##timeOfDay
|
||||
hours, minutes = value.split(':')
|
||||
body['requests'][-1]['policyValue']['value'][casedField] = {vtype: {'hours': hours, 'minutes': minutes}}
|
||||
body['requests'][-1]['updateMask'] += f'{casedField},'
|
||||
continue
|
||||
expected_fields = ', '.join(schemas[myarg]['settings'])
|
||||
if field not in expected_fields:
|
||||
msg = f'Expected {myarg} field of {expected_fields}. Got {field}.'
|
||||
controlflow.system_error_exit(4, msg)
|
||||
cased_field = schemas[myarg]['settings'][field]['name']
|
||||
value = sys.argv[i+1]
|
||||
vtype = schemas[myarg]['settings'][field]['type']
|
||||
if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']:
|
||||
if not value.isnumeric():
|
||||
msg = f'Value for {myarg} {field} must be a number, got {value}'
|
||||
controlflow.system_error_exit(7, msg)
|
||||
value = int(value)
|
||||
elif vtype in ['TYPE_BOOL']:
|
||||
value = gam.getBoolean(value, field)
|
||||
elif vtype in ['TYPE_ENUM']:
|
||||
value = value.upper()
|
||||
prefix = schemas[myarg]['settings'][field]['enum_prefix']
|
||||
enum_values = schemas[myarg]['settings'][field]['enums']
|
||||
if value in enum_values:
|
||||
value = f'{prefix}{value}'
|
||||
elif value.replace(prefix, '') in enum_values:
|
||||
pass
|
||||
else:
|
||||
expected_enums = ', '.join(enum_values)
|
||||
msg = f'Expected {myarg} {field} value to be one of ' \
|
||||
f'{expected_enums}, got {value}'
|
||||
controlflow.system_error_exit(8, msg)
|
||||
elif vtype in ['TYPE_LIST']:
|
||||
value = value.split(',')
|
||||
if myarg == 'chrome.users.chromebrowserupdates' and \
|
||||
cased_field == 'targetVersionPrefixSetting':
|
||||
mg = re.compile(r'^([a-z]+)-(\d+)$').match(value)
|
||||
if mg:
|
||||
channel = mg.group(1).lower().replace('_', '')
|
||||
minus = mg.group(2)
|
||||
channel_map = gapi_chromehistory.get_channel_map(None)
|
||||
if channel not in channel_map:
|
||||
expected_channels = ', '.join(channel_map)
|
||||
msg = f'Expected {myarg} {cased_field} channel to be one of ' \
|
||||
f'{expected_channels}, got {channel}'
|
||||
controlflow.system_error_exit(8, msg)
|
||||
milestone = gapi_chromehistory.get_relative_milestone(
|
||||
channel_map[channel], int(minus))
|
||||
if not milestone:
|
||||
msg = f'{myarg} {cased_field} channel {channel} offset {minus} does not exist'
|
||||
controlflow.system_error_exit(8, msg)
|
||||
value = f'{milestone}.'
|
||||
body['requests'][-1]['policyValue']['value'][cased_field] = value
|
||||
body['requests'][-1]['updateMask'] += f'{cased_field},'
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam update chromepolicy"'
|
||||
controlflow.system_error_exit(4, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
for request in body['requests']:
|
||||
request['policyTargetKey'] = {'targetResource': orgunit}
|
||||
if printer_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
elif app_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
gapi.call(svc.customers().policies().orgunits(),
|
||||
'batchModify',
|
||||
customer=customer,
|
||||
body=body)
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
@@ -11,7 +12,7 @@ from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
def _get_device_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
@@ -49,6 +50,7 @@ def create():
|
||||
result = gapi.call(ci.devices(), 'create', customer=customer, body=body)
|
||||
print(f'Created device {result["response"]["name"]}')
|
||||
|
||||
|
||||
def _get_device_name():
|
||||
name = sys.argv[3]
|
||||
if name == 'id':
|
||||
@@ -65,10 +67,16 @@ def info():
|
||||
device = gapi.call(ci.devices(), 'get', name=name, customer=customer)
|
||||
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
|
||||
'deviceUsers', parent=name, customer=customer)
|
||||
for device_user in device_users:
|
||||
parent = device_user['name']
|
||||
device_user['client_states'] = gapi.get_all_pages(
|
||||
ci.devices().deviceUsers().clientStates(),
|
||||
'list', 'clientStates', parent=parent, customer=customer)
|
||||
display.print_json(device)
|
||||
print('Device Users:')
|
||||
display.print_json(device_users)
|
||||
|
||||
|
||||
def _generic_action(action, device_user=False):
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
@@ -87,30 +95,153 @@ def _generic_action(action, device_user=False):
|
||||
op = gapi.call(endpoint, action, name=name, **kwargs)
|
||||
print(op)
|
||||
|
||||
|
||||
def delete():
|
||||
_generic_action('delete')
|
||||
|
||||
|
||||
def cancel_wipe():
|
||||
_generic_action('cancelWipe')
|
||||
|
||||
|
||||
def wipe():
|
||||
_generic_action('wipe')
|
||||
|
||||
|
||||
def approve_user():
|
||||
_generic_action('approve', True)
|
||||
|
||||
|
||||
def block_user():
|
||||
_generic_action('block', True)
|
||||
|
||||
|
||||
def cancel_wipe_user():
|
||||
_generic_action('cancelWipe', True)
|
||||
|
||||
|
||||
def delete_user():
|
||||
_generic_action('delete', True)
|
||||
|
||||
|
||||
def wipe_user():
|
||||
_generic_action('wipe', True)
|
||||
|
||||
|
||||
def _get_deviceuser_name():
|
||||
i = 3
|
||||
name = sys.argv[i]
|
||||
if name == 'id':
|
||||
i += 1
|
||||
name = sys.argv[i]
|
||||
if not name.startswith('devices/'):
|
||||
name = f'devices/{name}'
|
||||
return (i+1, name)
|
||||
|
||||
def info_state():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer = _get_device_customerid()
|
||||
customer_id = customer[10:]
|
||||
client_id = f'{customer_id}-gam'
|
||||
i, deviceuser = _get_deviceuser_name()
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'clientid':
|
||||
client_id = f'{customer_id}-{sys.argv[i+1]}'
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam info deviceuserstate')
|
||||
name = f'{deviceuser}/clientStates/{client_id}'
|
||||
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'get',
|
||||
name=name, customer=customer)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def update_state():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer = _get_device_customerid()
|
||||
customer_id = customer[10:]
|
||||
client_id = f'{customer_id}-gam'
|
||||
body = {}
|
||||
i, deviceuser = _get_deviceuser_name()
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'clientid':
|
||||
client_id = f'{customer_id}-{sys.argv[i+1]}'
|
||||
i += 2
|
||||
elif myarg in ['assettag', 'assettags']:
|
||||
body['assetTags'] = gam.shlexSplitList(sys.argv[i+1])
|
||||
if body['assetTags'] == ['clear']:
|
||||
# TODO: this doesn't work to clear
|
||||
# existing values. Figure out why.
|
||||
body['assetTags'] = [None]
|
||||
i += 2
|
||||
elif myarg in ['compliantstate', 'compliancestate']:
|
||||
comp_states = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['complianceState']['enum'])
|
||||
body['complianceState'] = sys.argv[i+1].upper()
|
||||
if body['complianceState'] not in comp_states:
|
||||
controlflow.expected_argument_exit('compliant_state',
|
||||
', '.join(comp_states),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'customid':
|
||||
body['customId'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'healthscore':
|
||||
health_scores = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['healthScore']['enum'])
|
||||
body['healthScore'] = sys.argv[i+1].upper()
|
||||
if body['healthScore'] == 'CLEAR':
|
||||
body['healthScore'] = None
|
||||
if body['healthScore'] and body['healthScore'] not in health_scores:
|
||||
controlflow.expected_argument_exit('health_score',
|
||||
', '.join(health_scores),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'customvalue':
|
||||
allowed_types = ['bool', 'number', 'string']
|
||||
value_type = sys.argv[i+1].lower()
|
||||
if value_type not in allowed_types:
|
||||
controlflow.expected_argument_exit('custom_value',
|
||||
', '.join(allowed_types),
|
||||
sys.argv[i+1])
|
||||
key = sys.argv[i+2]
|
||||
value = sys.argv[i+3]
|
||||
if value_type == 'bool':
|
||||
value = gam.getBoolean(value, key)
|
||||
elif value_type == 'number':
|
||||
value = int(value)
|
||||
body.setdefault('keyValuePairs', {})
|
||||
body['keyValuePairs'][key] = {f'{value_type}Value': value}
|
||||
i += 4
|
||||
elif myarg in ['managedstate']:
|
||||
managed_states = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['managed']['enum'])
|
||||
body['managed'] = sys.argv[i+1].upper()
|
||||
if body['managed'] == 'CLEAR':
|
||||
body['managed'] = None
|
||||
if body['managed'] and body['managed'] not in managed_states:
|
||||
controlflow.expected_argument_exit('managed_state',
|
||||
', '.join(managed_states),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['scorereason']:
|
||||
body['scoreReason'] = sys.argv[i+1]
|
||||
if body['scoreReason'] == 'clear':
|
||||
body['scoreReason'] = None
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam update deviceuserstate')
|
||||
name = f'{deviceuser}/clientStates/{client_id}'
|
||||
updateMask = ','.join(body.keys())
|
||||
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'patch',
|
||||
name=name, customer=customer, updateMask=updateMask, body=body)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def print_():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
@@ -274,7 +405,7 @@ def sync():
|
||||
controlflow.csv_field_error_exit(devicetype_column, input_file.fieldnames)
|
||||
if assettag_column and assettag_column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(assettag_column, input_file.fieldnames)
|
||||
local_devices = []
|
||||
local_devices = {}
|
||||
for row in input_file:
|
||||
# upper() is very important to comparison since Google
|
||||
# always return uppercase serials
|
||||
@@ -283,28 +414,43 @@ def sync():
|
||||
local_device['deviceType'] = static_devicetype
|
||||
else:
|
||||
local_device['deviceType'] = row[devicetype_column].strip()
|
||||
sndt = f"{local_device['serialNumber']}-{local_device['deviceType']}"
|
||||
if assettag_column:
|
||||
local_device['assetTag'] = row[assettag_column].strip()
|
||||
local_devices.append(local_device)
|
||||
sndt += f"-{local_device['assetTag']}"
|
||||
local_devices[sndt] = local_device
|
||||
fileutils.close_file(f)
|
||||
page_message = gapi.got_total_items_msg('Company Devices', '...\n')
|
||||
device_fields = ['serialNumber', 'deviceType', 'lastSyncTime', 'name']
|
||||
if assettag_column:
|
||||
device_fields.append('assetTag')
|
||||
fields = f'nextPageToken,devices({",".join(device_fields)})'
|
||||
remote_devices = gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||
remote_devices = {}
|
||||
remote_device_map = {}
|
||||
result = gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||
customer=customer, page_message=page_message,
|
||||
pageSize=100, filter=device_filter, view='COMPANY_INVENTORY', fields=fields)
|
||||
remote_device_map = {}
|
||||
for remote_device in remote_devices:
|
||||
for remote_device in result:
|
||||
sn = remote_device['serialNumber']
|
||||
last_sync = remote_device.pop('lastSyncTime', NEVER_TIME_NOMS)
|
||||
name = remote_device.pop('name')
|
||||
remote_device_map[sn] = {'name': name}
|
||||
sndt = f"{remote_device['serialNumber']}-{remote_device['deviceType']}"
|
||||
if assettag_column:
|
||||
if 'assetTag' not in remote_device:
|
||||
remote_device['assetTag'] = ''
|
||||
sndt += f"-{remote_device['assetTag']}"
|
||||
remote_devices[sndt] = remote_device
|
||||
remote_device_map[sndt] = {'name': name}
|
||||
if last_sync == NEVER_TIME_NOMS:
|
||||
remote_device_map[sn]['unassigned'] = True
|
||||
devices_to_add = [device for device in local_devices if device not in remote_devices]
|
||||
missing_devices = [device for device in remote_devices if device not in local_devices]
|
||||
remote_device_map[sndt]['unassigned'] = True
|
||||
devices_to_add = []
|
||||
for sndt, device in iter(local_devices.items()):
|
||||
if sndt not in remote_devices:
|
||||
devices_to_add.append(device)
|
||||
missing_devices = []
|
||||
for sndt, device in iter(remote_devices.items()):
|
||||
if sndt not in local_devices:
|
||||
missing_devices.append(device)
|
||||
print(f'Need to add {len(devices_to_add)} and remove {len(missing_devices)} devices...')
|
||||
for add_device in devices_to_add:
|
||||
print(f'Creating {add_device["serialNumber"]}')
|
||||
@@ -316,8 +462,11 @@ def sync():
|
||||
print(f' {add_device["serialNumber"]} already exists')
|
||||
for missing_device in missing_devices:
|
||||
sn = missing_device['serialNumber']
|
||||
name = remote_device_map[sn]['name']
|
||||
unassigned = remote_device_map[sn].get('unassigned')
|
||||
sndt = f"{sn}-{missing_device['deviceType']}"
|
||||
if assettag_column:
|
||||
sndt += f"-{missing_device['assetTag']}"
|
||||
name = remote_device_map[sndt]['name']
|
||||
unassigned = remote_device_map[sndt].get('unassigned')
|
||||
action = unassigned_missing_action if unassigned else assigned_missing_action
|
||||
if action == 'donothing':
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import sys
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam.var import * # pylint: disable=unused-wildcard-import
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
@@ -10,7 +14,7 @@ from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
|
||||
def create():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build()
|
||||
initialGroupConfig = 'EMPTY'
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
@@ -40,7 +44,6 @@ def create():
|
||||
body['additionalGroupKeys'].append({'id': alias})
|
||||
i += 2
|
||||
elif myarg in ['dynamic']:
|
||||
# As of 2020/06/25 this doesn't work (yet?)
|
||||
body['dynamicGroupMetadata'] = {
|
||||
'queries': [{
|
||||
'query': sys.argv[i + 1],
|
||||
@@ -62,7 +65,7 @@ def create():
|
||||
|
||||
|
||||
def delete():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build()
|
||||
group = sys.argv[3]
|
||||
name = group_email_to_id(ci, group)
|
||||
print(f'Deleting group {group}')
|
||||
@@ -73,8 +76,10 @@ def info():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
||||
getUsers = True
|
||||
getSecuritySettings = True
|
||||
showJoinDate = True
|
||||
showUpdateDate = False
|
||||
showMemberTree = False
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -87,12 +92,24 @@ def info():
|
||||
elif myarg == 'showupdatedate':
|
||||
showUpdateDate = True
|
||||
i += 1
|
||||
elif myarg == 'membertree':
|
||||
showMemberTree = True
|
||||
i += 1
|
||||
elif myarg in ['nosecurity', 'nosecuritysettings']:
|
||||
getSecuritySettings = False
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam info cigroup')
|
||||
name = group_email_to_id(ci, group)
|
||||
basic_info = gapi.call(ci.groups(), 'get', name=name)
|
||||
display.print_json(basic_info)
|
||||
if getUsers:
|
||||
if getSecuritySettings:
|
||||
sec_info = gapi.call(ci.groups(),
|
||||
'getSecuritySettings',
|
||||
name=f'{name}/securitySettings',
|
||||
readMask='*')
|
||||
print(' Security settings:')
|
||||
display.print_json(sec_info, spacing=' ')
|
||||
if getUsers and not showMemberTree:
|
||||
if not showJoinDate and not showUpdateDate:
|
||||
view = 'BASIC'
|
||||
pageSize = 1000
|
||||
@@ -106,10 +123,11 @@ def info():
|
||||
fields='*',
|
||||
pageSize=pageSize,
|
||||
view=view)
|
||||
print('Members:')
|
||||
print(' Members:')
|
||||
for member in members:
|
||||
role = get_single_role(member.get('roles', [])).lower()
|
||||
email = member.get('memberKey', {}).get('id')
|
||||
email = member.get('preferredMemberKey', {}).get('id')
|
||||
member_type = member.get('type', 'USER').lower()
|
||||
jc_string = ''
|
||||
if showJoinDate:
|
||||
joined = member.get('createTime', 'Unknown')
|
||||
@@ -117,15 +135,39 @@ def info():
|
||||
if showUpdateDate:
|
||||
updated = member.get('updateTime', 'Unknown')
|
||||
jc_string += f' updated {updated}'
|
||||
print(
|
||||
f'{role}: {email}{jc_string}'
|
||||
# f' {member.get("role", ROLE_MEMBER).lower()}: {member.get("email", member["id"])} ({member["type"].lower()})'
|
||||
)
|
||||
print(f' {role}: {email} ({member_type}){jc_string}')
|
||||
print(f'Total {len(members)} users in group')
|
||||
elif showMemberTree:
|
||||
print(' Membership Tree:')
|
||||
cached_group_members = {}
|
||||
print_member_tree(ci, name, cached_group_members, 2, True)
|
||||
|
||||
|
||||
def print_member_tree(ci, group_id, cached_group_members, spaces, show_role):
|
||||
if not group_id in cached_group_members:
|
||||
cached_group_members[group_id] = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'list',
|
||||
'memberships',
|
||||
parent=group_id,
|
||||
view='FULL',
|
||||
fields='*',
|
||||
pageSize=1000)
|
||||
for member in cached_group_members[group_id]:
|
||||
member_id = member.get('name', '')
|
||||
member_id = member_id.split('/')[-1]
|
||||
email = member.get('preferredMemberKey', {}).get('id')
|
||||
member_type = member.get('type', 'USER').lower()
|
||||
if show_role:
|
||||
role = get_single_role(member.get('roles', [])).lower()
|
||||
print(f'{" " * spaces}{role}: {email} ({member_type})')
|
||||
else:
|
||||
print(f'{" " * spaces}{email} ({member_type})')
|
||||
if member_type == 'group':
|
||||
print_member_tree(ci, f'groups/{member_id}', cached_group_members, spaces + 2, False)
|
||||
|
||||
|
||||
def info_member():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build()
|
||||
member = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
||||
group = gam.normalizeEmailAddressOrUID(sys.argv[4])
|
||||
group_name = gapi.call(ci.groups(),
|
||||
@@ -157,9 +199,16 @@ GROUP_ROLES_MAP = {
|
||||
def print_():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
i = 3
|
||||
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
|
||||
members = False
|
||||
membersCountOnly = False
|
||||
managers = False
|
||||
managersCountOnly = False
|
||||
owners = False
|
||||
ownersCountOnly = False
|
||||
memberRestrictions = False
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
usemember = None
|
||||
memberDelimiter = '\n'
|
||||
todrive = False
|
||||
titles = []
|
||||
@@ -171,6 +220,10 @@ def print_():
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'enterprisemember':
|
||||
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
|
||||
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
i += 2
|
||||
elif myarg == 'delimiter':
|
||||
memberDelimiter = sys.argv[i + 1]
|
||||
i += 2
|
||||
@@ -195,6 +248,15 @@ def print_():
|
||||
if myarg == 'managerscount':
|
||||
managersCountOnly = True
|
||||
i += 1
|
||||
elif myarg in ['memberrestrictions']:
|
||||
memberRestrictions = True
|
||||
display.add_titles_to_csv_file(
|
||||
['memberRestrictionQuery',],
|
||||
titles)
|
||||
display.add_titles_to_csv_file(
|
||||
['memberRestrictionEvaluation',],
|
||||
titles)
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroups')
|
||||
if roles:
|
||||
@@ -222,16 +284,36 @@ def print_():
|
||||
display.add_titles_to_csv_file([
|
||||
'Owners',
|
||||
], titles)
|
||||
gam.printGettingAllItems('Groups', None)
|
||||
gam.printGettingAllItems('Groups', usemember)
|
||||
page_message = gapi.got_total_items_first_last_msg('Groups')
|
||||
entityList = gapi.get_all_pages(ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent=parent,
|
||||
view='FULL',
|
||||
pageSize=500)
|
||||
if usemember:
|
||||
try:
|
||||
result = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'searchTransitiveGroups',
|
||||
'memberships',
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent='groups/-', query=usemember,
|
||||
fields='nextPageToken,memberships(group,groupKey(id),relationType)',
|
||||
pageSize=1000)
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
'enterprisemember requires Enterprise license')
|
||||
entityList = []
|
||||
for entity in result:
|
||||
if entity['relationType'] == 'DIRECT':
|
||||
entityList.append(gapi.call(ci.groups(), 'get', name=entity['group']))
|
||||
else:
|
||||
entityList = gapi.get_all_pages(ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent=parent,
|
||||
view='FULL',
|
||||
pageSize=500)
|
||||
i = 0
|
||||
count = len(entityList)
|
||||
for groupEntity in entityList:
|
||||
@@ -258,7 +340,7 @@ def print_():
|
||||
'list',
|
||||
'memberships',
|
||||
page_message=page_message,
|
||||
message_attribute=['memberKey', 'id'],
|
||||
message_attribute=['preferredMemberKey', 'id'],
|
||||
soft_errors=True,
|
||||
parent=groupKey_id,
|
||||
view='BASIC')
|
||||
@@ -272,8 +354,8 @@ def print_():
|
||||
ownersList = []
|
||||
ownersCount = 0
|
||||
for member in groupMembers:
|
||||
member_email = member['memberKey']['id']
|
||||
role = get_single_role(member.get('roles'))
|
||||
member_email = member['preferredMemberKey']['id']
|
||||
role = get_single_role(member.get('roles', []))
|
||||
if not validRoles or role in validRoles:
|
||||
if role == ROLE_MEMBER:
|
||||
if members:
|
||||
@@ -306,6 +388,16 @@ def print_():
|
||||
group['OwnersCount'] = ownersCount
|
||||
if not ownersCountOnly:
|
||||
group['Owners'] = memberDelimiter.join(ownersList)
|
||||
if memberRestrictions:
|
||||
name = f'{groupKey_id}/securitySettings'
|
||||
print(f'Getting member restrictions for {groupEmail} ({i}/{count}')
|
||||
sec_info = gapi.call(ci.groups(),
|
||||
'getSecuritySettings',
|
||||
name=name,
|
||||
readMask='*')
|
||||
if 'memberRestriction' in sec_info:
|
||||
group['memberRestrictionQuery'] = sec_info['memberRestriction'].get('query', '')
|
||||
group['memberRestrictionEvaluation'] = sec_info['memberRestriction'].get('evaluation', {}).get('state', '')
|
||||
csvRows.append(group)
|
||||
if sortHeaders:
|
||||
display.sort_csv_titles([
|
||||
@@ -314,11 +406,62 @@ def print_():
|
||||
display.write_csv_file(csvRows, titles, 'Groups', todrive)
|
||||
|
||||
|
||||
def _get_groups_list(ci=None, member=None, parent=None):
|
||||
if not ci:
|
||||
ci = gapi_cloudidentity.build()
|
||||
if not parent:
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
gam.printGettingAllItems('Groups', member)
|
||||
page_message = gapi.got_total_items_first_last_msg('Groups')
|
||||
if member:
|
||||
fields = 'nextPageToken,memberships(groupKey(id),relationType)'
|
||||
try:
|
||||
groups_to_get = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'searchTransitiveGroups',
|
||||
'memberships',
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent='groups/-',
|
||||
query=member,
|
||||
pageSize=1000,
|
||||
fields=fields)
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
'enterprisemember requires Enterprise license')
|
||||
return [group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT']
|
||||
else:
|
||||
groups_to_get = gapi.get_all_pages(
|
||||
ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
view='BASIC',
|
||||
pageSize=1000,
|
||||
fields='nextPageToken,groups(groupKey(id))')
|
||||
return [group['groupKey']['id'] for group in groups_to_get]
|
||||
|
||||
|
||||
def get_membership_graph(member):
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
query = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
result = gapi.call(ci.groups().memberships(),
|
||||
'getMembershipGraph',
|
||||
parent='groups/-',
|
||||
query=query)
|
||||
return result.get('response')
|
||||
|
||||
|
||||
def print_members():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
todrive = False
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
usemember = None
|
||||
roles = []
|
||||
titles = ['group']
|
||||
csvRows = []
|
||||
@@ -339,6 +482,10 @@ def print_members():
|
||||
f'{role} is not a valid role for "gam print group-members {myarg}"'
|
||||
)
|
||||
i += 2
|
||||
elif myarg == 'enterprisemember':
|
||||
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
|
||||
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
i += 2
|
||||
elif myarg in ['cigroup', 'cigroups']:
|
||||
group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
|
||||
groups_to_get = [group_email]
|
||||
@@ -347,19 +494,7 @@ def print_members():
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print cigroup-members')
|
||||
if not groups_to_get:
|
||||
gam.printGettingAllItems('Groups', None)
|
||||
page_message = gapi.got_total_items_first_last_msg('Groups')
|
||||
groups_to_get = gapi.get_all_pages(
|
||||
ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
view='BASIC',
|
||||
pageSize=1000,
|
||||
fields='nextPageToken,groups(groupKey(id))')
|
||||
groups_to_get = [group['groupKey']['id'] for group in groups_to_get]
|
||||
groups_to_get = _get_groups_list(ci, usemember, parent)
|
||||
i = 0
|
||||
count = len(groups_to_get)
|
||||
for group_email in groups_to_get:
|
||||
@@ -379,8 +514,8 @@ def print_members():
|
||||
view='FULL',
|
||||
pageSize=500,
|
||||
page_message=page_message,
|
||||
message_attribute=['memberKey', 'id'])
|
||||
#fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)')
|
||||
message_attribute=['preferredMemberKey', 'id'])
|
||||
#fields='nextPageToken,memberships(preferredMemberKey,roles,createTime,updateTime)')
|
||||
if roles:
|
||||
group_members = filter_members_to_roles(group_members, roles)
|
||||
for member in group_members:
|
||||
@@ -411,7 +546,7 @@ def update():
|
||||
|
||||
def _getRoleAndUsers():
|
||||
checkSuspended = None
|
||||
role = None
|
||||
role = ROLE_MEMBER
|
||||
expireTime = None
|
||||
i = 5
|
||||
if sys.argv[i].lower() in GROUP_ROLES_MAP:
|
||||
@@ -421,7 +556,10 @@ def update():
|
||||
checkSuspended = sys.argv[i].lower() == 'suspended'
|
||||
i += 1
|
||||
if sys.argv[i].lower() in ['expire', 'expires']:
|
||||
expireTime = sys.argv[i+1]
|
||||
if role != ROLE_MEMBER:
|
||||
controlflow.invalid_argument_exit(
|
||||
sys.argv[i], f'role {role}')
|
||||
expireTime = utils.get_time_or_delta_from_now(sys.argv[i+1])
|
||||
i += 2
|
||||
if sys.argv[i].lower() in usergroup_types:
|
||||
users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(),
|
||||
@@ -449,8 +587,6 @@ def update():
|
||||
return
|
||||
if myarg == 'add':
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will add {len(users_email)} {role}s.\n')
|
||||
@@ -464,7 +600,7 @@ def update():
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
body = {
|
||||
'memberKey': {
|
||||
'preferredMemberKey': {
|
||||
'id': users_email[0]
|
||||
},
|
||||
'roles': [{
|
||||
@@ -473,7 +609,7 @@ def update():
|
||||
}
|
||||
if role != ROLE_MEMBER:
|
||||
body['roles'].append({'name': role})
|
||||
if expireTime:
|
||||
elif expireTime not in {None, NEVER_TIME}:
|
||||
for role in body['roles']:
|
||||
if role['name'] == ROLE_MEMBER:
|
||||
role['expiryDetail'] = {'expireTime': expireTime}
|
||||
@@ -544,7 +680,7 @@ def update():
|
||||
for user in to_add:
|
||||
item = ['gam', 'update', 'cigroup', f'id:{parent}', 'add',
|
||||
role,]
|
||||
if expireTime:
|
||||
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
|
||||
item.extend(['expires', expireTime])
|
||||
item.append(user)
|
||||
items.append(item)
|
||||
@@ -580,8 +716,6 @@ def update():
|
||||
)
|
||||
elif myarg == 'update':
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will update {len(users_email)} {role}s.\n'
|
||||
@@ -596,36 +730,48 @@ def update():
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
name = membership_email_to_id(ci, parent, users_email[0])
|
||||
preUpdateRoles = []
|
||||
addRoles = []
|
||||
removeRoles = []
|
||||
current_roles = gapi.call(ci.groups().memberships(),
|
||||
'get',
|
||||
name=name,
|
||||
fields='roles').get('roles', [])
|
||||
current_roles = [role['name'] for role in current_roles]
|
||||
postUpdateRoles = []
|
||||
member_roles = gapi.call(ci.groups().memberships(),
|
||||
'get',
|
||||
name=name,
|
||||
fields='roles').get('roles', [{'name': ROLE_MEMBER}])
|
||||
current_roles = [crole['name'] for crole in member_roles]
|
||||
# When upgrading role, strip any expiryDetail from member before role changes
|
||||
if role != ROLE_MEMBER:
|
||||
for crole in member_roles:
|
||||
if 'expiryDetail' in crole:
|
||||
preUpdateRoles.append(
|
||||
{'fieldMask': 'expiryDetail.expireTime',
|
||||
'membershipRole': {'name': ROLE_MEMBER,
|
||||
'expiryDetail': {'expireTime': None}}})
|
||||
break
|
||||
# When downgrading role or simply updating member expireTime, update expiryDetail after role changes
|
||||
elif expireTime:
|
||||
postUpdateRoles.append(
|
||||
{'fieldMask': 'expiryDetail.expireTime',
|
||||
'membershipRole': {'name': role,
|
||||
'expiryDetail': {'expireTime': expireTime if expireTime != NEVER_TIME else None}}})
|
||||
for crole in current_roles:
|
||||
if crole not in {ROLE_MEMBER, role}:
|
||||
removeRoles.append(crole)
|
||||
if role not in current_roles:
|
||||
new_role = {'name': role}
|
||||
if role == ROLE_MEMBER and expireTime:
|
||||
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
|
||||
new_role['expiryDetail'] = {'expireTime': expireTime}
|
||||
expireTime = None
|
||||
postUpdateRoles = []
|
||||
addRoles.append(new_role)
|
||||
bodys = []
|
||||
if preUpdateRoles:
|
||||
bodys.append({'updateRolesParams': preUpdateRoles})
|
||||
if addRoles:
|
||||
bodys.append({'addRoles': addRoles})
|
||||
if removeRoles:
|
||||
bodys.append({'removeRoles': removeRoles})
|
||||
if expireTime:
|
||||
bodys.append({
|
||||
'name': ROLE_MEMBER,
|
||||
# Note this doesn't actually work for some reason. Only known method to change
|
||||
# expire time right now is to remove/re-add member.
|
||||
'expiryDetail': {
|
||||
'expireTime': expireTime
|
||||
}
|
||||
})
|
||||
if postUpdateRoles:
|
||||
bodys.append({'updateRolesParams': postUpdateRoles})
|
||||
for body in bodys:
|
||||
try:
|
||||
gapi.call(ci.groups().memberships(),
|
||||
@@ -674,12 +820,12 @@ def update():
|
||||
page_message=page_message,
|
||||
throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
|
||||
parent=parent,
|
||||
fields='nextPageToken,memberships(memberKey,roles)')
|
||||
fields='nextPageToken,memberships(preferredMemberKey,roles)')
|
||||
result = filter_members_to_roles(result, roles)
|
||||
if not result:
|
||||
print('Group already has 0 members')
|
||||
return
|
||||
users_email = [member['memberKey']['id'] for member in result]
|
||||
users_email = [member['preferredMemberKey']['id'] for member in result]
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n'
|
||||
)
|
||||
@@ -697,6 +843,7 @@ def update():
|
||||
else:
|
||||
i = 4
|
||||
body = {}
|
||||
sec_body = {}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'name':
|
||||
@@ -711,17 +858,49 @@ def update():
|
||||
'cloudidentity.googleapis.com/groups.discussion_forum': ''
|
||||
}
|
||||
i += 1
|
||||
elif myarg in ['dynamic']:
|
||||
body['dynamicGroupMetadata'] = {
|
||||
'queries': [{
|
||||
'query': sys.argv[i + 1],
|
||||
'resourceType': 'USER'
|
||||
}]
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['memberrestriction', 'memberrestrictions']:
|
||||
query = sys.argv[i + 1]
|
||||
member_types = {
|
||||
'USER': '1',
|
||||
'SERVICE_ACCOUNT': '2',
|
||||
'GROUP': '3',
|
||||
}
|
||||
for key, val in member_types.items():
|
||||
query = query.replace(key, val)
|
||||
sec_body['memberRestriction'] = {'query': query}
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam update cigroup')
|
||||
updateMask = ','.join(body.keys())
|
||||
name = group_email_to_id(ci, group)
|
||||
print(f'Updating group {group}')
|
||||
gapi.call(ci.groups(),
|
||||
'patch',
|
||||
updateMask=updateMask,
|
||||
name=name,
|
||||
body=body)
|
||||
if body:
|
||||
updateMask = ','.join(body.keys())
|
||||
name = group_email_to_id(ci, group)
|
||||
print(f'Updating group {group}')
|
||||
gapi.call(ci.groups(),
|
||||
'patch',
|
||||
updateMask=updateMask,
|
||||
name=name,
|
||||
body=body)
|
||||
if sec_body:
|
||||
updateMask = 'member_restriction.query'
|
||||
# it seems like a bug that API requires /securitySettings
|
||||
# appended to name. We'll see if Google servers change this
|
||||
# at some point.
|
||||
name = f'{group_email_to_id(ci, group)}/securitySettings'
|
||||
print(f'Updating group {group} security settings')
|
||||
gapi.call(ci.groups(),
|
||||
'updateSecuritySettings',
|
||||
name=name,
|
||||
updateMask=updateMask,
|
||||
body=sec_body)
|
||||
|
||||
|
||||
def group_email_to_id(ci, group, i=0, count=0):
|
||||
|
||||
207
src/gam/gapi/cloudidentity/userinvitations.py
Normal file
207
src/gam/gapi/cloudidentity/userinvitations.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""Methods related to Cloud Identity User Invitation API"""
|
||||
import sys
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER, SORTORDER_CHOICES_MAP
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer in "customers/(C){customer_id}' format needed for this API'''
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
def _reduce_name(name):
|
||||
''' converts long name into email address'''
|
||||
return name.split('/')[-1]
|
||||
|
||||
def is_invitable_user(email):
|
||||
'''return email isInvitableUser'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
return gapi.call(svc.customers().userinvitations(), 'isInvitableUser',
|
||||
name=name)['isInvitableUser']
|
||||
|
||||
|
||||
def _generic_action(action):
|
||||
'''generic function to call actionable APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
action_map = {
|
||||
'cancel': 'Cancelling',
|
||||
'send': 'Sending'
|
||||
}
|
||||
print_action = action_map[action]
|
||||
print(f'{print_action} user invitation...')
|
||||
result = gapi.call(svc.customers().userinvitations(), action,
|
||||
name=name)
|
||||
name = result.get('response', {}).get('name')
|
||||
if name:
|
||||
result['response']['name'] = _reduce_name(name)
|
||||
display.print_json(result)
|
||||
|
||||
def _generic_get(get_type):
|
||||
'''generic function to call read data APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
result = gapi.call(svc.customers().userinvitations(), get_type,
|
||||
name=name)
|
||||
if 'name' in result:
|
||||
result['name'] = _reduce_name(result['name'])
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
# /batch is broken for Cloud Identity. Once fixed move this to using batch.
|
||||
# Current serial implementation will be SLOW...
|
||||
def bulk_is_invitable(emails):
|
||||
'''gam <users> check isinvitable'''
|
||||
def _invitation_result(request_id, response, _):
|
||||
if response.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': request_id})
|
||||
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
#batch_size = 1000
|
||||
#ebatch = svc.new_batch_http_request(callback=_invitation_result)
|
||||
rows = []
|
||||
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_THREE]
|
||||
for email in emails:
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
endpoint = svc.customers().userinvitations()
|
||||
#if len(ebatch._order) == batch_size:
|
||||
# ebatch.execute()
|
||||
# ebatch = svc.new_batch_http_request(callback=_invitation_result)
|
||||
#req = endpoint.isInvitableUser(name=name)
|
||||
#ebatch.add(req, request_id=email)
|
||||
try:
|
||||
result = gapi.call(endpoint,
|
||||
'isInvitableUser',
|
||||
throw_reasons=throw_reasons,
|
||||
name=name)
|
||||
except googleapiclient.errors.HttpError:
|
||||
continue
|
||||
if result.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': email})
|
||||
#ebatch.execute()
|
||||
titles = ['invitableUsers']
|
||||
display.write_csv_file(rows, titles, 'Invitable Users', todrive)
|
||||
|
||||
|
||||
def cancel():
|
||||
'''gam cancel userinvitation <email>'''
|
||||
_generic_action('cancel')
|
||||
|
||||
|
||||
def get():
|
||||
'''gam info userinvitation <email>'''
|
||||
_generic_get('get')
|
||||
|
||||
|
||||
def check():
|
||||
'''gam check userinvitation <email>'''
|
||||
_generic_get('isInvitableUser')
|
||||
|
||||
|
||||
def send():
|
||||
'''gam send userinvitation <email>'''
|
||||
_generic_action('send')
|
||||
|
||||
|
||||
USERINVITATION_ORDERBY_CHOICES_MAP = {
|
||||
'email': 'email',
|
||||
'updatetime': 'update_time',
|
||||
}
|
||||
|
||||
USERINVITATION_STATE_CHOICES_MAP = {
|
||||
'accepted': 'ACCEPTED',
|
||||
'declined': 'DECLINED',
|
||||
'invited': 'INVITED',
|
||||
'notyetsent': 'NOT_YET_SENT',
|
||||
}
|
||||
|
||||
def print_():
|
||||
'''gam print userinvitations'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = ['name', 'state', 'updateTime']
|
||||
rows = []
|
||||
filter_ = None
|
||||
orderByList = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'state':
|
||||
state = sys.argv[i + 1].lower().replace('_', '')
|
||||
if state in USERINVITATION_STATE_CHOICES_MAP:
|
||||
filter_ = f"state=='{USERINVITATION_STATE_CHOICES_MAP[state]}'"
|
||||
else:
|
||||
controlflow.expected_argument_exit('state',
|
||||
', '.join(USERINVITATION_STATE_CHOICES_MAP),
|
||||
state)
|
||||
i += 2
|
||||
elif myarg == 'orderby':
|
||||
fieldName = sys.argv[i + 1].lower()
|
||||
i += 2
|
||||
if fieldName in USERINVITATION_ORDERBY_CHOICES_MAP:
|
||||
fieldName = USERINVITATION_ORDERBY_CHOICES_MAP[fieldName]
|
||||
orderBy = ''
|
||||
if i < len(sys.argv):
|
||||
orderBy = sys.argv[i].lower()
|
||||
if orderBy in SORTORDER_CHOICES_MAP:
|
||||
orderBy = SORTORDER_CHOICES_MAP[orderBy]
|
||||
i += 1
|
||||
if orderBy != 'DESCENDING':
|
||||
orderByList.append(fieldName)
|
||||
else:
|
||||
orderByList.append(f'{fieldName} desc')
|
||||
else:
|
||||
controlflow.expected_argument_exit(
|
||||
'orderby', ', '.join(sorted(USERINVITATION_ORDERBY_CHOICES_MAP)),
|
||||
fieldName)
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print userinvitations')
|
||||
if orderByList:
|
||||
orderBy = ' '.join(orderByList)
|
||||
else:
|
||||
orderBy = None
|
||||
gam.printGettingAllItems('User Invitations', filter_)
|
||||
page_message = gapi.got_total_items_msg('User Invitations', '...\n')
|
||||
invitations = gapi.get_all_pages(svc.customers().userinvitations(),
|
||||
'list',
|
||||
'userInvitations',
|
||||
page_message=page_message,
|
||||
parent=customer,
|
||||
filter=filter_,
|
||||
orderBy=orderBy)
|
||||
for invitation in invitations:
|
||||
invitation['name'] = _reduce_name(invitation['name'])
|
||||
row = {}
|
||||
for key, val in invitation.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'User Invitations', todrive)
|
||||
96
src/gam/gapi/contactdelegation.py
Normal file
96
src/gam/gapi/contactdelegation.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Contact Delegation API calls"""
|
||||
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.gapi.directory import users as gapi_directory_users
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('contactdelegation')
|
||||
|
||||
|
||||
def create(users):
|
||||
condel = build()
|
||||
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
|
||||
delegate = gapi_directory_users.get_primary(delegate)
|
||||
if not delegate:
|
||||
controlflow.system_error_exit(5,
|
||||
f'{sys.argv[5]} is not the primary address of a user.')
|
||||
body = {'email': delegate}
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
print(
|
||||
f'Granting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
|
||||
)
|
||||
gapi.call(condel.delegates(),
|
||||
'create',
|
||||
soft_errors=True,
|
||||
user=user,
|
||||
body=body)
|
||||
|
||||
|
||||
def delete(users):
|
||||
condel = build()
|
||||
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
|
||||
delegate = gapi_directory_users.get_primary(delegate)
|
||||
if not delegate:
|
||||
controlflow.system_error_exit(5,
|
||||
f'{sys.argv[5]} is not the primary address of a user.')
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
print(
|
||||
f'Deleting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
|
||||
)
|
||||
gapi.call(condel.delegates(),
|
||||
'delete',
|
||||
soft_errors=True,
|
||||
user=user,
|
||||
delegate=delegate)
|
||||
|
||||
|
||||
def print_(users, csvFormat):
|
||||
condel = build()
|
||||
if csvFormat:
|
||||
todrive = False
|
||||
csv_rows = []
|
||||
titles = ['User', 'delegateAddress']
|
||||
else:
|
||||
csvStyle = False
|
||||
i = 5
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if not csvFormat and myarg == 'csv':
|
||||
csvStyle = True
|
||||
i += 1
|
||||
elif csvFormat and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print contactdelegation')
|
||||
page_message = gapi.got_total_items_msg('Contact Delegates', '...\n')
|
||||
for user in users:
|
||||
delegates = gapi.get_all_pages(condel.delegates(), 'list',
|
||||
'delegates',
|
||||
page_message=page_message,
|
||||
user=user)
|
||||
for delegate in delegates:
|
||||
if csvFormat:
|
||||
csv_rows.append({'User': user, 'delegateAddress': delegate['email']})
|
||||
else:
|
||||
if csvStyle:
|
||||
print(f'{user},{delegate["email"]}')
|
||||
else:
|
||||
print(
|
||||
f'Delegator: {user}\n Delegate Email: {delegate["email"]}\n'
|
||||
)
|
||||
if csvFormat:
|
||||
display.write_csv_file(csv_rows, titles, 'Contact Delegates', todrive)
|
||||
@@ -1,4 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import googleapiclient
|
||||
@@ -15,11 +18,22 @@ from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _display_cros_command_result(cd, device_id, command_id, times_to_check_status):
|
||||
print(f'deviceId: {device_id}, commandId: {command_id}')
|
||||
final_states = {'EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT'}
|
||||
for _ in range(0, times_to_check_status):
|
||||
time.sleep(2)
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
if result.get('state') in final_states:
|
||||
return
|
||||
|
||||
def issue_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
body = {}
|
||||
final_states = ['EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT']
|
||||
valid_commands = gapi.get_enum_values_minus_unspecified(
|
||||
cd._rootDesc['schemas']
|
||||
['DirectoryChromeosdevicesIssueCommandRequest']
|
||||
@@ -40,7 +54,7 @@ def issue_command():
|
||||
body['commandType'] = command_map[command]
|
||||
i += 2
|
||||
if command == 'setvolume':
|
||||
body['payload'] = {'volume': int(sys.argv[i])}
|
||||
body['payload'] = json.dumps({'volume': sys.argv[i]})
|
||||
i += 1
|
||||
elif myarg == 'timestocheckstatus':
|
||||
times_to_check_status = int(sys.argv[i+1])
|
||||
@@ -50,6 +64,8 @@ def issue_command():
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam issuecommand cros')
|
||||
if 'commandType' not in body:
|
||||
controlflow.missing_argument_exit('command <CrOSCommand>', 'gam issuecommand cros')
|
||||
if body['commandType'] == 'WIPE_USERS' and not doit:
|
||||
controlflow.system_error_exit(2, 'wipe_users command requires admin ' \
|
||||
'acknowledge user data will be destroyed with the ' \
|
||||
@@ -67,36 +83,28 @@ def issue_command():
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(4, '400 response from Google. This ' \
|
||||
'usually indicates the devices was not in a state where it will' \
|
||||
' accept the command. For example, reboot and take_a_screenshot' \
|
||||
' accept the command. For example, reboot, set_volume and take_a_screenshot' \
|
||||
' require the device to be in auto-start kiosk app mode.')
|
||||
display.print_json(result)
|
||||
command_id = result.get('commandId')
|
||||
for i in range(0, times_to_check_status):
|
||||
time.sleep(2)
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
state = result.get('state')
|
||||
if state in final_states:
|
||||
break
|
||||
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
|
||||
|
||||
def get_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
command_id = None
|
||||
times_to_check_status = 1
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'commandid':
|
||||
command_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'timestocheckstatus':
|
||||
times_to_check_status = int(sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam getcommand cros')
|
||||
for device_id in devices:
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
|
||||
|
||||
def doUpdateCros():
|
||||
cd = gapi_directory.build()
|
||||
@@ -388,9 +396,10 @@ def doGetCrosInfo():
|
||||
temp_label = tempInfo['label'].strip()
|
||||
temperature = tempInfo['temperature']
|
||||
print(f' {temp_label}: {temperature}')
|
||||
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
|
||||
util = ','.join([str(x) for x in pct_info])
|
||||
print(f' cpuUtilizationPercentageInfo: {util}')
|
||||
if 'cpuUtilizationPercentageInfo' in cpuStatusReport:
|
||||
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
|
||||
util = ','.join([str(x) for x in pct_info])
|
||||
print(f' cpuUtilizationPercentageInfo: {util}')
|
||||
diskVolumeReports = cros.get('diskVolumeReports', [])
|
||||
lenDVR = len(diskVolumeReports)
|
||||
if lenDVR:
|
||||
@@ -825,16 +834,16 @@ def doPrintCrosDevices():
|
||||
if i < lenCSR:
|
||||
nrow['cpuStatusReports.reportTime'] = \
|
||||
cpuStatusReports[i]['reportTime']
|
||||
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo',
|
||||
[])
|
||||
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo', [])
|
||||
for tempInfo in tempInfos:
|
||||
label = tempInfo['label'].strip()
|
||||
base = 'cpuStatusReports.cpuTemperatureInfo.'
|
||||
nrow[f'{base}{label}'] = tempInfo['temperature']
|
||||
cpu_field = 'cpuUtilizationPercentageInfo'
|
||||
cpu_reports = cpuStatusReports[i][cpu_field]
|
||||
cpu_pcts = [str(x) for x in cpu_reports]
|
||||
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
|
||||
if cpu_field in cpuStatusReports[i]:
|
||||
cpu_reports = cpuStatusReports[i][cpu_field]
|
||||
cpu_pcts = [str(x) for x in cpu_reports]
|
||||
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
|
||||
if i < lenDVR:
|
||||
volumeInfo = diskVolumeReports[i]['volumeInfo']
|
||||
j = 0
|
||||
|
||||
@@ -8,18 +8,25 @@ from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import reports as gapi_reports
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return customer
|
||||
|
||||
def doGetCustomerInfo():
|
||||
cd = gapi_directory.build()
|
||||
customer_id = _get_customerid()
|
||||
customer_info = gapi.call(cd.customers(),
|
||||
'get',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID])
|
||||
customerKey=customer_id)
|
||||
print(f'Customer ID: {customer_info["id"]}')
|
||||
print(f'Primary Domain: {customer_info["customerDomain"]}')
|
||||
try:
|
||||
result = gapi.call(
|
||||
cd.domains(),
|
||||
'get',
|
||||
customer=customer_info['id'],
|
||||
customer=customer_id,
|
||||
domainName=customer_info['customerDomain'],
|
||||
fields='verified',
|
||||
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND])
|
||||
@@ -35,7 +42,7 @@ def doGetCustomerInfo():
|
||||
domains = gapi.get_items(cd.domains(),
|
||||
'list',
|
||||
'domains',
|
||||
customer=GC_Values[GC_CUSTOMER_ID],
|
||||
customer=customer_id,
|
||||
fields='domains(creationTime)')
|
||||
for domain in domains:
|
||||
creation_timestamp = int(domain['creationTime']) / 1000
|
||||
@@ -57,9 +64,9 @@ def doGetCustomerInfo():
|
||||
'accounts:num_users': 'Total Users',
|
||||
'accounts:gsuite_basic_total_licenses': 'G Suite Basic Licenses',
|
||||
'accounts:gsuite_basic_used_licenses': 'G Suite Basic Users',
|
||||
'accounts:gsuite_enterprise_total_licenses': 'G Suite Enterprise ' \
|
||||
'accounts:gsuite_enterprise_total_licenses': 'Workspace Enterprise Plus ' \
|
||||
'Licenses',
|
||||
'accounts:gsuite_enterprise_used_licenses': 'G Suite Enterprise ' \
|
||||
'accounts:gsuite_enterprise_used_licenses': 'Workspace Enterprise Plus ' \
|
||||
'Users',
|
||||
'accounts:gsuite_unlimited_total_licenses': 'G Suite Business ' \
|
||||
'Licenses',
|
||||
@@ -67,9 +74,9 @@ def doGetCustomerInfo():
|
||||
}
|
||||
parameters = ','.join(list(user_counts_map))
|
||||
tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT)
|
||||
customerId = GC_Values[GC_CUSTOMER_ID]
|
||||
if customerId == MY_CUSTOMER:
|
||||
customerId = None
|
||||
reports_customer_id = customer_id
|
||||
if reports_customer_id == MY_CUSTOMER:
|
||||
reports_customer_id = None
|
||||
rep = gapi_reports.build()
|
||||
usage = None
|
||||
throw_reasons = [
|
||||
@@ -80,7 +87,7 @@ def doGetCustomerInfo():
|
||||
result = gapi.call(rep.customerUsageReports(),
|
||||
'get',
|
||||
throw_reasons=throw_reasons,
|
||||
customerId=customerId,
|
||||
customerId=reports_customer_id,
|
||||
date=tryDate,
|
||||
parameters=parameters)
|
||||
except gapi.errors.GapiInvalidError as e:
|
||||
@@ -111,6 +118,7 @@ def doGetCustomerInfo():
|
||||
def doUpdateCustomer():
|
||||
cd = gapi_directory.build()
|
||||
body = {}
|
||||
customer_id = _get_customerid()
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -136,14 +144,19 @@ def doUpdateCustomer():
|
||||
'update customer"')
|
||||
gapi.call(cd.customers(),
|
||||
'patch',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID],
|
||||
customerKey=customer_id,
|
||||
body=body)
|
||||
print('Updated customer')
|
||||
|
||||
|
||||
def setTrueCustomerId():
|
||||
if GC_Values[GC_CUSTOMER_ID] == MY_CUSTOMER:
|
||||
cd = gapi_directory.build()
|
||||
GC_Values[GC_CUSTOMER_ID] = gapi.call(cd.customers(), 'get',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID],
|
||||
fields='id').get('id', GC_Values[GC_CUSTOMER_ID])
|
||||
def setTrueCustomerId(cd=None):
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id == MY_CUSTOMER:
|
||||
if not cd:
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.customers(),
|
||||
'get',
|
||||
customerKey=customer_id,
|
||||
fields='id')
|
||||
GC_Values[GC_CUSTOMER_ID] = result.get('id',
|
||||
customer_id)
|
||||
|
||||
@@ -7,18 +7,22 @@ from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
from gam import utils
|
||||
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations
|
||||
|
||||
|
||||
def GroupIsAbuseOrPostmaster(emailAddr):
|
||||
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
|
||||
|
||||
|
||||
def mapGroupEmailForSettings(emailAddr):
|
||||
return emailAddr.replace('/', '%2F')
|
||||
|
||||
|
||||
def create():
|
||||
cd = gapi_directory.build()
|
||||
body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)}
|
||||
gs_get_before_update = got_name = False
|
||||
verifyNotInvitable = False
|
||||
i = 4
|
||||
gs_body = {}
|
||||
gs = None
|
||||
@@ -47,6 +51,9 @@ def create():
|
||||
elif myarg == 'getbeforeupdate':
|
||||
gs_get_before_update = True
|
||||
i += 1
|
||||
elif myarg == 'verifynotinvitable':
|
||||
verifyNotInvitable = True
|
||||
i += 1
|
||||
else:
|
||||
if not gs:
|
||||
gs = gam.buildGAPIObject('groupssettings')
|
||||
@@ -56,6 +63,10 @@ def create():
|
||||
i += 2
|
||||
if not got_name:
|
||||
body['name'] = body['email']
|
||||
if (verifyNotInvitable and
|
||||
gapi_cloudidentity_userinvitations.get_is_invitable_user(body['email'])):
|
||||
sys.stderr.write(f'Group not created, {body["email"]} is an unmanaged account\n')
|
||||
sys.exit(51)
|
||||
print(f'Creating group {body["email"]}')
|
||||
gapi.call(cd.groups(), 'insert', body=body, fields='email')
|
||||
if gs and not GroupIsAbuseOrPostmaster(body['email']):
|
||||
@@ -67,7 +78,7 @@ def create():
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.NOT_FOUND
|
||||
],
|
||||
groupUniqueId=body['email'],
|
||||
groupUniqueId=mapGroupEmailForSettings(body['email']),
|
||||
fields='*')
|
||||
if current_settings is not None:
|
||||
gs_body = dict(
|
||||
@@ -75,7 +86,7 @@ def create():
|
||||
if gs_body:
|
||||
gapi.call(gs.groups(),
|
||||
'update',
|
||||
groupUniqueId=body['email'],
|
||||
groupUniqueId=mapGroupEmailForSettings(body['email']),
|
||||
retry_reasons=[
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.NOT_FOUND
|
||||
@@ -170,7 +181,7 @@ def info(group_name=None):
|
||||
'get',
|
||||
throw_reasons=[gapi_errors.ErrorReason.AUTH_ERROR],
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=basic_info['email']
|
||||
groupUniqueId=mapGroupEmailForSettings(basic_info['email'])
|
||||
) # Use email address retrieved from cd since GS API doesn't support uid
|
||||
if settings is None:
|
||||
settings = {}
|
||||
@@ -255,6 +266,8 @@ GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'customReplyTo',
|
||||
'defaultmessagedenynotificationtext':
|
||||
'defaultMessageDenyNotificationText',
|
||||
'defaultsender':
|
||||
'defaultSender',
|
||||
'enablecollaborativeinbox':
|
||||
'enableCollaborativeInbox',
|
||||
'favoriterepliesontop':
|
||||
@@ -589,7 +602,7 @@ def print_():
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.INVALID
|
||||
],
|
||||
groupUniqueId=groupEmail,
|
||||
groupUniqueId=mapGroupEmailForSettings(groupEmail),
|
||||
fields=gsfields)
|
||||
if settings:
|
||||
for key in settings:
|
||||
@@ -968,6 +981,9 @@ def update():
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n'
|
||||
)
|
||||
for user in to_remove:
|
||||
items.append(
|
||||
['gam', 'update', 'group', group, 'remove', user])
|
||||
for user in to_add:
|
||||
item = ['gam', 'update', 'group', group, 'add']
|
||||
if role:
|
||||
@@ -976,9 +992,6 @@ def update():
|
||||
item.append(delivery)
|
||||
item.append(user)
|
||||
items.append(item)
|
||||
for user in to_remove:
|
||||
items.append(
|
||||
['gam', 'update', 'group', group, 'remove', user])
|
||||
elif myarg in ['delete', 'remove']:
|
||||
_, users_email, _ = _getRoleAndUsers()
|
||||
if not exists(cd, group):
|
||||
@@ -1134,6 +1147,7 @@ def update():
|
||||
else:
|
||||
i = 4
|
||||
use_cd_api = False
|
||||
verifyNotInvitable = False
|
||||
gs = None
|
||||
gs_body = {}
|
||||
cd_body = {}
|
||||
@@ -1151,6 +1165,9 @@ def update():
|
||||
elif myarg == 'getbeforeupdate':
|
||||
gs_get_before_update = True
|
||||
i += 1
|
||||
elif myarg == 'verifynotinvitable':
|
||||
verifyNotInvitable = True
|
||||
i += 1
|
||||
else:
|
||||
if not gs:
|
||||
gs = gam.buildGAPIObject('groupssettings')
|
||||
@@ -1162,6 +1179,10 @@ def update():
|
||||
if use_cd_api or (
|
||||
group.find('@') == -1
|
||||
): # group settings API won't take uid so we make sure cd API is used so that we can grab real email.
|
||||
if (verifyNotInvitable and 'email' in cd_body and
|
||||
gapi_cloudidentity_userinvitations.get_is_invitable_user(cd_body['email'])):
|
||||
sys.stderr.write(f'Group {group} not updated, new email {cd_body["email"]} is an unmanaged account\n')
|
||||
sys.exit(51)
|
||||
group = gapi.call(cd.groups(),
|
||||
'update',
|
||||
groupKey=group,
|
||||
@@ -1174,7 +1195,7 @@ def update():
|
||||
gs.groups(),
|
||||
'get',
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=group,
|
||||
groupUniqueId=mapGroupEmailForSettings(group),
|
||||
fields='*')
|
||||
if current_settings is not None:
|
||||
gs_body = dict(
|
||||
@@ -1185,7 +1206,7 @@ def update():
|
||||
gs.groups(),
|
||||
'update',
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=group,
|
||||
groupUniqueId=mapGroupEmailForSettings(group),
|
||||
body=gs_body)
|
||||
print(f'updated group {group}')
|
||||
|
||||
@@ -1200,7 +1221,7 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
||||
params) in list(gs_object['schemas']['Groups']['properties'].items()):
|
||||
if attrib in ['kind', 'etag', 'email']:
|
||||
continue
|
||||
if myarg == attrib.lower():
|
||||
if myarg == attrib.lower().replace('_', ''):
|
||||
if params['type'] == 'integer':
|
||||
try:
|
||||
if value[-1:].upper() == 'M':
|
||||
|
||||
@@ -27,13 +27,13 @@ def info():
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
resourceId=resourceId)
|
||||
if 'deviceId' in info:
|
||||
if 'deviceId' in device_info:
|
||||
device_info['deviceId'] = device_info['deviceId'].encode('unicode-escape').decode(
|
||||
UTF8)
|
||||
attrib = 'securityPatchLevel'
|
||||
if attrib in info and int(device_info[attrib]):
|
||||
if attrib in device_info and int(device_info[attrib]):
|
||||
device_info[attrib] = utils.formatTimestampYMDHMS(device_info[attrib])
|
||||
display.print_json(info)
|
||||
display.print_json(device_info)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -402,20 +402,18 @@ def getOrgUnitId(orgUnit, cd=None):
|
||||
return (orgUnit, result['orgUnitId'])
|
||||
|
||||
|
||||
def buildOrgUnitIdToNameMap():
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.orgunits(),
|
||||
'list',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
fields='organizationUnits(orgUnitPath,orgUnitId)',
|
||||
type='all')
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME] = {}
|
||||
for orgUnit in result['organizationUnits']:
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][
|
||||
orgUnit['orgUnitId']] = orgUnit['orgUnitPath']
|
||||
|
||||
|
||||
def orgunit_from_orgunitid(orgunitid):
|
||||
if not GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME]:
|
||||
buildOrgUnitIdToNameMap()
|
||||
return GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid, orgunitid)
|
||||
def orgunit_from_orgunitid(orgunitid, cd=None):
|
||||
if cd is None:
|
||||
cd = gapi_directory.build()
|
||||
orgunitpath = GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid)
|
||||
if not orgunitpath:
|
||||
try:
|
||||
orgunitpath = gapi.call(cd.orgunits(),
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
orgUnitPath=f'id:{orgunitid}' if not orgunitid.startswith('id:') else orgunitid,
|
||||
fields='orgUnitPath')['orgUnitPath']
|
||||
except:
|
||||
orgunitpath = orgunitid
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][orgunitid] = orgunitpath
|
||||
return orgunitpath
|
||||
|
||||
187
src/gam/gapi/directory/printers.py
Normal file
187
src/gam/gapi/directory/printers.py
Normal file
@@ -0,0 +1,187 @@
|
||||
'''Commands to manage directory printers.'''
|
||||
# pylint: disable=unused-wildcard-import wildcard-import
|
||||
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.var import *
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer in "customers/C{customer}" format needed for this API'''
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
def _get_printer_attributes(i, cdapi=None):
|
||||
'''get printer attributes for create/update commands'''
|
||||
body = {}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'description':
|
||||
body['description'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'displayname':
|
||||
body['displayName'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'makeandmodel':
|
||||
body['makeAndModel'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'orgunitid']:
|
||||
_, body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i+1], cdapi)
|
||||
body['orgUnitId'] = body['orgUnitId'][3:]
|
||||
i += 2
|
||||
elif myarg == 'uri':
|
||||
body['uri'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in {'driverless', 'usedriverlessconfig'}:
|
||||
body['useDriverlessConfig'] = True
|
||||
i += 1
|
||||
return body
|
||||
|
||||
|
||||
def create():
|
||||
'''gam create printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
body = _get_printer_attributes(3, cdapi)
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'create',
|
||||
parent=parent,
|
||||
body=body)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def delete():
|
||||
'''gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer_id = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
if printer_id.lower() not in {'file', 'csvfile'}:
|
||||
printer_ids = printer_id.replace(',', ' ').split()
|
||||
else:
|
||||
printer_ids = gam.getUsersToModify(f'cros{printer_id.lower()}', sys.argv[4])
|
||||
# max 50 per API call
|
||||
batch_size = 50
|
||||
for chunk in range(0, len(printer_ids), batch_size):
|
||||
body = {
|
||||
'printerIds': printer_ids[chunk:chunk + batch_size]
|
||||
}
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'batchDeletePrinters',
|
||||
parent=customer_id,
|
||||
body=body)
|
||||
for printer_id in result.get('printerIds', []):
|
||||
print(f'Deleted printer {printer_id}')
|
||||
for printer_id in result.get('failedPrinters', []):
|
||||
print(f'ERROR: failed to delete {printer_id.get("printerIds")}')
|
||||
|
||||
|
||||
def info():
|
||||
'''gam info printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
name = f'{customer}/chrome/printers/{printer_id}'
|
||||
printer = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'get',
|
||||
name=name)
|
||||
if 'orgUnitId' in printer:
|
||||
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
|
||||
printer['orgUnitId'], cdapi)
|
||||
display.print_json(printer)
|
||||
|
||||
|
||||
def print_():
|
||||
'''gam print printers'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
filter_ = None
|
||||
todrive = False
|
||||
titles = []
|
||||
rows = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'filter':
|
||||
filter_ = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
|
||||
printers = gapi.get_all_pages(cdapi.customers().chrome().printers(),
|
||||
'list',
|
||||
items='printers',
|
||||
parent=parent,
|
||||
filter=filter_)
|
||||
for printer in printers:
|
||||
if 'orgUnitId' in printer:
|
||||
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
|
||||
printer['orgUnitId'], cdapi)
|
||||
row = {}
|
||||
for key, val in printer.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'Printers', todrive)
|
||||
|
||||
|
||||
def print_models():
|
||||
'''gam print printermodels'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
filter_ = None
|
||||
todrive = False
|
||||
titles = []
|
||||
rows = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'filter':
|
||||
filter_ = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
|
||||
models = gapi.get_all_pages(cdapi.customers().chrome().printers(),
|
||||
'listPrinterModels',
|
||||
items='printerModels',
|
||||
parent=parent,
|
||||
pageSize=10000,
|
||||
filter=filter_)
|
||||
for model in models:
|
||||
row = {}
|
||||
for key, val in model.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'Printer Models', todrive)
|
||||
|
||||
|
||||
def update():
|
||||
'''gam update printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
name = f'{customer}/chrome/printers/{printer_id}'
|
||||
body = _get_printer_attributes(4, cdapi)
|
||||
update_mask = ','.join(body)
|
||||
# note clearMask seems unnecessary. Updating field to '' clears it.
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'patch',
|
||||
name=name,
|
||||
updateMask=update_mask,
|
||||
body=body)
|
||||
display.print_json(result)
|
||||
@@ -1,6 +1,22 @@
|
||||
from time import sleep
|
||||
|
||||
import gam
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import errors as gapi_errors
|
||||
|
||||
|
||||
def get_primary(email):
|
||||
'''returns primary email of user or empty if email is not a user primary or
|
||||
alias address.'''
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.users(), 'get', userKey=email,
|
||||
projection='basic', fields='primaryEmail',
|
||||
soft_errors=True)
|
||||
if not result:
|
||||
return ''
|
||||
return result.get('primaryEmail', '').lower()
|
||||
|
||||
|
||||
def signout(users):
|
||||
cd = gapi_directory.build()
|
||||
@@ -28,3 +44,28 @@ def turn_off_2sv(users):
|
||||
'turnOff',
|
||||
soft_errors=True,
|
||||
userKey=user)
|
||||
|
||||
def wait_for_mailbox(users):
|
||||
'''Wait until users mailbox is provisioned.'''
|
||||
cd = gapi_directory.build()
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user = gam.normalizeEmailAddressOrUID(user)
|
||||
while True:
|
||||
try:
|
||||
result = gapi.call(cd.users(),
|
||||
'get',
|
||||
'fields=isMailboxSetup',
|
||||
userKey=user,
|
||||
throw_reasons=[gapi_errors.ErrorReason.USER_NOT_FOUND])
|
||||
except gapi_errors.GapiUserNotFoundError:
|
||||
print(f'{user} mailboxIsSetup: False (user does not exist yet)')
|
||||
sleep(3)
|
||||
continue
|
||||
mailbox_is_setup = result.get('isMailboxSetup')
|
||||
print(f'{user} mailboxIsSetup: {mailbox_is_setup}')
|
||||
if mailbox_is_setup:
|
||||
break
|
||||
sleep(3)
|
||||
|
||||
@@ -60,6 +60,10 @@ class GapiGroupNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GapiInternalServerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GapiInvalidError(Exception):
|
||||
pass
|
||||
|
||||
@@ -116,13 +120,16 @@ class ErrorReason(Enum):
|
||||
DUPLICATE = 'duplicate'
|
||||
FAILED_PRECONDITION = 'failedPrecondition'
|
||||
FORBIDDEN = 'forbidden'
|
||||
FIVE_O_THREE = '503'
|
||||
FOUR_O_NINE = '409'
|
||||
FOUR_O_O = '400'
|
||||
FOUR_O_FOUR = '404'
|
||||
FOUR_O_THREE = '403'
|
||||
FOUR_TWO_NINE = '429'
|
||||
GATEWAY_TIMEOUT = 'gatewayTimeout'
|
||||
GROUP_NOT_FOUND = 'groupNotFound'
|
||||
INTERNAL_ERROR = 'internalError'
|
||||
INTERNAL_SERVER_ERROR = 'internalServerError'
|
||||
INVALID = 'invalid'
|
||||
INVALID_ARGUMENT = 'invalidArgument'
|
||||
INVALID_MEMBER = 'invalidMember'
|
||||
@@ -153,6 +160,7 @@ DEFAULT_RETRY_REASONS = [
|
||||
ErrorReason.GATEWAY_TIMEOUT,
|
||||
ErrorReason.INTERNAL_ERROR,
|
||||
ErrorReason.FOUR_TWO_NINE,
|
||||
ErrorReason.FIVE_O_THREE,
|
||||
]
|
||||
GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE]
|
||||
GROUP_GET_THROW_REASONS = [
|
||||
@@ -196,6 +204,8 @@ ERROR_REASON_TO_EXCEPTION = {
|
||||
GapiGatewayTimeoutError,
|
||||
ErrorReason.GROUP_NOT_FOUND:
|
||||
GapiGroupNotFoundError,
|
||||
ErrorReason.INTERNAL_SERVER_ERROR:
|
||||
GapiInternalServerError,
|
||||
ErrorReason.INVALID:
|
||||
GapiInvalidError,
|
||||
ErrorReason.INVALID_ARGUMENT:
|
||||
@@ -333,6 +343,10 @@ def get_gapi_error_detail(e,
|
||||
if 'Requested entity was not found' in message or 'does not exist' in message:
|
||||
error = _create_http_error_dict(404, ErrorReason.NOT_FOUND.value,
|
||||
message)
|
||||
elif http_status == 500:
|
||||
if 'Failed to convert server response to JSON' in message:
|
||||
error = _create_http_error_dict(500, ErrorReason.INTERNAL_SERVER_ERROR.value,
|
||||
message)
|
||||
else:
|
||||
if 'error_description' in error:
|
||||
if error['error_description'] == 'Invalid Value':
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import patch
|
||||
|
||||
import googleapiclient.errors
|
||||
from gam.gapi import errors
|
||||
import httplib2
|
||||
|
||||
|
||||
def create_simple_http_error(status, reason, message):
|
||||
@@ -15,10 +16,10 @@ def create_simple_http_error(status, reason, message):
|
||||
|
||||
|
||||
def create_http_error(status, content):
|
||||
response = {
|
||||
response = httplib2.Response({
|
||||
'status': status,
|
||||
'content-type': 'application/json',
|
||||
}
|
||||
})
|
||||
content_as_bytes = json.dumps(content).encode('UTF-8')
|
||||
return googleapiclient.errors.HttpError(response, content_as_bytes)
|
||||
|
||||
@@ -73,6 +74,7 @@ class ErrorsTest(unittest.TestCase):
|
||||
def test_get_gapi_error_extracts_user_not_found(self):
|
||||
err = create_simple_http_error(404, 'notFound',
|
||||
'Resource Not Found: userKey.')
|
||||
print(err)
|
||||
http_status, reason, message = errors.get_gapi_error_detail(err)
|
||||
self.assertEqual(http_status, 404)
|
||||
self.assertEqual(reason, errors.ErrorReason.USER_NOT_FOUND.value)
|
||||
@@ -158,7 +160,7 @@ class ErrorsTest(unittest.TestCase):
|
||||
|
||||
def test_get_gapi_error_extracts_single_error_with_message(self):
|
||||
status_code = 999
|
||||
response = {'status': status_code}
|
||||
response = httplib2.Response({'status': status_code})
|
||||
# This error does not have an "errors" key describing each error.
|
||||
content = {'error': {'code': status_code, 'message': 'unknown error'}}
|
||||
content_as_bytes = json.dumps(content).encode('UTF-8')
|
||||
@@ -172,7 +174,7 @@ class ErrorsTest(unittest.TestCase):
|
||||
def test_get_gapi_error_exits_code_4_on_malformed_error_with_unknown_description(
|
||||
self):
|
||||
status_code = 999
|
||||
response = {'status': status_code}
|
||||
response = httplib2.Response({'status': status_code})
|
||||
# This error only has an error_description_field and an unknown description.
|
||||
content = {'error_description': 'something errored'}
|
||||
content_as_bytes = json.dumps(content).encode('UTF-8')
|
||||
@@ -184,7 +186,7 @@ class ErrorsTest(unittest.TestCase):
|
||||
|
||||
def test_get_gapi_error_exits_on_invalid_error_description(self):
|
||||
status_code = 400
|
||||
response = {'status': status_code}
|
||||
response = httplib2.Response({'status': status_code})
|
||||
content = {'error_description': 'Invalid Value'}
|
||||
content_as_bytes = json.dumps(content).encode('UTF-8')
|
||||
err = googleapiclient.errors.HttpError(response, content_as_bytes)
|
||||
@@ -196,7 +198,7 @@ class ErrorsTest(unittest.TestCase):
|
||||
|
||||
def test_get_gapi_error_exits_code_4_on_unexpected_error_contents(self):
|
||||
status_code = 900
|
||||
response = {'status': status_code}
|
||||
response = httplib2.Response({'status': status_code})
|
||||
content = {'notErrorContentThatIsExpected': 'foo'}
|
||||
content_as_bytes = json.dumps(content).encode('UTF-8')
|
||||
err = googleapiclient.errors.HttpError(response, content_as_bytes)
|
||||
|
||||
@@ -7,8 +7,17 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customerId with format C{customer_id}'''
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id[0] != 'C':
|
||||
customer_id = 'C' + customer_id
|
||||
return customer_id
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('licensing')
|
||||
|
||||
@@ -127,6 +136,7 @@ def print_(returnFields=None,
|
||||
countsOnly=False,
|
||||
returnCounts=False):
|
||||
lic = build()
|
||||
customer_id = _get_customerid()
|
||||
products = []
|
||||
licenses = []
|
||||
licenseCounts = []
|
||||
@@ -193,7 +203,7 @@ def print_(returnFields=None,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
customerId=customer_id,
|
||||
productId=product,
|
||||
skuId=sku,
|
||||
fields=fields)
|
||||
@@ -223,7 +233,7 @@ def print_(returnFields=None,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
customerId=customer_id,
|
||||
productId=productId,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
|
||||
@@ -285,7 +285,7 @@ def showReport():
|
||||
customerId = GC_Values[GC_CUSTOMER_ID]
|
||||
if customerId == MY_CUSTOMER:
|
||||
customerId = None
|
||||
filters = parameters = actorIpAddress = startTime = endTime = eventName = orgUnitId = None
|
||||
filters = parameters = actorIpAddress = groupIdFilter = startTime = endTime = eventName = orgUnitId = None
|
||||
tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT)
|
||||
to_drive = False
|
||||
userKey = 'all'
|
||||
@@ -330,6 +330,9 @@ def showReport():
|
||||
elif myarg == 'ip':
|
||||
actorIpAddress = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'groupidfilter':
|
||||
groupIdFilter = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
to_drive = True
|
||||
i += 1
|
||||
@@ -489,7 +492,8 @@ def showReport():
|
||||
endTime=endTime,
|
||||
eventName=eventName,
|
||||
filters=filters,
|
||||
orgUnitID=orgUnitId)
|
||||
orgUnitID=orgUnitId,
|
||||
groupIdFilter=groupIdFilter)
|
||||
if activities:
|
||||
titles = ['name']
|
||||
csvRows = []
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
import googleapiclient.http
|
||||
|
||||
@@ -11,6 +12,7 @@ from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi import storage as gapi_storage
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
@@ -84,21 +86,120 @@ VAULT_SEARCH_METHODS_MAP = {
|
||||
VAULT_SEARCH_METHODS_LIST = [
|
||||
'accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone'
|
||||
]
|
||||
QUERY_ARGS = ['corpus', 'scope', 'terms', 'start', 'starttime',
|
||||
'end', 'endtime', 'timezone', 'excludedrafts',
|
||||
'driveversiondate', 'includeshareddrives', 'includeteamdrives',
|
||||
'includerooms'] + list(VAULT_SEARCH_METHODS_MAP.keys())
|
||||
|
||||
def _build_query(query, myarg, i, query_discovery):
|
||||
if not query:
|
||||
query = {'dataScope': 'ALL_DATA'}
|
||||
if myarg == 'corpus':
|
||||
query['corpus'] = sys.argv[i + 1].upper()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['corpus']['enum'])
|
||||
if query['corpus'] not in allowed_corpuses:
|
||||
controlflow.expected_argument_exit('corpus',
|
||||
', '.join(allowed_corpuses),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in VAULT_SEARCH_METHODS_MAP:
|
||||
if query.get('searchMethod'):
|
||||
message = f'Multiple search methods ' \
|
||||
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
|
||||
f'specified, only one is allowed'
|
||||
controlflow.system_error_exit(3, message)
|
||||
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
|
||||
query['searchMethod'] = searchMethod
|
||||
if searchMethod == 'ACCOUNT':
|
||||
query['accountInfo'] = {
|
||||
'emails': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ORG_UNIT':
|
||||
query['orgUnitInfo'] = {
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'SHARED_DRIVE':
|
||||
query['sharedDriveInfo'] = {
|
||||
'sharedDriveIds': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ROOM':
|
||||
query['hangoutsChatInfo'] = {
|
||||
'roomId': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
elif myarg == 'scope':
|
||||
query['dataScope'] = sys.argv[i + 1].upper()
|
||||
allowed_scopes = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['dataScope']['enum'])
|
||||
if query['dataScope'] not in allowed_scopes:
|
||||
controlflow.expected_argument_exit('scope',
|
||||
', '.join(allowed_scopes),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['terms']:
|
||||
query['terms'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
query['startTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['end', 'endtime']:
|
||||
query['endTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['timezone']:
|
||||
query['timeZone'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['excludedrafts']:
|
||||
query['mailOptions'] = {
|
||||
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['driveversiondate']:
|
||||
query.setdefault('driveOptions', {})['versionDate'] = \
|
||||
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['includeshareddrives', 'includeteamdrives']:
|
||||
query.setdefault(
|
||||
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
|
||||
sys.argv[i + 1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['includerooms']:
|
||||
query['hangoutsChatOptions'] = {
|
||||
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
return (query, i)
|
||||
|
||||
def _validate_query(query, query_discovery):
|
||||
if 'corpus' not in query:
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['corpus']['enum'])
|
||||
controlflow.system_error_exit(3, 'you must specify a corpus. ' \
|
||||
f'Choose one of {", ".join(allowed_corpuses)}')
|
||||
if 'searchMethod' not in query:
|
||||
controlflow.system_error_exit(3, f'you must specify a search method. ' \
|
||||
'Choose one of ' \
|
||||
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
|
||||
|
||||
|
||||
def createExport():
|
||||
v = buildGAPIObject()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
|
||||
allowed_scopes = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
|
||||
query_discovery = v._rootDesc['schemas']['Query']
|
||||
allowed_formats = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['MailExportOptions']['properties']
|
||||
['exportFormat']['enum'])
|
||||
export_format = 'MBOX'
|
||||
showConfidentialModeContent = None # default to not even set
|
||||
matterId = None
|
||||
body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}}
|
||||
query = None
|
||||
body = {'exportOptions': {}}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -109,83 +210,8 @@ def createExport():
|
||||
elif myarg == 'name':
|
||||
body['name'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'corpus':
|
||||
body['query']['corpus'] = sys.argv[i + 1].upper()
|
||||
if body['query']['corpus'] not in allowed_corpuses:
|
||||
controlflow.expected_argument_exit('corpus',
|
||||
', '.join(allowed_corpuses),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in VAULT_SEARCH_METHODS_MAP:
|
||||
if body['query'].get('searchMethod'):
|
||||
message = f'Multiple search methods ' \
|
||||
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
|
||||
f'specified, only one is allowed'
|
||||
controlflow.system_error_exit(3, message)
|
||||
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
|
||||
body['query']['searchMethod'] = searchMethod
|
||||
if searchMethod == 'ACCOUNT':
|
||||
body['query']['accountInfo'] = {
|
||||
'emails': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ORG_UNIT':
|
||||
body['query']['orgUnitInfo'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'SHARED_DRIVE':
|
||||
body['query']['sharedDriveInfo'] = {
|
||||
'sharedDriveIds': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ROOM':
|
||||
body['query']['hangoutsChatInfo'] = {
|
||||
'roomId': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
elif myarg == 'scope':
|
||||
body['query']['dataScope'] = sys.argv[i + 1].upper()
|
||||
if body['query']['dataScope'] not in allowed_scopes:
|
||||
controlflow.expected_argument_exit('scope',
|
||||
', '.join(allowed_scopes),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['terms']:
|
||||
body['query']['terms'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
body['query']['startTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['end', 'endtime']:
|
||||
body['query']['endTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['timezone']:
|
||||
body['query']['timeZone'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['excludedrafts']:
|
||||
body['query']['mailOptions'] = {
|
||||
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['driveversiondate']:
|
||||
body['query'].setdefault('driveOptions', {})['versionDate'] = \
|
||||
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['includeshareddrives', 'includeteamdrives']:
|
||||
body['query'].setdefault(
|
||||
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
|
||||
sys.argv[i + 1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['includerooms']:
|
||||
body['query']['hangoutsChatOptions'] = {
|
||||
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in QUERY_ARGS:
|
||||
query, i = _build_query(query, myarg, i, query_discovery)
|
||||
elif myarg in ['format']:
|
||||
export_format = sys.argv[i + 1].upper()
|
||||
if export_format not in allowed_formats:
|
||||
@@ -216,13 +242,8 @@ def createExport():
|
||||
if not matterId:
|
||||
controlflow.system_error_exit(
|
||||
3, 'you must specify a matter for the new export.')
|
||||
if 'corpus' not in body['query']:
|
||||
controlflow.system_error_exit(3, f'you must specify a corpus for the ' \
|
||||
f'new export. Choose one of {", ".join(allowed_corpuses)}')
|
||||
if 'searchMethod' not in body['query']:
|
||||
controlflow.system_error_exit(3, f'you must specify a search method ' \
|
||||
'for the new export. Choose one of ' \
|
||||
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
|
||||
_validate_query(query, query_discovery)
|
||||
body['query'] = query
|
||||
if 'name' not in body:
|
||||
corpus_name = body['query']['corpus']
|
||||
corpus_date = datetime.datetime.now()
|
||||
@@ -270,6 +291,81 @@ def getExportInfo():
|
||||
display.print_json(export)
|
||||
|
||||
|
||||
def print_count():
|
||||
v = buildGAPIObject()
|
||||
query_discovery = v._rootDesc['schemas']['Query']
|
||||
matterId = None
|
||||
operation_wait = 15
|
||||
query = None
|
||||
body = {'view': 'ALL'}
|
||||
name = None
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'matter':
|
||||
matterId = getMatterItem(v, sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'operation':
|
||||
name = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in QUERY_ARGS:
|
||||
query, i = _build_query(query, myarg, i, query_discovery)
|
||||
elif myarg == 'wait':
|
||||
operation_wait = int(sys.argv[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam create export')
|
||||
if not matterId:
|
||||
controlflow.system_error_exit(
|
||||
3, 'you must specify a matter for the count.')
|
||||
if name:
|
||||
operation = {'name': name}
|
||||
else:
|
||||
_validate_query(query, query_discovery)
|
||||
body['query'] = query
|
||||
operation = gapi.call(v.matters(), 'count', matterId=matterId, body=body)
|
||||
print(f'Watching operation {operation["name"]}...')
|
||||
while not operation.get('done'):
|
||||
print(f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds')
|
||||
sleep(operation_wait)
|
||||
operation = gapi.call(v.operations(), 'get', name=operation['name'])
|
||||
response = operation.get('response', {})
|
||||
query = operation['metadata']['query']
|
||||
search_method = query.get('searchMethod')
|
||||
# ARGH count results don't include accounts with zero items.
|
||||
# so we keep track of which accounts we searched and can report
|
||||
# zero data for them.
|
||||
if search_method == 'ACCOUNT':
|
||||
query_accounts = query.get('accountInfo', [])
|
||||
elif search_method == 'ENTIRE_ORG':
|
||||
query_accounts = gam.getUsersToModify('all', 'users')
|
||||
elif search_method == 'ORG_UNIT':
|
||||
org_unit = query['orgUnitInfo']['orgUnitId']
|
||||
query_accounts = gam.getUsersToModify('ou', org_unit)
|
||||
mailcounts = response.get('mailCountResult', {})
|
||||
groupcounts = response.get('groupsCountResult', {})
|
||||
csv_rows = []
|
||||
for a_count in [mailcounts, groupcounts]:
|
||||
for errored_account in a_count.get('accountCountErrors', []):
|
||||
account = errored_account.get('account')
|
||||
csv_rows.append({'account': account, 'error': errored_account.get('errorType')})
|
||||
if account in query_accounts: query_accounts.remove(account)
|
||||
for account in a_count.get('nonQueryableAccounts', []):
|
||||
csv_rows.append({'account': account, 'error': 'Not queried because not on hold'})
|
||||
if account in query_accounts: query_accounts.remove(account)
|
||||
for account in a_count.get('accountCounts', []):
|
||||
email = account.get('account', {}).get('email', '')
|
||||
csv_rows.append({'account': email, 'count': account.get('count')})
|
||||
if email in query_accounts: query_accounts.remove(email)
|
||||
for account in query_accounts:
|
||||
csv_rows.append({'account': account, 'count': 0})
|
||||
titles = ['account', 'count', 'error']
|
||||
display.write_csv_file(csv_rows, titles, 'Vault Counts', todrive)
|
||||
|
||||
def createHold():
|
||||
v = buildGAPIObject()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
@@ -301,7 +397,7 @@ def createHold():
|
||||
i += 2
|
||||
elif myarg in ['orgunit', 'ou']:
|
||||
body['orgUnit'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
@@ -407,7 +503,7 @@ def getHoldInfo():
|
||||
acct_email = gam.convertUIDtoEmailAddress(uid, cd, [account_type])
|
||||
results['accounts'][i]['email'] = acct_email
|
||||
if 'orgUnit' in results:
|
||||
results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo(
|
||||
results['orgUnit']['orgUnitPath'] = gapi_directory_orgunits.info(
|
||||
results['orgUnit']['orgUnitId'], return_attrib='orgUnitPath')
|
||||
display.print_json(results)
|
||||
|
||||
@@ -496,7 +592,7 @@ def updateHold():
|
||||
i += 2
|
||||
elif myarg in ['orgunit', 'ou']:
|
||||
body['orgUnit'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
|
||||
@@ -40,19 +40,19 @@ def create_http(cache=None,
|
||||
return httpObj
|
||||
|
||||
|
||||
def create_request(http=None):
|
||||
def create_request(httpObj=None):
|
||||
"""Creates a uniform Request object with a default http, if not provided.
|
||||
|
||||
Args:
|
||||
http: Optional httplib2.Http compatible object to be used with the request.
|
||||
httpObj: Optional httplib2.Http compatible object to be used with the request.
|
||||
If not provided, a default HTTP will be used.
|
||||
|
||||
Returns:
|
||||
Request: A google_auth_httplib2.Request compatible Request.
|
||||
"""
|
||||
if not http:
|
||||
http = create_http()
|
||||
return Request(http)
|
||||
if not httpObj:
|
||||
httpObj = create_http()
|
||||
return Request(httpObj)
|
||||
|
||||
|
||||
GAM_USER_AGENT = GAM_INFO
|
||||
|
||||
@@ -64,7 +64,7 @@ class TransportTest(unittest.TestCase):
|
||||
self.assertEqual(request.http, mock_create_http.return_value)
|
||||
|
||||
def test_create_request_uses_provided_http(self):
|
||||
request = transport.create_request(http=self.mock_http)
|
||||
request = transport.create_request(httpObj=self.mock_http)
|
||||
self.assertEqual(request.http, self.mock_http)
|
||||
|
||||
def test_create_request_returns_request_with_forced_user_agent(self):
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import sys
|
||||
@@ -5,8 +9,10 @@ import time
|
||||
from hashlib import md5
|
||||
from html.entities import name2codepoint
|
||||
from html.parser import HTMLParser
|
||||
import importlib
|
||||
import json
|
||||
import dateutil.parser
|
||||
import types
|
||||
|
||||
from gam import controlflow
|
||||
from gam import fileutils
|
||||
@@ -14,6 +20,41 @@ from gam import transport
|
||||
from gam.var import *
|
||||
|
||||
|
||||
class LazyLoader(types.ModuleType):
|
||||
"""Lazily import a module, mainly to avoid pulling in large dependencies.
|
||||
|
||||
`contrib`, and `ffmpeg` are examples of modules that are large and not always
|
||||
needed, and this allows them to only be loaded when they are used.
|
||||
"""
|
||||
|
||||
# The lint error here is incorrect.
|
||||
def __init__(self, local_name, parent_module_globals, name): # pylint: disable=super-on-old-class
|
||||
self._local_name = local_name
|
||||
self._parent_module_globals = parent_module_globals
|
||||
|
||||
super(LazyLoader, self).__init__(name)
|
||||
|
||||
def _load(self):
|
||||
# Import the target module and insert it into the parent's namespace
|
||||
module = importlib.import_module(self.__name__)
|
||||
self._parent_module_globals[self._local_name] = module
|
||||
|
||||
# Update this object's dict so that if someone keeps a reference to the
|
||||
# LazyLoader, lookups are efficient (__getattr__ is only called on lookups
|
||||
# that fail).
|
||||
self.__dict__.update(module.__dict__)
|
||||
|
||||
return module
|
||||
|
||||
def __getattr__(self, item):
|
||||
module = self._load()
|
||||
return getattr(module, item)
|
||||
|
||||
def __dir__(self):
|
||||
module = self._load()
|
||||
return dir(module)
|
||||
|
||||
|
||||
class _DeHTMLParser(HTMLParser):
|
||||
|
||||
def __init__(self):
|
||||
@@ -59,6 +100,16 @@ class _DeHTMLParser(HTMLParser):
|
||||
re.sub(r'\n +', '\n', ''.join(self.__text))).strip()
|
||||
|
||||
|
||||
def commonprefix(m):
|
||||
'''Given a list of strings m, return string which is prefix common to all'''
|
||||
s1 = min(m)
|
||||
s2 = max(m)
|
||||
for i, c in enumerate(s1):
|
||||
if c != s2[i]:
|
||||
return s1[:i]
|
||||
return s1
|
||||
|
||||
|
||||
def dehtml(text):
|
||||
try:
|
||||
parser = _DeHTMLParser()
|
||||
@@ -203,6 +254,18 @@ def get_delta_time(argstr):
|
||||
return deltaTime
|
||||
|
||||
|
||||
def get_hhmm(argstr):
|
||||
argstr = argstr.strip()
|
||||
if argstr:
|
||||
try:
|
||||
dateTime = datetime.datetime.strptime(argstr, HHMM_FORMAT)
|
||||
return argstr
|
||||
except ValueError:
|
||||
controlflow.system_error_exit(
|
||||
2, f'expected a <{HHMM_FORMAT_REQUIRED}>; got {argstr}')
|
||||
controlflow.system_error_exit(2, f'expected a <{HHMM_FORMAT_REQUIRED}>')
|
||||
|
||||
|
||||
def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False):
|
||||
argstr = argstr.strip()
|
||||
if argstr:
|
||||
@@ -228,12 +291,14 @@ def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False):
|
||||
def get_time_or_delta_from_now(time_string):
|
||||
"""Get an ISO 8601 time or a positive/negative delta applied to now.
|
||||
Args:
|
||||
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h')
|
||||
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h') or never
|
||||
Returns:
|
||||
string: iso8601 formatted datetime in UTC.
|
||||
"""
|
||||
time_string = time_string.strip().upper()
|
||||
if time_string:
|
||||
if time_string == 'NEVER':
|
||||
return NEVER_TIME
|
||||
if time_string[0] not in ['+', '-']:
|
||||
return time_string
|
||||
return (datetime.datetime.utcnow() +
|
||||
|
||||
149
src/gam/var.py
149
src/gam/var.py
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '5.24'
|
||||
GAM_VERSION = '6.09'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://git.io/gam'
|
||||
@@ -56,15 +56,50 @@ SKUS = {
|
||||
'aliases': ['identitypremium', 'cloudidentitypremium'],
|
||||
'displayName': 'Cloud Identity Premium'
|
||||
},
|
||||
'1010350001': {
|
||||
'product': '101035',
|
||||
'aliases': ['cloudsearch'],
|
||||
'displayName': 'Google Cloud Search',
|
||||
},
|
||||
'1010310002': {
|
||||
'product': '101031',
|
||||
'aliases': ['gsefe', 'e4e', 'gsuiteenterpriseeducation'],
|
||||
'displayName': 'G Suite Enterprise for Education'
|
||||
'displayName': 'Google Workspace for Education Plus - Legacy'
|
||||
},
|
||||
'1010310003': {
|
||||
'product': '101031',
|
||||
'aliases': ['gsefes', 'e4es', 'gsuiteenterpriseeducationstudent'],
|
||||
'displayName': 'G Suite Enterprise for Education (Student)'
|
||||
'displayName': 'Google Workspace for Education Plus - Legacy (Student)'
|
||||
},
|
||||
'1010310005': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwes', 'workspaceeducationstandard'],
|
||||
'displayName': 'Google Workspace for Education Standard'
|
||||
},
|
||||
'1010310006': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwesstaff', 'workspaceeducationstandardstaff'],
|
||||
'displayName': 'Google Workspace for Education Standard (Staff)'
|
||||
},
|
||||
'1010310007': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwesstudent', 'workspaceeducationstandardstudent'],
|
||||
'displayName': 'Google Workspace for Education Standard (Extra Student)'
|
||||
},
|
||||
'1010310008': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwep', 'workspaceeducationplus'],
|
||||
'displayName': 'Google Workspace for Education Plus'
|
||||
},
|
||||
'1010310009': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwepstaff', 'workspaceeducationplusstaff'],
|
||||
'displayName': 'Google Workspace for Education Plus (Staff)'
|
||||
},
|
||||
'1010310010': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwepstudent', 'workspaceeducationplusstudent'],
|
||||
'displayName': 'Google Workspace for Education Plus (Extra Student)'
|
||||
},
|
||||
'1010330003': {
|
||||
'product': '101033',
|
||||
@@ -81,10 +116,15 @@ SKUS = {
|
||||
'aliases': ['gvpremier', 'voicepremier', 'googlevoicepremier'],
|
||||
'displayName': 'Google Voice Premier'
|
||||
},
|
||||
'1010370001': {
|
||||
'product': '101037',
|
||||
'aliases': ['gwetlu', 'workspaceeducationupgrade'],
|
||||
'displayName': 'Google Workspace for Education: Teaching and Learning Upgrade'
|
||||
},
|
||||
'Google-Apps': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['standard', 'free'],
|
||||
'displayName': 'G Suite Free/Standard'
|
||||
'displayName': 'G Suite Legacy'
|
||||
},
|
||||
'Google-Apps-For-Business': {
|
||||
'product': 'Google-Apps',
|
||||
@@ -153,6 +193,11 @@ SKUS = {
|
||||
'wsentplus', 'workspaceenterpriseplus'],
|
||||
'displayName': 'Workspace Enterprise Plus'
|
||||
},
|
||||
'1010020030': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['workspacefrontline', 'workspacefrontlineworker'],
|
||||
'displayName': 'Workspace Frontline'
|
||||
},
|
||||
'1010340002': {
|
||||
'product': '101034',
|
||||
'aliases': ['gsbau', 'businessarchived', 'gsuitebusinessarchived'],
|
||||
@@ -228,25 +273,21 @@ SKUS = {
|
||||
PRODUCTID_NAME_MAPPINGS = {
|
||||
'101001': 'Cloud Identity Free',
|
||||
'101005': 'Cloud Identity Premium',
|
||||
'101031': 'G Suite Enterprise for Education',
|
||||
'101031': 'G Suite Workspace for Education',
|
||||
'101033': 'Google Voice',
|
||||
'101034': 'G Suite Archived',
|
||||
'101035': 'Cloud Search',
|
||||
'101037': 'G Suite Workspace for Education',
|
||||
'Google-Apps': 'Google Workspace',
|
||||
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
|
||||
'Google-Coordinate': 'Google Coordinate',
|
||||
'Google-Drive-storage': 'Google Drive Storage',
|
||||
'Google-Vault': 'Google Vault',
|
||||
}
|
||||
|
||||
# Legacy APIs that use v1 discovery. Newer APIs should all use v2.
|
||||
V1_DISCOVERY_APIS = {
|
||||
'admin',
|
||||
'appsactivity',
|
||||
'calendar',
|
||||
'drive',
|
||||
'oauth2',
|
||||
'reseller',
|
||||
'siteVerification',
|
||||
}
|
||||
|
||||
API_NAME_MAPPING = {
|
||||
@@ -254,19 +295,22 @@ API_NAME_MAPPING = {
|
||||
'reports': 'admin',
|
||||
'datatransfer': 'admin',
|
||||
'drive3': 'drive',
|
||||
'cloudresourcemanagerv1': 'cloudresourcemanager',
|
||||
'calendar': 'calendar-json',
|
||||
'cloudidentity_beta': 'cloudidentity',
|
||||
}
|
||||
|
||||
API_VER_MAPPING = {
|
||||
'alertcenter': 'v1beta1',
|
||||
'appsactivity': 'v1',
|
||||
'driveactivity': 'v2',
|
||||
'calendar': 'v3',
|
||||
'cbcm': 'v1.1beta1',
|
||||
'chromemanagement': 'v1',
|
||||
'chromepolicy': 'v1',
|
||||
'classroom': 'v1',
|
||||
'cloudidentity': 'v1',
|
||||
'cloudidentity_beta': 'v1beta1',
|
||||
'cloudresourcemanager': 'v2',
|
||||
'cloudresourcemanagerv1': 'v1',
|
||||
'cloudresourcemanager': 'v3',
|
||||
'contactdelegation': 'v1',
|
||||
'datatransfer': 'datatransfer_v1',
|
||||
'directory': 'directory_v1',
|
||||
'drive': 'v2',
|
||||
@@ -286,18 +330,20 @@ API_VER_MAPPING = {
|
||||
'siteVerification': 'v1',
|
||||
'storage': 'v1',
|
||||
'vault': 'v1',
|
||||
'versionhistory': 'v1',
|
||||
}
|
||||
|
||||
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
|
||||
|
||||
API_SCOPE_MAPPING = {
|
||||
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
|
||||
'appsactivity': [
|
||||
'https://www.googleapis.com/auth/activity',
|
||||
'driveactivity': [
|
||||
'https://www.googleapis.com/auth/drive.activity',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
],
|
||||
'calendar': ['https://www.googleapis.com/auth/calendar',],
|
||||
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity',],
|
||||
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity'],
|
||||
'cloudidentity_beta': ['https://www.googleapis.com/auth/cloud-identity'],
|
||||
'drive': ['https://www.googleapis.com/auth/drive',],
|
||||
'drive3': ['https://www.googleapis.com/auth/drive',],
|
||||
'gmail': [
|
||||
@@ -344,16 +390,21 @@ ADDRESS_FIELDS_ARGUMENT_MAP = {
|
||||
}
|
||||
|
||||
SERVICE_NAME_TO_ID_MAP = {
|
||||
'Calendar': '435070579839',
|
||||
'Currents': '553547912911',
|
||||
'Drive and Docs': '55656082996',
|
||||
'Calendar': '435070579839'
|
||||
'Google Data Studio': '810260081642',
|
||||
}
|
||||
|
||||
SERVICE_NAME_CHOICES_MAP = {
|
||||
'calendar': 'Calendar',
|
||||
'currents': 'Currents',
|
||||
'datastudio': 'Google Data Studio',
|
||||
'google data studio': 'Google Data Studio',
|
||||
'drive': 'Drive and Docs',
|
||||
'drive and docs': 'Drive and Docs',
|
||||
'googledrive': 'Drive and Docs',
|
||||
'gdrive': 'Drive and Docs',
|
||||
'calendar': 'Calendar',
|
||||
}
|
||||
|
||||
PRINTJOB_ASCENDINGORDER_MAP = {
|
||||
@@ -422,6 +473,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
|
||||
'lastviewedbymedate': 'lastViewedByMeDate',
|
||||
'lastviewedbymetime': 'lastViewedByMeDate',
|
||||
'lastviewedbyuser': 'lastViewedByMeDate',
|
||||
'linksharemetadata': 'linkShareMetadata',
|
||||
'md5': 'md5Checksum',
|
||||
'md5checksum': 'md5Checksum',
|
||||
'md5sum': 'md5Checksum',
|
||||
@@ -440,6 +492,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
|
||||
'owners': 'owners',
|
||||
'parents': 'parents',
|
||||
'permissions': 'permissions',
|
||||
'resourcekey': 'resourceKey',
|
||||
'quotabytesused': 'quotaBytesUsed',
|
||||
'quotaused': 'quotaBytesUsed',
|
||||
'shareable': 'shareable',
|
||||
@@ -447,6 +500,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
|
||||
'sharedwithmedate': 'sharedWithMeDate',
|
||||
'sharedwithmetime': 'sharedWithMeDate',
|
||||
'sharinguser': 'sharingUser',
|
||||
'shortcutdetails': 'shortcutDetails',
|
||||
'spaces': 'spaces',
|
||||
'thumbnaillink': 'thumbnailLink',
|
||||
'title': 'title',
|
||||
@@ -563,17 +617,21 @@ GOOGLEDOC_VALID_EXTENSIONS_MAP = {
|
||||
}
|
||||
|
||||
MACOS_CODENAMES = {
|
||||
6: 'Snow Leopard',
|
||||
7: 'Lion',
|
||||
8: 'Mountain Lion',
|
||||
9: 'Mavericks',
|
||||
10: 'Yosemite',
|
||||
11: 'El Capitan',
|
||||
12: 'Sierra',
|
||||
13: 'High Sierra',
|
||||
14: 'Mojave',
|
||||
15: 'Catalina'
|
||||
}
|
||||
10: {
|
||||
6: 'Snow Leopard',
|
||||
7: 'Lion',
|
||||
8: 'Mountain Lion',
|
||||
9: 'Mavericks',
|
||||
10: 'Yosemite',
|
||||
11: 'El Capitan',
|
||||
12: 'Sierra',
|
||||
13: 'High Sierra',
|
||||
14: 'Mojave',
|
||||
15: 'Catalina',
|
||||
16: 'Big Sur'
|
||||
},
|
||||
11: 'Big Sur',
|
||||
}
|
||||
|
||||
_MICROSOFT_FORMATS_LIST = [{
|
||||
'mime':
|
||||
@@ -838,8 +896,6 @@ RT_TAG_REPLACE_PATTERN = re.compile(r'{(.*?)}')
|
||||
LOWERNUMERIC_CHARS = string.ascii_lowercase + string.digits
|
||||
ALPHANUMERIC_CHARS = LOWERNUMERIC_CHARS + string.ascii_uppercase
|
||||
URL_SAFE_CHARS = ALPHANUMERIC_CHARS + '-._~'
|
||||
PASSWORD_SAFE_CHARS = ALPHANUMERIC_CHARS + string.punctuation + ' '
|
||||
FILENAME_SAFE_CHARS = ALPHANUMERIC_CHARS + '-_.() '
|
||||
|
||||
FILTER_ADD_LABEL_TO_ARGUMENT_MAP = {
|
||||
'IMPORTANT': 'important',
|
||||
@@ -906,6 +962,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'ethernetmacaddress0': ['ethernetMacAddress0',],
|
||||
'firmwareversion': ['firmwareVersion',],
|
||||
'lastenrollmenttime': ['lastEnrollmentTime',],
|
||||
'lastknownnetwork': ['lastKnownNetwork'],
|
||||
'lastsync': ['lastSync',],
|
||||
'location': ['annotatedLocation',],
|
||||
'macaddress': ['macAddress',],
|
||||
@@ -1053,7 +1110,8 @@ GROUP_SETTINGS_LIST_ATTRIBUTES = set([
|
||||
'whoCanUnmarkFavoriteReplyOnAnyTopic',
|
||||
'whoCanViewGroup',
|
||||
'whoCanViewMembership',
|
||||
# Miscellaneous hoices
|
||||
# Miscellaneous choices
|
||||
'default_sender',
|
||||
'messageModerationLevel',
|
||||
'replyTo',
|
||||
'spamModerationLevel',
|
||||
@@ -1142,7 +1200,7 @@ GM_Globals = {
|
||||
GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID: None,
|
||||
GM_ENABLEDASA_TXT: '',
|
||||
GM_LAST_UPDATE_CHECK_TXT: '',
|
||||
GM_MAP_ORGUNIT_ID_TO_NAME: None,
|
||||
GM_MAP_ORGUNIT_ID_TO_NAME: {},
|
||||
GM_MAP_ROLE_ID_TO_NAME: None,
|
||||
GM_MAP_ROLE_NAME_TO_ID: None,
|
||||
GM_MAP_USER_ID_TO_NAME: None,
|
||||
@@ -1188,10 +1246,12 @@ GC_DOMAIN = 'domain'
|
||||
GC_DRIVE_DIR = 'drive_dir'
|
||||
# Enable Delegated Admin Service Accounts
|
||||
GC_ENABLE_DASA = 'enabledasa'
|
||||
# If no_browser is False, writeCSVfile won't open a browser when todrive is set
|
||||
# If no_browser is True, writeCSVfile won't open a browser when todrive is set
|
||||
# and doRequestOAuth prints a link and waits for the verification code when
|
||||
# oauth2.txt is being created
|
||||
GC_NO_BROWSER = 'no_browser'
|
||||
# If no_tdemail is True, writeCSVfile won't send an email
|
||||
GC_NO_TDEMAIL = 'no_tdemail'
|
||||
# oauth_browser forces usage of web server OAuth flow that proved problematic.
|
||||
GC_OAUTH_BROWSER = 'oauth_browser'
|
||||
# Disable GAM API caching
|
||||
@@ -1220,6 +1280,8 @@ GC_CSV_HEADER_FILTER = 'csv_header_filter'
|
||||
GC_CSV_HEADER_DROP_FILTER = 'csv_header_drop_filter'
|
||||
# CSV Rows GAM should filter
|
||||
GC_CSV_ROW_FILTER = 'csv_row_filter'
|
||||
# CSV Rows GAM should filter/drop
|
||||
GC_CSV_ROW_DROP_FILTER = 'csv_row_drop_filter'
|
||||
# Minimum TLS Version required for HTTPS connections
|
||||
GC_TLS_MIN_VERSION = 'tls_min_ver'
|
||||
# Maximum TLS Version used for HTTPS connections
|
||||
@@ -1244,6 +1306,7 @@ GC_Defaults = {
|
||||
GC_DRIVE_DIR: '',
|
||||
GC_ENABLE_DASA: False,
|
||||
GC_NO_BROWSER: False,
|
||||
GC_NO_TDEMAIL: False,
|
||||
GC_NO_CACHE: False,
|
||||
GC_NO_SHORT_URLS: False,
|
||||
GC_NO_UPDATE_CHECK: False,
|
||||
@@ -1258,6 +1321,7 @@ GC_Defaults = {
|
||||
GC_CSV_HEADER_FILTER: '',
|
||||
GC_CSV_HEADER_DROP_FILTER: '',
|
||||
GC_CSV_ROW_FILTER: '',
|
||||
GC_CSV_ROW_DROP_FILTER: '',
|
||||
GC_TLS_MIN_VERSION: TLS_MIN,
|
||||
GC_TLS_MAX_VERSION: None,
|
||||
GC_CA_FILE: None,
|
||||
@@ -1328,6 +1392,9 @@ GC_VAR_INFO = {
|
||||
GC_NO_BROWSER: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
GC_NO_TDEMAIL: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
GC_NO_CACHE: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
@@ -1372,6 +1439,9 @@ GC_VAR_INFO = {
|
||||
GC_CSV_ROW_FILTER: {
|
||||
GC_VAR_TYPE: GC_TYPE_ROWFILTER
|
||||
},
|
||||
GC_CSV_ROW_DROP_FILTER: {
|
||||
GC_VAR_TYPE: GC_TYPE_ROWFILTER
|
||||
},
|
||||
GC_TLS_MIN_VERSION: {
|
||||
GC_VAR_TYPE: GC_TYPE_STRING
|
||||
},
|
||||
@@ -1457,7 +1527,7 @@ USER_EXTERNALID_TYPES = [
|
||||
]
|
||||
USER_GENDER_TYPES = ['female', 'male', 'unknown']
|
||||
USER_IM_TYPES = ['home', 'work', 'other']
|
||||
USER_KEYWORD_TYPES = ['occupation', 'outlook']
|
||||
USER_KEYWORD_TYPES = ['occupation', 'outlook', 'mission']
|
||||
USER_LOCATION_TYPES = ['default', 'desk']
|
||||
USER_ORGANIZATION_TYPES = ['domain_only', 'school', 'unknown', 'work']
|
||||
USER_PHONE_TYPES = [
|
||||
@@ -1472,7 +1542,7 @@ USER_RELATION_TYPES = [
|
||||
]
|
||||
USER_WEBSITE_TYPES = [
|
||||
'app_install_page', 'blog', 'ftp', 'home', 'home_page', 'other', 'profile',
|
||||
'reservations', 'work'
|
||||
'reservations', 'resume', 'work'
|
||||
]
|
||||
|
||||
WEBCOLOR_MAP = {
|
||||
@@ -1874,6 +1944,9 @@ DELTA_DATE_FORMAT_REQUIRED = '(+|-)<Number>(d|w|y)'
|
||||
DELTA_TIME_PATTERN = re.compile(r'^([+-])(\d+)([mhdwy])$')
|
||||
DELTA_TIME_FORMAT_REQUIRED = '(+|-)<Number>(m|h|d|w|y)'
|
||||
|
||||
HHMM_FORMAT = '%H:%M'
|
||||
HHMM_FORMAT_REQUIRED = 'hh:mm'
|
||||
|
||||
YYYYMMDD_FORMAT = '%Y-%m-%d'
|
||||
YYYYMMDD_FORMAT_REQUIRED = 'yyyy-mm-dd'
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
admin.googleapis.com
|
||||
alertcenter.googleapis.com
|
||||
appsactivity.googleapis.com
|
||||
calendar-json.googleapis.com
|
||||
chat.googleapis.com
|
||||
chromemanagement.googleapis.com
|
||||
chromepolicy.googleapis.com
|
||||
classroom.googleapis.com
|
||||
cloudidentity.googleapis.com
|
||||
contacts.googleapis.com
|
||||
drive.googleapis.com
|
||||
driveactivity.googleapis.com
|
||||
iap.googleapis.com
|
||||
gmail.googleapis.com
|
||||
groupssettings.googleapis.com
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
cryptography
|
||||
distro; sys_platform == 'linux'
|
||||
filelock
|
||||
google-api-python-client>=1.7.10
|
||||
google-api-python-client>=2.1
|
||||
google-auth-httplib2
|
||||
google-auth-oauthlib>=0.4.1
|
||||
google-auth>=1.11.2
|
||||
httplib2>=0.17.0
|
||||
passlib>=1.7.2; sys_platform == 'win32'
|
||||
importlib.metadata; python_version < '3.8'
|
||||
passlib>=1.7.2
|
||||
python-dateutil
|
||||
yubikey-manager>=4.0.0
|
||||
pathvalidate
|
||||
|
||||
49
src/setup.cfg
Normal file
49
src/setup.cfg
Normal file
@@ -0,0 +1,49 @@
|
||||
[metadata]
|
||||
name = GAM for Google Workspace
|
||||
version = 6.0.7
|
||||
description = Command line management for Google Workspaces
|
||||
long_description = file: readme.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/jay0lee/GAM
|
||||
author = Jay Lee
|
||||
author_email = jay0lee@gmail.com
|
||||
license = Apache
|
||||
license_files = LICENSE
|
||||
keywords = google, oauth2, gsuite, google-apps, google-admin-sdk, google-drive, google-cloud, google-calendar, gam, google-api, oauth2-client, google-workspace
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
License :: OSI Approved :: Apache License
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
cryptography
|
||||
distro; sys_platform == 'linux'
|
||||
filelock
|
||||
google-api-python-client >= 2.1
|
||||
google-auth-httplib2
|
||||
google-auth-oauthlib >= 0.4.1
|
||||
google-auth >= 1.11.2
|
||||
httplib2 >= 0.17.0
|
||||
importlib.metadata; python_version < '3.8'
|
||||
passlib >= 1.7.2
|
||||
python-dateutil
|
||||
yubikey-manager >= 4.0.0
|
||||
pathvalidate
|
||||
|
||||
# used during pip install .[test]
|
||||
[options.extras_require]
|
||||
test = pre-commit
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
gam = gam.__main__:main
|
||||
|
||||
[bdist_wheel]
|
||||
universal = True
|
||||
3
src/setup.py
Normal file
3
src/setup.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#from packaging import version
|
||||
from distutils.version import LooseVersion
|
||||
from packaging import version
|
||||
import sys
|
||||
|
||||
a = sys.argv[1]
|
||||
b = sys.argv[2]
|
||||
#result = version.parse(a) >= version.parse(b)
|
||||
result = LooseVersion(a) >= LooseVersion(b)
|
||||
result = version.parse(a) >= version.parse(b)
|
||||
if result:
|
||||
print('OK: %s is equal or newer than %s' % (a, b))
|
||||
else:
|
||||
|
||||
Binary file not shown.
@@ -1,38 +0,0 @@
|
||||
cd src
|
||||
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
export gam="$python -m gam"
|
||||
export gampath=$(readlink -e .)
|
||||
else
|
||||
export gampath="dist/gam"
|
||||
rm -rf $gampath
|
||||
mkdir -p $gampath
|
||||
export gampath=$(readlink -e $gampath)
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-glibc$this_glibc_ver.tar.xz
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
# tar will cd to dist and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
echo "PyInstaller GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
if [ "${TRAVIS_DIST}" == "xenial" ] && [ "${PLATFORM}" == "x86_64" ]; then
|
||||
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
||||
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
|
||||
strip $gam-staticx
|
||||
rm $gampath/gam
|
||||
mv $gam-staticx $gam
|
||||
chmod 755 $gam
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
|
||||
echo "Legacy StaticX GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
fi
|
||||
echo "GAM packages:"
|
||||
ls -l gam-*.tar.xz
|
||||
fi
|
||||
@@ -1,111 +0,0 @@
|
||||
mypath=$HOME
|
||||
whereibelong=$(pwd)
|
||||
cpucount=$(sysctl -n hw.ncpu)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
|
||||
#echo "Brew installing xz..."
|
||||
#brew install xz > /dev/null
|
||||
|
||||
#brew upgrade
|
||||
|
||||
# prefer standard GNU tools like date over MacOS defaults
|
||||
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
|
||||
|
||||
cd ~
|
||||
|
||||
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
|
||||
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
|
||||
#fi
|
||||
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
|
||||
|
||||
#brew install openssl@1.1
|
||||
#brew upgrade python
|
||||
|
||||
#export python=python3
|
||||
#export pip=pip3
|
||||
|
||||
#echo "Python location:"
|
||||
#which $python
|
||||
|
||||
cd ~
|
||||
|
||||
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
export openssl=~/ssl/bin/openssl
|
||||
export python=~/python/bin/python3
|
||||
export pip=~/python/bin/pip3
|
||||
SSLVER=$($openssl version)
|
||||
SSLRESULT=$?
|
||||
PYVER=$($python -V)
|
||||
PYRESULT=$?
|
||||
if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
|
||||
echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
|
||||
if [ $SSLRESULT -ne 0 ]; then
|
||||
echo "sslresult -ne 0"
|
||||
fi
|
||||
if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
|
||||
echo "sslver not equal to..."
|
||||
fi
|
||||
if [ $PYRESULT -ne 0 ]; then
|
||||
echo "pyresult -ne 0"
|
||||
fi
|
||||
if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
|
||||
echo "pyver not equal to..."
|
||||
fi
|
||||
|
||||
# Start clean
|
||||
rm -rf python
|
||||
rm -rf ssl
|
||||
mkdir python
|
||||
mkdir ssl
|
||||
|
||||
# Compile latest OpenSSL
|
||||
wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
echo "Extracting OpenSSL..."
|
||||
tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
cd openssl-$BUILD_OPENSSL_VERSION
|
||||
echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
./config shared --prefix=$HOME/ssl
|
||||
echo "Running make for OpenSSL..."
|
||||
make -j$cpucount -s
|
||||
echo "Running make install for OpenSSL..."
|
||||
make install > /dev/null
|
||||
cd ~
|
||||
|
||||
# Compile latest Python
|
||||
echo "Downloading Python $BUILD_PYTHON_VERSION..."
|
||||
curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
echo "Extracting Python..."
|
||||
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
cd Python-$BUILD_PYTHON_VERSION
|
||||
echo "Compiling Python $BUILD_PYTHON_VERSION..."
|
||||
safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
|
||||
unsafe_flags="--enable-optimizations --with-lto"
|
||||
if [ ! -e Makefile ]; then
|
||||
echo "running configure with safe and unsafe"
|
||||
./configure $safe_flags $unsafe_flags > /dev/null
|
||||
fi
|
||||
make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
|
||||
RESULT=$?
|
||||
echo "First make exited with $RESULT"
|
||||
if [ $RESULT != 0 ]; then
|
||||
echo "Trying Python compile again without unsafe flags..."
|
||||
make clean
|
||||
./configure $safe_flags > /dev/null
|
||||
make -j$cpucount -s
|
||||
echo "Sticking with safe Python for now..."
|
||||
fi
|
||||
echo "Installing Python..."
|
||||
make install > /dev/null
|
||||
cd ~
|
||||
fi
|
||||
|
||||
$python -V
|
||||
|
||||
cd $whereibelong
|
||||
|
||||
#export PATH=/usr/local/opt/python/libexec/bin:$PATH
|
||||
$pip install --upgrade pip
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
cd src
|
||||
echo "MacOS Version Info According to Python:"
|
||||
python -c "import platform; print(platform.mac_ver())"
|
||||
echo "Xcode versionn:"
|
||||
xcodebuild -version
|
||||
export gampath=dist/gam
|
||||
rm -rf $gampath
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
|
||||
export gam="$gampath/gam"
|
||||
$gam version extended
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
MACOSVERSION=$(defaults read loginwindow SystemVersionStampAsString)
|
||||
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-MacOS$MACOSVERSION.tar.xz
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
# tar will cd to dist/ and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
@@ -1,82 +0,0 @@
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export BITS="64"
|
||||
export PYTHONFILE_BITS="-amd64"
|
||||
export OPENSSL_BITS="-x64"
|
||||
export WIX_BITS="x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export BITS="32"
|
||||
export PYTHONFILE_BITS=""
|
||||
export OPENSSL_BITS=""
|
||||
export WIX_BITS="x86"
|
||||
fi
|
||||
echo "This is a ${BITS}-bit build for ${PLATFORM}"
|
||||
|
||||
export mypath=$(pwd)
|
||||
cd ~
|
||||
|
||||
# .NET Core
|
||||
echo "Installing Net-Framework-Core..."
|
||||
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying .net again..."; done
|
||||
|
||||
# VS 2015
|
||||
echo "Installing Visual Studio 2015.."
|
||||
until choco install vcbuildtools; do echo "Trying Visual Studio again..."; done
|
||||
|
||||
# Python
|
||||
echo "Installing Python..."
|
||||
export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
|
||||
if [ ! -e $python_file ]; then
|
||||
echo "Downloading $python_file..."
|
||||
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
|
||||
fi
|
||||
until powershell ".\\${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python"; do echo "trying python again..."; done
|
||||
export python=/c/python/python.exe
|
||||
export pip=/c/python/scripts/pip.exe
|
||||
until [ -f $python ]; do sleep 1; done
|
||||
export PATH=$PATH:/c/python/scripts
|
||||
|
||||
# OpenSSL
|
||||
echo "Installing OpenSSL..."
|
||||
export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
|
||||
if [ ! -e $exefile ]; then
|
||||
echo "Downloading $exefile..."
|
||||
wget --quiet https://slproweb.com/download/$exefile
|
||||
fi
|
||||
until powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"; do echo "trying openssl again..."; done
|
||||
until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
|
||||
until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
|
||||
cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
|
||||
fi
|
||||
|
||||
# WIX Toolset
|
||||
until cinst -y wixtoolset; do echo "trying wix install again..."; done
|
||||
|
||||
cd $mypath
|
||||
|
||||
$pip install --upgrade pip
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
#$pip install --upgrade pyinstaller
|
||||
# Install PyInstaller from source and build bootloader
|
||||
# to try and avoid getting flagged as malware since
|
||||
# lots of malware uses PyInstaller default bootloader
|
||||
# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller
|
||||
echo "Downloading PyInstaller..."
|
||||
wget --quiet https://github.com/pyinstaller/pyinstaller/archive/$PYINSTALLER_COMMIT.tar.gz
|
||||
tar xf $PYINSTALLER_COMMIT.tar.gz
|
||||
mv pyinstaller-$PYINSTALLER_COMMIT pyinstaller
|
||||
cd pyinstaller/bootloader
|
||||
echo "bootloader before:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
|
||||
|
||||
$python ./waf all --target-arch=${BITS}bit --msvc_version "msvc 14.0"
|
||||
|
||||
echo "bootloader after:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
$python setup.py install
|
||||
echo "cd to $mypath"
|
||||
cd $mypath
|
||||
486
src/versionhistory-v1.json
Normal file
486
src/versionhistory-v1.json
Normal file
@@ -0,0 +1,486 @@
|
||||
{
|
||||
"revision": "20210322",
|
||||
"name": "versionhistory",
|
||||
"mtlsRootUrl": "https://versionhistory.mtls.googleapis.com/",
|
||||
"version_module": true,
|
||||
"basePath": "",
|
||||
"title": "Version History API",
|
||||
"kind": "discovery#restDescription",
|
||||
"servicePath": "",
|
||||
"ownerDomain": "google.com",
|
||||
"parameters": {
|
||||
"access_token": {
|
||||
"location": "query",
|
||||
"description": "OAuth access token.",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"location": "query",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"type": "string",
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"description": "Data format for response."
|
||||
},
|
||||
"quotaUser": {
|
||||
"type": "string",
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query"
|
||||
},
|
||||
"$.xgafv": {
|
||||
"location": "query",
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"description": "V1 error format.",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "string",
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"location": "query",
|
||||
"description": "JSONP",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"location": "query",
|
||||
"type": "string",
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\")."
|
||||
},
|
||||
"prettyPrint": {
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"default": "true",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"type": "string",
|
||||
"location": "query"
|
||||
}
|
||||
},
|
||||
"ownerName": "Google",
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"platforms": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"httpMethod": "GET",
|
||||
"parameterOrder": [
|
||||
"parent"
|
||||
],
|
||||
"response": {
|
||||
"$ref": "ListPlatformsResponse"
|
||||
},
|
||||
"path": "v1/{+parent}/platforms",
|
||||
"description": "Returns list of platforms that are avaialble for a given product. The resource \"product\" has no resource name in its name.",
|
||||
"flatPath": "v1/{v1Id}/platforms",
|
||||
"id": "versionhistory.platforms.list",
|
||||
"parameters": {
|
||||
"parent": {
|
||||
"location": "path",
|
||||
"pattern": "^[^/]+$",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Required. The product, which owns this collection of platforms. Format: {product}"
|
||||
},
|
||||
"pageSize": {
|
||||
"format": "int32",
|
||||
"description": "Optional. Optional limit on the number of channels to include in the response. If unspecified, the server will pick an appropriate default.",
|
||||
"type": "integer",
|
||||
"location": "query"
|
||||
},
|
||||
"pageToken": {
|
||||
"location": "query",
|
||||
"description": "Optional. A page token, received from a previous `ListChannels` call. Provide this to retrieve the subsequent page.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"channels": {
|
||||
"resources": {
|
||||
"versions": {
|
||||
"resources": {
|
||||
"releases": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"id": "versionhistory.platforms.channels.versions.releases.list",
|
||||
"path": "v1/{+parent}/releases",
|
||||
"httpMethod": "GET",
|
||||
"parameterOrder": [
|
||||
"parent"
|
||||
],
|
||||
"response": {
|
||||
"$ref": "ListReleasesResponse"
|
||||
},
|
||||
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels/{channelsId}/versions/{versionsId}/releases",
|
||||
"parameters": {
|
||||
"parent": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Required. The version, which owns this collection of releases. Format: {product}/platforms/{platform}/channels/{channel}/versions/{version}",
|
||||
"pattern": "^[^/]+/platforms/[^/]+/channels/[^/]+/versions/[^/]+$",
|
||||
"location": "path"
|
||||
},
|
||||
"filter": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "Optional. Filter string. Format is a comma separated list of All comma separated filter clauses are conjoined with a logical \"and\". Valid field_names are \"version\", \"name\", \"platform\", \"channel\", \"fraction\" \"starttime\", and \"endtime\". Valid operators are \"\u003c\", \"\u003c=\", \"=\", \"\u003e=\", and \"\u003e\". Channel comparison is done by distance from stable. must be a valid channel when filtering by channel. Ex) stable \u003c beta, beta \u003c dev, canary \u003c canary_asan. Version comparison is done numerically. Ex) 1.0.0.8 \u003c 1.0.0.10. If version is not entirely written, the version will be appended with 0 for the missing fields. Ex) version \u003e 80 becoms version \u003e 80.0.0.0 When filtering by starttime or endtime, string must be in RFC 3339 date string format. Name and platform are filtered by string comparison. Ex) \"...?filter=channel\u003c=beta, version \u003e= 80 Ex) \"...?filter=version \u003e 80, version \u003c 81 Ex) \"...?filter=starttime\u003e2020-01-01T00:00:00Z"
|
||||
},
|
||||
"orderBy": {
|
||||
"location": "query",
|
||||
"description": "Optional. Ordering string. Valid order_by strings are \"version\", \"name\", \"starttime\", \"endtime\", \"platform\", \"channel\", and \"fraction\". Optionally, you can append \"desc\" or \"asc\" to specify the sorting order. Multiple order_by strings can be used in a comma separated list. Ordering by channel will sort by distance from the stable channel (not alphabetically). A list of channels sorted in this order is: stable, beta, dev, canary, and canary_asan. Sorting by name may cause unexpected behaviour as it is a naive string sort. For example, 1.0.0.8 will be before 1.0.0.10 in descending order. If order_by is not specified the response will be sorted by starttime in descending order. Ex) \"...?order_by=starttime asc\" Ex) \"...?order_by=platform desc, channel, startime desc\"",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"location": "query",
|
||||
"format": "int32",
|
||||
"description": "Optional. Optional limit on the number of releases to include in the response. If unspecified, the server will pick an appropriate default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Optional. A page token, received from a previous `ListReleases` call. Provide this to retrieve the subsequent page.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Returns list of releases of the given version."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"list": {
|
||||
"response": {
|
||||
"$ref": "ListVersionsResponse"
|
||||
},
|
||||
"path": "v1/{+parent}/versions",
|
||||
"parameters": {
|
||||
"pageSize": {
|
||||
"location": "query",
|
||||
"format": "int32",
|
||||
"description": "Optional. Optional limit on the number of versions to include in the response. If unspecified, the server will pick an appropriate default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Optional. A page token, received from a previous `ListVersions` call. Provide this to retrieve the subsequent page.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"parent": {
|
||||
"required": true,
|
||||
"location": "path",
|
||||
"description": "Required. The channel, which owns this collection of versions. Format: {product}/platforms/{platform}/channels/{channel}",
|
||||
"pattern": "^[^/]+/platforms/[^/]+/channels/[^/]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"orderBy": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "Optional. Ordering string. Valid order_by strings are \"version\", \"name\", \"platform\", and \"channel\". Optionally, you can append \" desc\" or \" asc\" to specify the sorting order. Multiple order_by strings can be used in a comma separated list. Ordering by channel will sort by distance from the stable channel (not alphabetically). A list of channels sorted in this order is: stable, beta, dev, canary, and canary_asan. Sorting by name may cause unexpected behaviour as it is a naive string sort. For example, 1.0.0.8 will be before 1.0.0.10 in descending order. If order_by is not specified the response will be sorted by version in descending order. Ex) \"...?order_by=version asc\" Ex) \"...?order_by=platform desc, channel, version\""
|
||||
},
|
||||
"filter": {
|
||||
"description": "Optional. Filter string. Format is a comma separated list of All comma separated filter clauses are conjoined with a logical \"and\". Valid field_names are \"version\", \"name\", \"platform\", and \"channel\". Valid operators are \"\u003c\", \"\u003c=\", \"=\", \"\u003e=\", and \"\u003e\". Channel comparison is done by distance from stable. Ex) stable \u003c beta, beta \u003c dev, canary \u003c canary_asan. Version comparison is done numerically. If version is not entirely written, the version will be appended with 0 in missing fields. Ex) version \u003e 80 becoms version \u003e 80.0.0.0 Name and platform are filtered by string comparison. Ex) \"...?filter=channel\u003c=beta, version \u003e= 80 Ex) \"...?filter=version \u003e 80, version \u003c 81",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"id": "versionhistory.platforms.channels.versions.list",
|
||||
"parameterOrder": [
|
||||
"parent"
|
||||
],
|
||||
"description": "Returns list of version for the given platform/channel.",
|
||||
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels/{channelsId}/versions",
|
||||
"httpMethod": "GET"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"list": {
|
||||
"response": {
|
||||
"$ref": "ListChannelsResponse"
|
||||
},
|
||||
"parameterOrder": [
|
||||
"parent"
|
||||
],
|
||||
"parameters": {
|
||||
"pageToken": {
|
||||
"type": "string",
|
||||
"location": "query",
|
||||
"description": "Optional. A page token, received from a previous `ListChannels` call. Provide this to retrieve the subsequent page."
|
||||
},
|
||||
"parent": {
|
||||
"location": "path",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"pattern": "^[^/]+/platforms/[^/]+$",
|
||||
"description": "Required. The platform, which owns this collection of channels. Format: {product}/platforms/{platform}"
|
||||
},
|
||||
"pageSize": {
|
||||
"format": "int32",
|
||||
"type": "integer",
|
||||
"description": "Optional. Optional limit on the number of channels to include in the response. If unspecified, the server will pick an appropriate default.",
|
||||
"location": "query"
|
||||
}
|
||||
},
|
||||
"path": "v1/{+parent}/channels",
|
||||
"httpMethod": "GET",
|
||||
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels",
|
||||
"id": "versionhistory.platforms.channels.list",
|
||||
"description": "Returns list of channels that are available for a given platform."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Version History API - Prod",
|
||||
"discoveryVersion": "v1",
|
||||
"schemas": {
|
||||
"Channel": {
|
||||
"id": "Channel",
|
||||
"type": "object",
|
||||
"description": "Each Channel is owned by a Platform and owns a collection of versions. Possible Channels are listed in the Channel enum below. Not all Channels are available for every Platform (e.g. CANARY does not exist for LINUX).",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Channel name. Format is \"{product}/platforms/{platform}/channels/{channel}\"",
|
||||
"type": "string"
|
||||
},
|
||||
"channelType": {
|
||||
"description": "Type of channel.",
|
||||
"enumDescriptions": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CHANNEL_TYPE_UNSPECIFIED",
|
||||
"STABLE",
|
||||
"BETA",
|
||||
"DEV",
|
||||
"CANARY",
|
||||
"CANARY_ASAN",
|
||||
"ALL",
|
||||
"EXTENDED"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Interval": {
|
||||
"description": "Represents a time interval, encoded as a Timestamp start (inclusive) and a Timestamp end (exclusive). The start must be less than or equal to the end. When the start equals the end, the interval is empty (matches no time). When both start and end are unspecified, the interval matches any time.",
|
||||
"type": "object",
|
||||
"id": "Interval",
|
||||
"properties": {
|
||||
"endTime": {
|
||||
"type": "string",
|
||||
"format": "google-datetime",
|
||||
"description": "Optional. Exclusive end of the interval. If specified, a Timestamp matching this interval will have to be before the end."
|
||||
},
|
||||
"startTime": {
|
||||
"format": "google-datetime",
|
||||
"type": "string",
|
||||
"description": "Optional. Inclusive start of the interval. If specified, a Timestamp matching this interval will have to be the same or after the start."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ListVersionsResponse": {
|
||||
"description": "Response message for ListVersions.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"versions": {
|
||||
"type": "array",
|
||||
"description": "The list of versions.",
|
||||
"items": {
|
||||
"$ref": "Version"
|
||||
}
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"id": "ListVersionsResponse"
|
||||
},
|
||||
"ListChannelsResponse": {
|
||||
"description": "Response message for ListChannels.",
|
||||
"id": "ListChannelsResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nextPageToken": {
|
||||
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
|
||||
"type": "string"
|
||||
},
|
||||
"channels": {
|
||||
"description": "The list of channels.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "Channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Platform": {
|
||||
"properties": {
|
||||
"platformType": {
|
||||
"enum": [
|
||||
"PLATFORM_TYPE_UNSPECIFIED",
|
||||
"WIN",
|
||||
"WIN64",
|
||||
"MAC",
|
||||
"LINUX",
|
||||
"ANDROID",
|
||||
"WEBVIEW",
|
||||
"IOS",
|
||||
"ALL",
|
||||
"MAC_ARM64",
|
||||
"LACROS"
|
||||
],
|
||||
"description": "Type of platform.",
|
||||
"enumDescriptions": [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Platform name. Format is \"{product}/platforms/{platform}\"",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"id": "Platform",
|
||||
"type": "object",
|
||||
"description": "Each Platform is owned by a Product and owns a collection of channels. Available platforms are listed in Platform enum below. Not all Channels are available for every Platform (e.g. CANARY does not exist for LINUX)."
|
||||
},
|
||||
"Version": {
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "String containing just the version number. e.g. \"84.0.4147.38\"",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Version name. Format is \"{product}/platforms/{platform}/channels/{channel}/versions/{version}\" e.g. \"chrome/platforms/win/channels/beta/versions/84.0.4147.38\"",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"id": "Version",
|
||||
"type": "object",
|
||||
"description": "Each Version is owned by a Channel. A Version only displays the Version number (e.g. 84.0.4147.38). A Version owns a collection of releases."
|
||||
},
|
||||
"ListPlatformsResponse": {
|
||||
"description": "Response message for ListPlatforms.",
|
||||
"id": "ListPlatformsResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nextPageToken": {
|
||||
"type": "string",
|
||||
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages."
|
||||
},
|
||||
"platforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "Platform"
|
||||
},
|
||||
"description": "The list of platforms."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ListReleasesResponse": {
|
||||
"type": "object",
|
||||
"id": "ListReleasesResponse",
|
||||
"description": "Response message for ListReleases.",
|
||||
"properties": {
|
||||
"nextPageToken": {
|
||||
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
|
||||
"type": "string"
|
||||
},
|
||||
"releases": {
|
||||
"description": "The list of releases.",
|
||||
"items": {
|
||||
"$ref": "Release"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Release": {
|
||||
"properties": {
|
||||
"fraction": {
|
||||
"format": "double",
|
||||
"type": "number",
|
||||
"description": "Rollout fraction. This fraction indicates the fraction of people that should receive this version in this release. If the fraction is not specified in ReleaseManager, the API will assume fraction is 1."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "String containing just the version number. e.g. \"84.0.4147.38\""
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Release name. Format is \"{product}/platforms/{platform}/channels/{channel}/versions/{version}/releases/{release}\""
|
||||
},
|
||||
"serving": {
|
||||
"description": "Timestamp interval of when the release was live. If end_time is unspecified, the release is currently live.",
|
||||
"$ref": "Interval"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"description": "A Release is owned by a Version. A Release contains information about the release(s) of its parent version. This includes when the release began and ended, as well as what percentage it was released at. If the version is released again, or if the serving percentage changes, it will create another release under the version.",
|
||||
"id": "Release"
|
||||
}
|
||||
},
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"documentationLink": "https://developers.chrome.com/",
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"baseUrl": "https://versionhistory.googleapis.com/",
|
||||
"batchPath": "batch",
|
||||
"version": "v1",
|
||||
"canonicalName": "Version History",
|
||||
"id": "versionhistory:v1",
|
||||
"rootUrl": "https://versionhistory.googleapis.com/"
|
||||
}
|
||||
Reference in New Issue
Block a user