Compare commits

..

29 Commits

Author SHA1 Message Date
Jay Lee
e4af5e6126 [no ci] Enhance build workflow with custom cryptography wheels
Added conditional installation for custom cryptography wheels on macOS and Windows ARM64.
2026-06-27 15:23:10 -04:00
Jay Lee
6e296e0f2d Clean up build.yml by removing ykman hack
Removed workaround for ykman cryptography dependency.
2026-06-27 15:16:07 -04:00
github-actions[bot]
4b2e14c2d5 chore: upgrade PyPi deps (#1933)
Co-authored-by: jay0lee <4623536+jay0lee@users.noreply.github.com>
2026-06-27 15:13:30 -04:00
Jay Lee
eb59663f6a [no ci] Enhance upgrade_deps.yml with comments and cleanup
Updated comments for clarity and added cleanup for resolved.txt.
2026-06-27 15:10:29 -04:00
Jay Lee
d12289c4f4 [no ci] Add uv installation and enhance dependency resolution
This update adds a step to install the 'uv' package and modifies the dependency resolution process to handle mutually compatible package versions. It also updates the commit message body for clarity.
2026-06-27 15:06:38 -04:00
Jay Lee
738ff3e7fb Modify ykman installation to remove cryptography constraint
Updated the build workflow to patch the ykman METADATA file for cryptography dependency.
2026-06-27 14:51:37 -04:00
Jay Lee
789e543b3f Fix yubikey-manager installation and cryptography dependency
Updated yubikey-manager installation to use version 5.9.1 and modified cryptography dependency handling.
2026-06-27 14:34:01 -04:00
Jay Lee
4e933f4485 Modify yubikey installation commands in build.yml
Updated installation commands for yubikey-manager and dependencies.
2026-06-27 14:11:37 -04:00
Jay Lee
a99f23ce40 Refactor pip installation steps in build workflow
Commented out pip upgrade commands and adjusted yubikey installation.
2026-06-27 14:09:10 -04:00
github-actions[bot]
5462c0359e chore: upgrade PyPi deps (#1931)
Co-authored-by: jay0lee <4623536+jay0lee@users.noreply.github.com>
2026-06-27 14:06:15 -04:00
Jay Lee
0c143414a8 Delete dep-overrides.txt 2026-06-27 14:01:08 -04:00
Jay Lee
bebcfb7c44 Update dependency overrides in dep-overrides.txt 2026-06-27 13:58:00 -04:00
Jay Lee
18ce64886d Modify yubikey-manager installation command
Update yubikey-manager installation to avoid dependency installation.
2026-06-27 13:32:54 -04:00
Jay Lee
db87aa54d8 Update yubikey-manager installation to version 5.9.1
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Specify version for yubikey-manager installation
2026-06-27 13:28:14 -04:00
Jay Lee
1867af9366 [actions] Comment out yubikey upgrade command in build.yml
Comment out the upgrade command for yubikey dependency and add note about future upgrade.
2026-06-27 13:18:54 -04:00
github-actions[bot]
e1f2bedd7c chore: upgrade PyPi deps (#1930)
Co-authored-by: jay0lee <4623536+jay0lee@users.noreply.github.com>
2026-06-27 13:01:09 -04:00
Ross Scroggs
8735021c9b Update Vault API error handling 2026-06-27 09:16:54 -07:00
Ross Scroggs
2860ae02a2 Update Users-People-Contacts-Profiles.md
Some checks failed
Push wiki / pushwiki (push) Has been cancelled
2026-06-26 19:26:16 -07:00
github-actions[bot]
31b4b70f0f chore: upgrade PyPi deps (#1929)
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
Check for Google Root CA Updates / check-certs (push) Has been cancelled
Daily Dependency Pinning (2-Week Buffer) / pin-deps (push) Has been cancelled
Co-authored-by: jay0lee <4623536+jay0lee@users.noreply.github.com>
2026-06-25 05:09:11 -04:00
Jay Lee
1c8bf44867 Update checkout action to version 7.0.0
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
Check for Google Root CA Updates / check-certs (push) Has been cancelled
Daily Dependency Pinning (2-Week Buffer) / pin-deps (push) Has been cancelled
2026-06-23 11:33:08 -04:00
Jay Lee
472905d6c0 [no ci] Update checkout action to version 7.0.0 2026-06-23 11:32:46 -04:00
Jay Lee
bed9b5e1b6 [no ci] Update checkout action version in pushwiki.yml 2026-06-23 11:32:19 -04:00
Jay Lee
c8521c6307 [no ci] Update checkout action to version 7.0.0 2026-06-23 11:31:48 -04:00
Jay Lee
870a3149d8 [no ci] Upgrade actions/checkout to version 7.0.0
Updated checkout action version from v5.0.0 to v7.0.0.
2026-06-23 11:31:07 -04:00
Jay Lee
8622ae6c0f Upgrade actions/checkout to version 7.0.0
Updated checkout action version from v6.0.2 to v7.0.0 in build workflows.
2026-06-23 11:30:33 -04:00
Jay Lee
c92468276a [actions] reduce pip verbosity
Remove verbose flag from pip install command for yubikey.
2026-06-23 05:25:54 -04:00
dependabot[bot]
d80b93b86e Bump cryptography in the pip group across 1 directory (#1926)
Bumps the pip group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography).


Updates `cryptography` from 48.0.0 to 48.0.1
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.0...48.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 48.0.1
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 04:53:05 -04:00
Ross Scroggs
2d26fa6004 Update GamUpdates.md
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
Check for Google Root CA Updates / check-certs (push) Has been cancelled
Daily Dependency Pinning (2-Week Buffer) / pin-deps (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Push wiki / pushwiki (push) Has been cancelled
2026-06-19 08:10:22 -07:00
Ross Scroggs
a09a98e3ae Updated gam <UserTypeEntity> show calsettings 2026-06-19 08:00:34 -07:00
15 changed files with 225 additions and 197 deletions

View File

@@ -145,7 +145,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
fetch-depth: 0
@@ -529,18 +529,6 @@ jobs:
echo "gam=${gam}" >> $GITHUB_ENV
fi
#- name: Upgrade pip, wheel, etc
# run: |
# curl $curl_retry -O https://bootstrap.pypa.io/get-pip.py
# "$PYTHON" get-pip.py
# "$PYTHON" -m pip install --upgrade pip
# "$PYTHON" -m pip install --upgrade wheel
# "$PYTHON" -m pip install --upgrade setuptools
# "$PYTHON" -m pip install --upgrade importlib-metadata
# "$PYTHON" -m pip install --upgrade setuptools-scm
# "$PYTHON" -m pip install --upgrade packaging
# "$PYTHON" -m pip list
- name: Install pip requirements
env:
GH_TOKEN: ${{ github.token }}
@@ -550,8 +538,12 @@ jobs:
# https://github.com/pyca/cryptography/issues/14293
gh release download --repo "jay0lee/cryptography-wheels" --pattern "*win_arm64.whl" --clobber
"$PYTHON" -m pip install cryptography-*.whl
elif [[ "$RUNNER_OS" == "macOS" && "$RUNNER_ARCH" == "x86_64" ]]; then
# custom cryptography wheel for macos x86_64 since it's no longer standard
gh release download --repo "jay0lee/cryptography-wheels" --pattern "*macosx_15_0_x86_64.whl" --clobber
"$PYTHON" -m pip install cryptography-*.whl
fi
"$PYTHON" -m pip install -vvv --upgrade ..[yubikey]
"$PYTHON" -m pip install ..[yubikey]
- name: Install PyInstaller
if: matrix.goal == 'build'
@@ -1121,7 +1113,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
fetch-depth: 0

View File

@@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:

View File

@@ -14,7 +14,7 @@ jobs:
check-certs:
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo

View File

@@ -18,7 +18,7 @@ jobs:
git clone https://github.com/GAM-team/GAM
- name: Checkout Wiki source
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: GAM.wiki
repository: GAM-team/GAM.wiki

View File

@@ -16,7 +16,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
fetch-depth: 0

View File

@@ -15,20 +15,25 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.14'
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Calculate and pin two-week old stable versions
shell: python
run: |
import json
import urllib.request
import subprocess
import re
import tomllib
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
@@ -42,14 +47,10 @@ jobs:
target_deps = set()
# Standard [project.dependencies]
# Gather all dependencies
target_deps.update(parsed_toml.get("project", {}).get("dependencies", []))
# Optional [project.optional-dependencies]
for deps in parsed_toml.get("project", {}).get("optional-dependencies", {}).values():
target_deps.update(deps)
# Dev [dependency-groups] (uv / PEP 735 standard)
for deps in parsed_toml.get("dependency-groups", {}).values():
if isinstance(deps, list):
for d in deps:
@@ -60,73 +61,80 @@ jobs:
print("No dependencies found to process.")
exit(0)
updates = {}
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
print(f"Evaluating dependencies against cutoff date: {two_weeks_ago.isoformat()}")
# 1. Create a "clean" requirements list (base names + markers only, no versions)
clean_reqs = []
for dep in target_deps:
# Isolate base package name (e.g., "yubikey-manager>=5.6.1" -> "yubikey-manager")
pkg_name = re.split(r'[<>=!~;\s]', dep)[0].strip()
pkg_base = re.split(r'[<>=!~;\s]', dep)[0].strip()
if ";" in dep:
marker = dep.split(";", 1)[1].strip()
clean_reqs.append(f"{pkg_base} ; {marker}")
else:
clean_reqs.append(pkg_base)
temp_in = Path("temp_reqs.in")
temp_in.write_text("\n".join(clean_reqs), encoding="utf-8")
# 2. Calculate Cutoff Date
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
cutoff_str = two_weeks_ago.strftime("%Y-%m-%dT%H:%M:%SZ")
print(f"Resolving dependencies against cutoff date: {cutoff_str}")
# 3. Use uv to resolve the CLEAN list, allowing free upgrades/downgrades
try:
subprocess.run([
"uv", "pip", "compile",
str(temp_in),
"--exclude-newer", cutoff_str,
"--quiet",
"-o", "resolved.txt"
], check=True)
except subprocess.CalledProcessError:
print("\nDependency resolution failed! Upstream constraints are impossible to satisfy.")
if temp_in.exists(): temp_in.unlink()
exit(1)
# 4. Parse the resolved lockfile
resolved_versions = {}
with open("resolved.txt", "r", encoding="utf-8") as f:
for line in f:
line = line.split("#")[0].strip()
if "==" in line:
pkg, ver = line.split("==", 1)
resolved_versions[pkg.strip().lower()] = ver.strip()
# Cleanup temp files so they don't get committed to the PR
if temp_in.exists():
temp_in.unlink()
if Path("resolved.txt").exists():
Path("resolved.txt").unlink()
# 5. Map the newly resolved versions back to your pyproject.toml updates
updates = {}
for dep in target_deps:
pkg_name_raw = re.split(r'[<>=!~;\s]', dep)[0].strip()
pkg_name_lower = pkg_name_raw.lower()
# Preserve environment markers if they exist
marker = ""
if ";" in dep:
marker = " ; " + dep.split(";", 1)[1].strip()
print(f"Fetching PyPI data for: {pkg_name}")
try:
url = f"https://pypi.org/pypi/{pkg_name}/json"
req = urllib.request.Request(url, headers={'User-Agent': 'GAM-CI-Script'})
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
if pkg_name_lower in resolved_versions:
target_version = resolved_versions[pkg_name_lower]
pinned_dep = f"{pkg_name_raw}=={target_version}{marker}"
valid_versions = []
for ver, files in data.get("releases", {}).items():
if not files:
continue
upload_time_str = files[0].get("upload_time_iso_8601")
if not upload_time_str:
continue
if upload_time_str.endswith('Z'):
upload_time_str = upload_time_str[:-1] + '+00:00'
upload_time = datetime.fromisoformat(upload_time_str)
# Filter: Must be older than 2 weeks and not a pre-release
if upload_time <= two_weeks_ago and not any(x in ver.lower() for x in ['a', 'b', 'rc', 'dev']):
valid_versions.append((upload_time, ver))
if valid_versions:
# Sort by upload time descending to get the newest valid option
valid_versions.sort(key=lambda x: x[0], reverse=True)
target_version = valid_versions[0][1]
pinned_dep = f"{pkg_name}=={target_version}{marker}"
if pinned_dep != dep:
updates[dep] = pinned_dep
print(f" -> Pinning: '{dep}' => '{pinned_dep}'")
else:
print(f" -> Already pinned correctly to {target_version}")
if pinned_dep != dep:
updates[dep] = pinned_dep
print(f" -> Changing: '{dep}' => '{pinned_dep}'")
else:
print(f" -> No valid historical versions found.")
except urllib.error.HTTPError as e:
print(f" -> Package not found on PyPI or HTTP error: {e}")
except Exception as e:
print(f" -> Error processing {pkg_name}: {e}")
print(f" -> Up to date: {dep}")
# 3. Replace the strings safely in the original file content
# 6. Replace the strings safely in the original file content
new_content = content
for old_dep, new_dep in updates.items():
# Regex targets the exact string inside either single or double quotes
# Using a lambda replacement ensures we don't trip over escape sequences in the new string
escaped_old = re.escape(old_dep)
pattern = r'([\'"])' + escaped_old + r'\1'
new_content = re.sub(pattern, lambda m: m.group(1) + new_dep + m.group(1), new_content)
# Write changes back to pyproject.toml
if content != new_content:
toml_path.write_text(new_content, encoding="utf-8")
print("\npyproject.toml updated successfully.")
@@ -139,6 +147,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore: upgrade PyPi deps"
title: "Upgrade PyPi deps"
body: "Automated scan checking PyPI for package versions at least 2 weeks old."
body: "Automated scan checking PyPI for mutually compatible package versions at least 2 weeks old. Handles both upgrades and conflict-driven downgrades."
branch: sys-deps-upgrade
force: false # Standard push, plays nice with rulesets

View File

@@ -1,6 +0,0 @@
# overrides uv.lock to force newer dependencies
# when old deps are vulnerable. These should be set
# to expire after 2 weeks when the fixed version will
# be automatically picked up anyway.
# Format: package_requirement | MM/DD/YYYY
urllib3>=2.7.0 | 05/22/2026

View File

@@ -10,13 +10,13 @@ authors = [
dependencies = [
"arrow==1.4.0",
"chardet==7.4.3",
"cryptography==48.0.0",
"cryptography==48.0.1",
"distro==1.9.0 ; sys_platform=='linux'",
"filelock==3.29.1",
"filelock==3.29.4",
"google-api-python-client==2.197.0",
"google-auth-httplib2==0.4.0",
"google-auth-oauthlib==1.4.0",
"google-auth==2.53.0",
"google-auth==2.54.0",
"httplib2==0.31.2",
"lxml==6.1.1",
"passlib==1.7.4",

View File

@@ -1676,6 +1676,10 @@ gam <UserTypeEntity> show analyticdatastreams
<CalendarACLScopeEntity>::=
<CalendarACLScopeList> | <FileSelector> | <CSVkmdSelector> | <CSVDataSelector>
Transfer ownership of a selection of a users secondary calendars to another user
gam calendars <CalendarEntity> transfer <UserItem>
gam calendars <CalendarEntity> create|add acls|calendaracls <CalendarACLRole> <CalendarACLScopeEntity> [sendnotifications <Boolean>]
gam calendars <CalendarEntity> update acls|calendaracls <CalendarACLRole> <CalendarACLScopeEntity> [sendnotifications <Boolean>]
gam calendars <CalendarEntity> delete acls|calendaracls [<CalendarACLRole>] <CalendarACLScopeEntity>
@@ -6298,13 +6302,6 @@ gam <UserTypeEntity> print calendaracls <UserCalendarEntity> [todrive <ToDriveAt
[noselfowner] (addcsvdata <FieldName> <String>)*
[formatjson [quotechar <Character>]]
Transfer ownership of a selection of a users secondary calendars to another user
gam <UserTypeEntity> transfer calendars|seccals <UserItem> [<UserCalendarEntity>]
[keepuser | (retainrole <CalendarACLRole>)] [sendnotifications <Boolean>] [noretentionmessages]
<CalendarSettings>* [append description|location|summary] [noupdatemessages]
[deletefromoldowner] [addtonewowner <CalendarAttribute>*] [nolistmessages]
<AttendeeAttendance> ::= optional|required
<AttendeeStatus> ::= accepted|declined|needsaction|tentative

View File

@@ -1,6 +1,10 @@
7.46.03
Updated all Vault related commands to handle the following error: `ERROR: 403: permissionDenied`
7.46.02
Updated `gam <UserTypeEntity> show calsettings` to display `dataOwner` field;
Updated `gam calendars <CalendarEntity> show settings` to display `dataOwner` field;
it is labelled `Owner`.
7.46.01

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
"""
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.46.02'
__version__ = '7.46.03'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
# pylint: disable=wrong-import-position
@@ -683,7 +683,7 @@ def accessErrorMessage(cd, errMsg=None):
[Ent.Singular(Ent.CUSTOMER_ID), GC.Values[GC.CUSTOMER_ID],
Msg.DOES_NOT_EXIST],
'')
except GAPI.forbidden:
except (GAPI.forbidden, GAPI.permissionDenied):
return formatKeyValueList('',
Ent.FormatEntityValueList([Ent.CUSTOMER_ID, GC.Values[GC.CUSTOMER_ID],
Ent.DOMAIN, GC.Values[GC.DOMAIN],
@@ -24871,9 +24871,9 @@ def infoCrOSDevices(entityList):
i += 1
try:
cros = callGAPI(cd.chromeosdevices(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
customerId=GC.Values[GC.CUSTOMER_ID], deviceId=deviceId, projection=projection, fields=fields)
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.permissionDenied):
checkEntityAFDNEorAccessErrorExit(cd, Ent.CROS_DEVICE, deviceId, i, count)
continue
checkTPMVulnerability(cros)
@@ -25498,7 +25498,7 @@ def doPrintCrOSDevices(entityList=None):
try:
feed = callGAPI(cd.chromeosdevices(), 'list',
throwReasons=[GAPI.INVALID_INPUT, GAPI.INVALID_ORGUNIT,
GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
pageToken=pageToken,
customerId=GC.Values[GC.CUSTOMER_ID], query=query, projection=projection,
@@ -25532,7 +25532,7 @@ def doPrintCrOSDevices(entityList=None):
except GAPI.invalidOrgunit as e:
entityActionFailedWarning([Ent.CROS_DEVICE, None], str(e))
return
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.permissionDenied):
accessErrorExit(cd)
if showItemCountOnly:
writeStdout(f'{totalItems}\n')
@@ -43644,7 +43644,7 @@ def convertExportNameToID(v, nameOrId, matterId, matterNameId):
if cg:
try:
export = callGAPI(v.matters().exports(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN,
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED,
GAPI.INVALID_ARGUMENT, GAPI.FAILED_PRECONDITION],
matterId=matterId, exportId=cg.group(1))
return (export['id'], export['name'], formatVaultNameId(export['id'], export['name']))
@@ -43652,14 +43652,14 @@ def convertExportNameToID(v, nameOrId, matterId, matterNameId):
entityDoesNotHaveItemExit([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, nameOrId])
except (GAPI.failedPrecondition) as e:
entityActionFailedExit([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, nameOrId], str(e))
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
nameOrIdlower = nameOrId.lower()
try:
exports = callGAPIpages(v.matters().exports(), 'list', 'exports',
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields='exports(id,name),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
for export in exports:
if export['name'].lower() == nameOrIdlower:
@@ -43671,19 +43671,19 @@ def convertHoldNameToID(v, nameOrId, matterId, matterNameId):
if cg:
try:
hold = callGAPI(v.matters().holds(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=cg.group(1))
return (hold['holdId'], hold['name'], formatVaultNameId(hold['holdId'], hold['name']))
except (GAPI.notFound, GAPI.badRequest):
entityDoesNotHaveItemExit([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, nameOrId])
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
nameOrIdlower = nameOrId.lower()
try:
holds = callGAPIpages(v.matters().holds(), 'list', 'holds',
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields='holds(holdId,name),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
for hold in holds:
if hold['name'].lower() == nameOrIdlower:
@@ -43695,16 +43695,18 @@ def convertMatterNameToID(v, nameOrId, state=None):
if cg:
try:
matter = callGAPI(v.matters(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=cg.group(1), view='BASIC', fields='matterId,name,state')
return (matter['matterId'], matter['name'], formatVaultNameId(matter['name'], matter['matterId']), matter['state'])
except (GAPI.notFound, GAPI.forbidden):
except GAPI.notFound:
entityDoesNotExistExit(Ent.VAULT_MATTER, nameOrId)
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
try:
matters = callGAPIpages(v.matters(), 'list', 'matters',
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
view='BASIC', state=state, fields='matters(matterId,name,state),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
nameOrIdlower = nameOrId.lower()
ids = []
@@ -43726,19 +43728,19 @@ def convertQueryNameToID(v, nameOrId, matterId, matterNameId):
if cg:
try:
query = callGAPI(v.matters().savedQueries(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, savedQueryId=cg.group(1))
return (query['savedQueryId'], query['displayName'], formatVaultNameId(query['savedQueryId'], query['displayName']), query['query'])
except (GAPI.notFound, GAPI.badRequest):
entityDoesNotHaveItemExit([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, nameOrId])
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
nameOrIdlower = nameOrId.lower()
try:
queries = callGAPIpages(v.matters().savedQueries(), 'list', 'savedQueries',
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields='savedQueries(savedQueryId,displayName,query),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
ClientAPIAccessDeniedExit(str(e))
for query in queries:
if query['displayName'].lower() == nameOrIdlower:
@@ -43753,9 +43755,9 @@ def warnMatterNotOpen(v, matter, matterNameId, j, jcount):
if v is not None:
try:
matter['state'] = callGAPI(v.matters(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matter['matterId'], view='BASIC', fields='state')['state']
except (GAPI.notFound, GAPI.forbidden, GAPI.invalidArgument):
except (GAPI.notFound, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument):
matter['state'] = 'Unknown'
else:
setSysExitRC(DATA_NOT_AVALIABLE_RC)
@@ -44126,10 +44128,10 @@ def doDeleteVaultExport():
unknownArgumentExit()
try:
callGAPI(v.matters().exports(), 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, exportId=exportId)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, exportNameId])
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, exportNameId], str(e))
VAULT_EXPORT_FIELDS_CHOICE_MAP = {
@@ -44184,7 +44186,7 @@ def doInfoVaultExport():
GAPI.INVALID_ARGUMENT, GAPI.FAILED_PRECONDITION],
matterId=matterId, exportId=exportId, fields=fields)
_showVaultExport(matterNameId, export, cd, FJQC)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, exportNameId], str(e))
VAULT_EXPORT_STATUS_MAP = {'completed': 'COMPLETED', 'failed': 'FAILED', 'inprogress': 'IN_PROGRESS'}
@@ -44247,9 +44249,9 @@ def doPrintShowVaultExports():
try:
results = callGAPIpages(v.matters(), 'list', 'matters',
pageMessage=getPageMessage(),
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
view='BASIC', state='OPEN', fields='matters(matterId,name,state),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_EXPORT, None], str(e))
return
else:
@@ -44277,12 +44279,12 @@ def doPrintShowVaultExports():
try:
exports = callGAPIpages(v.matters().exports(), 'list', 'exports',
pageMessage=pageMessage,
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields=fields)
except GAPI.failedPrecondition:
warnMatterNotOpen(v, matter, matterNameId, j, jcount)
continue
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_EXPORT, None], str(e))
break
else:
@@ -44360,7 +44362,7 @@ def doCopyVaultExport():
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN,
GAPI.INVALID_ARGUMENT, GAPI.FAILED_PRECONDITION],
matterId=matterId, exportId=exportId)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, exportNameId], str(e))
return
if export['status'] == 'COMPLETED':
@@ -44461,7 +44463,7 @@ def doDownloadVaultExport():
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN,
GAPI.INVALID_ARGUMENT, GAPI.FAILED_PRECONDITION],
matterId=matterId, exportId=exportId)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_EXPORT, exportNameId], str(e))
return
if export['status'] == 'COMPLETED':
@@ -44687,7 +44689,7 @@ def doCreateVaultHold():
try:
hold = callGAPI(v.matters().holds(), 'create',
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.BAD_REQUEST, GAPI.BACKEND_ERROR, GAPI.FAILED_PRECONDITION,
GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, body=body)
if not returnIdOnly:
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, formatVaultNameId(hold['name'], hold['holdId'])])
@@ -44696,7 +44698,7 @@ def doCreateVaultHold():
else:
writeStdout(f'{hold["holdId"]}\n')
except (GAPI.alreadyExists, GAPI.badRequest, GAPI.backendError, GAPI.failedPrecondition,
GAPI.forbidden, GAPI.invalidArgument) as e:
GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, body.get('name')], str(e))
# gam update vaulthold|hold <HoldItem> matter <MatterItem>
@@ -44741,9 +44743,9 @@ def doUpdateVaultHold():
missingArgumentExit('matter')
try:
old_body = callGAPI(v.matters().holds(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId, fields='name,corpus,query,orgUnit')
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId], str(e))
return
accountType = 'group' if old_body['corpus'] == 'GROUPS' else 'user'
@@ -44769,10 +44771,10 @@ def doUpdateVaultHold():
if body:
try:
hold = callGAPI(v.matters().holds(), 'update',
throwReas=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReas=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId, body=body)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId])
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId], str(e))
return
jcount = len(addAccountIds)
@@ -44785,12 +44787,12 @@ def doUpdateVaultHold():
j += 1
try:
callGAPI(v.matters().holds().accounts(), 'create',
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.BACKEND_ERROR, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.BACKEND_ERROR, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId, body={'accountId': account['id']})
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId, Ent.ACCOUNT, account['email']], j, jcount)
except (GAPI.alreadyExists, GAPI.backendError) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId, Ent.ACCOUNT, account['email']], str(e), j, jcount)
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, None], str(e))
return
Ind.Decrement()
@@ -44806,12 +44808,12 @@ def doUpdateVaultHold():
j += 1
try:
callGAPI(v.matters().holds().accounts(), 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.BACKEND_ERROR, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BACKEND_ERROR, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId, accountId=account['id'])
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId, Ent.ACCOUNT, account['email']], j, jcount)
except (GAPI.alreadyExists, GAPI.backendError) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId, Ent.ACCOUNT, account['email']], str(e), j, jcount)
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, None], str(e))
return
Ind.Decrement()
@@ -44836,10 +44838,10 @@ def doDeleteVaultHold():
unknownArgumentExit()
try:
callGAPI(v.matters().holds(), 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId])
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId], str(e))
VAULT_HOLD_FIELDS_CHOICE_MAP = {
@@ -44889,10 +44891,10 @@ def doInfoVaultHold():
fields = getFieldsFromFieldsList(fieldsList)
try:
hold = callGAPI(v.matters().holds(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, holdId=holdId, fields=fields)
_showVaultHold(matterNameId, hold, cd, FJQC)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_HOLD, holdNameId], str(e))
PRINT_VAULT_HOLDS_TITLES = ['matterId', 'matterName', 'holdId', 'name', 'updateTime']
@@ -44943,9 +44945,9 @@ def doPrintShowVaultHolds():
try:
results = callGAPIpages(v.matters(), 'list', 'matters',
pageMessage=getPageMessage(),
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
view='BASIC', state='OPEN', fields='matters(matterId,name,state),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_HOLD, None], str(e))
return
else:
@@ -44972,12 +44974,12 @@ def doPrintShowVaultHolds():
try:
holds = callGAPIpages(v.matters().holds(), 'list', 'holds',
pageMessage=pageMessage,
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields=fields)
except GAPI.failedPrecondition:
warnMatterNotOpen(v, matter, matterNameId, j, jcount)
continue
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_HOLD, None], str(e))
break
else:
@@ -45023,9 +45025,9 @@ def printShowUserVaultHolds(entityList):
try:
matters = callGAPIpages(v.matters(), 'list', 'matters',
pageMessage=getPageMessage(),
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
view='BASIC', state='OPEN', fields='matters(matterId,name,state),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_HOLD, None], str(e))
return
jcount = len(matters)
@@ -45039,11 +45041,11 @@ def printShowUserVaultHolds(entityList):
try:
matter['holds'] = callGAPIpages(v.matters().holds(), 'list', 'holds',
pageMessage=getPageMessageForWhom(),
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields='holds(holdId,name,accounts(accountId,email),orgUnit(orgUnitId)),nextPageToken')
except GAPI.failedPrecondition:
warnMatterNotOpen(v, matter, matterNameId, j, jcount)
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_HOLD, None], str(e), j, jcount)
totalHolds = 0
_, _, entityList = getEntityArgument(entityList)
@@ -45157,7 +45159,7 @@ def doCreateCopyVaultQuery(copyCmd):
resultNameId = matterNameId
try:
result = callGAPI(v.matters().savedQueries(), 'create',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT, GAPI.ALREADY_EXISTS],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT, GAPI.ALREADY_EXISTS],
matterId=resultId, body=body)
if not returnIdOnly:
if not FJQC.formatJSON:
@@ -45166,7 +45168,7 @@ def doCreateCopyVaultQuery(copyCmd):
_showVaultQuery(resultNameId, result, cd, drive, FJQC)
else:
writeStdout(f'{result["savedQueryId"]}\n')
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument, GAPI.alreadyExists) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.alreadyExists) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, resultNameId, Ent.VAULT_QUERY, body['displayName']], str(e))
# gam create vaultquery <MatterItem> [name <String>]
@@ -45214,10 +45216,10 @@ def doDeleteVaultQuery():
unknownArgumentExit()
try:
callGAPI(v.matters().savedQueries(), 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, savedQueryId=queryId)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId])
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e))
VAULT_QUERY_FIELDS_CHOICE_MAP = {
@@ -45263,10 +45265,10 @@ def doInfoVaultQuery():
fields = getFieldsFromFieldsList(fieldsList)
try:
query = callGAPI(v.matters().savedQueries(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, savedQueryId=queryId, fields=fields)
_showVaultQuery(matterNameId, query, cd, drive, FJQC)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e))
PRINT_VAULT_QUERIES_TITLES = ['matterId', 'matterName', 'savedQueryId', 'displayName']
@@ -45307,9 +45309,9 @@ def doPrintShowVaultQueries():
try:
results = callGAPIpages(v.matters(), 'list', 'matters',
pageMessage=getPageMessage(),
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
view='BASIC', state='OPEN', fields='matters(matterId,name,state),nextPageToken')
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_QUERY, None], str(e))
return
else:
@@ -45336,12 +45338,12 @@ def doPrintShowVaultQueries():
try:
queries = callGAPIpages(v.matters().savedQueries(), 'list', 'savedQueries',
pageMessage=pageMessage,
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, fields=fields)
except GAPI.failedPrecondition:
warnMatterNotOpen(v, matter, matterNameId, j, jcount)
continue
except (GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_QUERY, None], str(e))
break
else:
@@ -45428,7 +45430,7 @@ def doCreateVaultMatter():
body['name'] = f'GAM Matter - {ISOformatTimeStamp(todaysTime())}'
try:
matter = callGAPI(v.matters(), 'create',
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.ALREADY_EXISTS, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
body=body)
matterId = matter['matterId']
matterNameId = formatVaultNameId(matter['name'], matterId)
@@ -45436,7 +45438,7 @@ def doCreateVaultMatter():
entityActionPerformed([Ent.VAULT_MATTER, matterNameId])
else:
writeStdout(f'{matterId}\n')
except (GAPI.alreadyExists, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.alreadyExists, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, body['name']], str(e))
return
jcount = len(collaborators)
@@ -45451,11 +45453,11 @@ def doCreateVaultMatter():
cbody['matterPermission']['accountId'] = collaborator['id']
try:
callGAPI(v.matters(), 'addPermissions',
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, body=cbody)
if not returnIdOnly:
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.COLLABORATOR, collaborator['email']], j, jcount)
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
break
Ind.Decrement()
@@ -45479,10 +45481,10 @@ def doActionVaultMatter(action, matterId=None, matterNameId=None, v=None):
action_kwargs = {} if action == 'delete' else {'body': {}}
try:
callGAPI(v.matters(), action,
throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, **action_kwargs)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId])
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
# gam close vaultmatter|matter <MatterItem>
@@ -45535,7 +45537,7 @@ def doUpdateVaultMatter():
if 'name' not in body or 'description' not in body:
# bah, API requires name/description to be sent on update even when it's not changing
result = callGAPI(v.matters(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, view='BASIC')
body.setdefault('name', result['name'])
body.setdefault('description', result.get('description'))
@@ -45543,7 +45545,7 @@ def doUpdateVaultMatter():
throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN],
matterId=matterId, body=body)
entityActionPerformed([Ent.VAULT_MATTER, matterNameId])
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
return
jcount = len(addCollaborators)
@@ -45556,10 +45558,10 @@ def doUpdateVaultMatter():
j += 1
try:
callGAPI(v.matters(), 'addPermissions',
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, body={'matterPermission': {'role': 'COLLABORATOR', 'accountId': collaborator['id']}})
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.COLLABORATOR, collaborator['email']], j, jcount)
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
break
Ind.Decrement()
@@ -45573,10 +45575,10 @@ def doUpdateVaultMatter():
j += 1
try:
callGAPI(v.matters(), 'removePermissions',
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, body={'accountId': collaborator['id']})
entityActionPerformed([Ent.VAULT_MATTER, matterNameId, Ent.COLLABORATOR, collaborator['email']], j, jcount)
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
break
Ind.Decrement()
@@ -45609,11 +45611,11 @@ def doInfoVaultMatter():
fields = getFieldsFromFieldsList(fieldsList)
try:
matter = callGAPI(v.matters(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT],
matterId=matterId, view=view, fields=fields)
cd = buildGAPIObject(API.DIRECTORY) if 'matterPermissions' in matter else None
_showVaultMatter(matter, cd, FJQC)
except (GAPI.notFound, GAPI.forbidden, GAPI.invalidArgument) as e:
except (GAPI.notFound, GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId], str(e))
VAULT_MATTER_STATE_MAP = {'open': 'OPEN', 'closed': 'CLOSED', 'deleted': 'DELETED'}
@@ -45669,9 +45671,9 @@ def doPrintShowVaultMatters():
try:
matters = callGAPIpages(v.matters(), 'list', 'matters',
pageMessage=getPageMessage(),
throwReasons=[GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT, GAPI.INVALID_ARGUMENT],
throwReasons=[GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.INVALID_ARGUMENT, GAPI.INVALID_ARGUMENT],
view=view, state=stateParm, fields=fields)
except (GAPI.forbidden, GAPI.invalidArgument, GAPI.invalidArgument) as e:
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.invalidArgument) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, None], str(e))
return
jcount = len(matters)
@@ -55348,16 +55350,30 @@ def printShowCalendarACLs(users):
if csvPF:
csvPF.writeCSVfile('Calendar ACLs')
# gam <UserTypeEntity> transfer calendars|seccals <UserItem> [<UserCalendarEntity>]
# [keepuser | (retainrole <CalendarACLRole>)] [sendnotifications <Boolean>] [noretentionmessages]
# <CalendarSettings>* [append description|location|summary] [noupdatemessages]
# [deletefromoldowner] [addtonewowner <CalendarAttribute>*] [nolistmessages]
def transferCalendars(users):
errMsg = ''' Due to the following Calendar API update, the `gam <UserTypeEntity> transfer calendars` command has been removed.
* See: https://developers.google.com/workspace/calendar/release-notes#October_27_2025
Data ownership can be transferred in the Google Calendar UI.
'''
systemErrorExit(ACTION_FAILED_RC, errMsg)
# gam <CalendarEntity> transfer ownership <UserItem>
def doCalendarsTransferOwnership(calIds):
Act.Set(Act.TRANSFER_OWNERSHIP)
newDataOwner = getEmailAddress()
checkForExtraneousArguments()
count = len(calIds)
i = 0
for calId in calIds:
i += 1
calId, cal = validateCalendar(calId, i, count)
if not cal:
continue
try:
callGAPI(cal.calendars(), 'transferOwnership',
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID, GAPI.INVALID_PARAMETER,
GAPI.FORBIDDEN, GAPI.AUTH_ERROR, GAPI.CONDITION_NOT_MET],
calendarId=calId, newDataOwner=newDataOwner, useAdminAccess=True)
entityPerformActionModifierNewValue([Ent.CALENDAR, calId], Act.MODIFIER_TO, newDataOwner, i, count)
except (GAPI.notFound, GAPI.invalid, GAPI.invalidParameter,
GAPI.forbidden, GAPI.authError, GAPI.conditionNotMet) as e:
entityModifierNewValueActionFailedWarning([Ent.CALENDAR, calId], Act.MODIFIER_TO, newDataOwner, str(e), i, count)
except AttributeError as e:
entityModifierNewValueActionFailedWarning([Ent.CALENDAR, calId], Act.MODIFIER_TO, newDataOwner, str(e), i, count)
return
def _createImportCalendarEvent(users, function):
calendarEntity = getUserCalendarEntity()
@@ -81570,6 +81586,11 @@ CALENDARS_SUBCOMMANDS_WITH_OBJECTS = {
Cmd.ARG_SETTINGS: doCalendarsPrintShowSettings,
}
),
'transfer':
(Act.TRANSFER,
{Cmd.ARG_OWNERSHIP: doCalendarsTransferOwnership,
}
),
'update':
(Act.UPDATE,
{Cmd.ARG_CALENDARACL: doCalendarsUpdateACLs,
@@ -82304,7 +82325,6 @@ USER_COMMANDS_WITH_OBJECTS = {
'transfer':
(Act.TRANSFER,
{Cmd.ARG_DRIVE: transferDrive,
Cmd.ARG_CALENDAR: transferCalendars,
Cmd.ARG_OWNERSHIP: transferOwnership,
}
),

View File

@@ -10,6 +10,11 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
### 7.46.02
Updated `gam calendars <CalendarEntity> show settings` to display `dataOwner` field;
it is labelled `Owner`.
### 7.46.01
Fixed bug in `gam <CrOSTypeEntity> issuecommand command <CrOSCommand> ... csv` where

View File

@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
```
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
gamteam@server:/Users/gamteam$ gam version
GAM 7.46.01 - https://github.com/GAM-team/GAM - pyinstaller
GAM 7.46.02 - https://github.com/GAM-team/GAM - pyinstaller
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
macOS Tahoe 26.5.1 arm64
@@ -1034,7 +1034,7 @@ writes the credentials into the file oauth2.txt.
```
C:\>del C:\GAMConfig\oauth2.txt
C:\>gam version
GAM 7.46.01 - https://github.com/GAM-team/GAM - pythonsource
GAM 7.46.02 - https://github.com/GAM-team/GAM - pythonsource
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
Windows 11 10.0.26200 AMD64

View File

@@ -535,6 +535,14 @@ User: user@domain.com, Delete maximum of 15 Other Contacts
User: user@domain.com, Other Contact: otherContacts/c6318452176100245073, Deleted
```
Bulk delete Other Contacts
Let's suppose you have a CSV file (OtherContacts.csv) with at least these two headers: User,resourceName
The file can contain multiple users and their other contacts. This is the most API effecient way to delete the contacts.
```
gam redirect stdout ./DeleteOtherContacts.txt redirect stderr stdout csvkmd users OtherContacts.csv keyfield User datafield resourceName delete othercontacts csvdata resourceName
```
## Display User Other Contacts
### Display as an indented list of keys and values.
```

View File

@@ -3,7 +3,7 @@
Print the current version of Gam with details
```
gam version
GAM 7.46.01 - https://github.com/GAM-team/GAM - pyinstaller
GAM 7.46.02 - https://github.com/GAM-team/GAM - pyinstaller
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
macOS Tahoe 26.5.1 arm64
@@ -15,7 +15,7 @@ Time: 2026-02-15T07:51:00-08:00
Print the current version of Gam with details and time offset information
```
gam version timeoffset
GAM 7.46.01 - https://github.com/GAM-team/GAM - pyinstaller
GAM 7.46.02 - https://github.com/GAM-team/GAM - pyinstaller
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
macOS Tahoe 26.5.1 arm64
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
Print the current version of Gam with extended details and SSL information
```
gam version extended
GAM 7.46.01 - https://github.com/GAM-team/GAM - pyinstaller
GAM 7.46.02 - https://github.com/GAM-team/GAM - pyinstaller
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
macOS Tahoe 26.5.1 arm64
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/gamteam/bin/gam7
Version Check:
Current: 5.35.08
Latest: 7.46.01
Latest: 7.46.02
echo $?
1
```
@@ -76,7 +76,7 @@ echo $?
Print the current version number without details
```
gam version simple
7.46.01
7.46.02
```
In Linux/MacOS you can do:
```
@@ -86,7 +86,7 @@ echo $VER
Print the current version of Gam and address of this Wiki
```
gam help
GAM 7.46.01 - https://github.com/GAM-team/GAM
GAM 7.46.02 - https://github.com/GAM-team/GAM
GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.6 64-bit final
macOS Tahoe 26.5.1 arm64