ttsd-colabcli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +148 -0
- package/core/app/__init__.py +0 -0
- package/core/app/colab_cli/__init__.py +0 -0
- package/core/app/colab_cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/auth.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/auto_update.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/cli.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/client.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/common.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/console.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/contents.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/history.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/runtime.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/state.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/utils.cpython-312.pyc +0 -0
- package/core/app/colab_cli/auth.py +278 -0
- package/core/app/colab_cli/auto_update.py +248 -0
- package/core/app/colab_cli/cli.py +155 -0
- package/core/app/colab_cli/client.py +310 -0
- package/core/app/colab_cli/commands/__init__.py +14 -0
- package/core/app/colab_cli/commands/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/automation.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/execution.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/files.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/run.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/session.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/utility.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/automation.py +265 -0
- package/core/app/colab_cli/commands/execution.py +362 -0
- package/core/app/colab_cli/commands/files.py +204 -0
- package/core/app/colab_cli/commands/run.py +477 -0
- package/core/app/colab_cli/commands/session.py +519 -0
- package/core/app/colab_cli/commands/utility.py +436 -0
- package/core/app/colab_cli/common.py +185 -0
- package/core/app/colab_cli/console.py +172 -0
- package/core/app/colab_cli/contents.py +93 -0
- package/core/app/colab_cli/converter.py +184 -0
- package/core/app/colab_cli/history.py +65 -0
- package/core/app/colab_cli/oauth_config.json +11 -0
- package/core/app/colab_cli/repl.py +173 -0
- package/core/app/colab_cli/runtime.py +262 -0
- package/core/app/colab_cli/state.py +156 -0
- package/core/app/colab_cli/utils.py +85 -0
- package/core/colab/worker.py +679 -0
- package/core/daemon.py +184 -0
- package/core/requirements.txt +8 -0
- package/package.json +22 -0
package/cli.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn, execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const CMD = process.argv[2] || 'status';
|
|
8
|
+
const ENV_PATH = path.join(__dirname, '.env');
|
|
9
|
+
const CORE_DIR = path.join(__dirname, 'core');
|
|
10
|
+
const PID_FILE = path.join(__dirname, '.daemon.pid');
|
|
11
|
+
|
|
12
|
+
async function promptConfig() {
|
|
13
|
+
const inquirer = require('inquirer');
|
|
14
|
+
console.log('=====================================');
|
|
15
|
+
console.log(' SATELLITE NODE INITIAL SETUP');
|
|
16
|
+
console.log('=====================================\n');
|
|
17
|
+
const answers = await inquirer.prompt([
|
|
18
|
+
{
|
|
19
|
+
type: 'input',
|
|
20
|
+
name: 'masterUrl',
|
|
21
|
+
message: 'Enter MASTER_URL (e.g., http://api.yourdomain.com):',
|
|
22
|
+
validate: input => input ? true : 'Required!'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'nodeApiKey',
|
|
27
|
+
message: 'Enter NODE_API_KEY (e.g., satellite-secret-key-123):',
|
|
28
|
+
validate: input => input ? true : 'Required!'
|
|
29
|
+
}
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const envContent = `MASTER_URL=${answers.masterUrl}\nNODE_API_KEY=${answers.nodeApiKey}\n`;
|
|
33
|
+
fs.writeFileSync(ENV_PATH, envContent);
|
|
34
|
+
console.log('Configuration saved to .env file.\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setupPythonEnv() {
|
|
38
|
+
const venvDir = path.join(CORE_DIR, 'venv');
|
|
39
|
+
if (!fs.existsSync(venvDir)) {
|
|
40
|
+
console.log('Creating Python virtual environment...');
|
|
41
|
+
try {
|
|
42
|
+
execSync(`python3 -m venv "${venvDir}"`, { stdio: 'inherit' });
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.log('python3 not found, trying python...');
|
|
45
|
+
execSync(`python -m venv "${venvDir}"`, { stdio: 'inherit' });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('Installing dependencies...');
|
|
50
|
+
const pip = process.platform === 'win32'
|
|
51
|
+
? path.join(venvDir, 'Scripts', 'pip')
|
|
52
|
+
: path.join(venvDir, 'bin', 'pip');
|
|
53
|
+
execSync(`"${pip}" install -r "${path.join(CORE_DIR, 'requirements.txt')}" -q`, { stdio: 'inherit' });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function start() {
|
|
57
|
+
if (fs.existsSync(PID_FILE)) {
|
|
58
|
+
const oldPid = fs.readFileSync(PID_FILE, 'utf8').trim();
|
|
59
|
+
try {
|
|
60
|
+
process.kill(parseInt(oldPid), 0);
|
|
61
|
+
console.log(`Daemon is already running with PID ${oldPid}.`);
|
|
62
|
+
return;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Process not running, clean up PID file
|
|
65
|
+
fs.unlinkSync(PID_FILE);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(ENV_PATH)) {
|
|
70
|
+
await promptConfig();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setupPythonEnv();
|
|
74
|
+
|
|
75
|
+
console.log('Starting Satellite Daemon in background...');
|
|
76
|
+
|
|
77
|
+
const pythonBin = process.platform === 'win32'
|
|
78
|
+
? path.join(CORE_DIR, 'venv', 'Scripts', 'python')
|
|
79
|
+
: path.join(CORE_DIR, 'venv', 'bin', 'python');
|
|
80
|
+
|
|
81
|
+
// Copy .env to core dir so daemon.py can load it
|
|
82
|
+
fs.copyFileSync(ENV_PATH, path.join(CORE_DIR, '.env'));
|
|
83
|
+
|
|
84
|
+
const out = fs.openSync(path.join(__dirname, 'daemon.log'), 'a');
|
|
85
|
+
const err = fs.openSync(path.join(__dirname, 'daemon.error.log'), 'a');
|
|
86
|
+
|
|
87
|
+
const child = spawn(pythonBin, ['daemon.py'], {
|
|
88
|
+
cwd: CORE_DIR,
|
|
89
|
+
detached: true,
|
|
90
|
+
stdio: ['ignore', out, err]
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
child.unref();
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(PID_FILE, child.pid.toString());
|
|
96
|
+
console.log(`Daemon started successfully (PID: ${child.pid}).`);
|
|
97
|
+
console.log('You can now close this terminal. The node will keep running in the background.');
|
|
98
|
+
console.log('Run "colabcli status" to check its status or "colabcli stop" to stop it.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stop() {
|
|
102
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
103
|
+
console.log('No daemon is currently running.');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pid = fs.readFileSync(PID_FILE, 'utf8').trim();
|
|
108
|
+
try {
|
|
109
|
+
process.kill(parseInt(pid), 'SIGTERM');
|
|
110
|
+
console.log(`Daemon (PID ${pid}) stopped successfully.`);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.log(`Could not stop process ${pid}. It might have already stopped.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fs.unlinkSync(PID_FILE);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function status() {
|
|
119
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
120
|
+
console.log('Satellite Daemon is NOT running.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const pid = fs.readFileSync(PID_FILE, 'utf8').trim();
|
|
125
|
+
try {
|
|
126
|
+
process.kill(parseInt(pid), 0);
|
|
127
|
+
console.log(`Satellite Daemon is RUNNING (PID ${pid}).`);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.log('Satellite Daemon is NOT running (stale PID file found).');
|
|
130
|
+
fs.unlinkSync(PID_FILE);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function main() {
|
|
135
|
+
if (CMD === 'start') {
|
|
136
|
+
await start();
|
|
137
|
+
} else if (CMD === 'stop') {
|
|
138
|
+
stop();
|
|
139
|
+
} else if (CMD === 'status') {
|
|
140
|
+
status();
|
|
141
|
+
} else {
|
|
142
|
+
console.log('Usage: colabcli <start|stop|status>');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main().catch(err => {
|
|
147
|
+
console.error('Error:', err);
|
|
148
|
+
});
|
|
File without changes
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import enum
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
from importlib import resources
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import warnings
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
import google.auth
|
|
25
|
+
import typer
|
|
26
|
+
from google.auth.transport import requests
|
|
27
|
+
from google.auth.transport.requests import Request
|
|
28
|
+
from google.oauth2.credentials import Credentials
|
|
29
|
+
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# Set by worker to skip interactive OAuth prompt
|
|
34
|
+
_DISABLE_INTERACTIVE_AUTH = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AuthProvider(str, enum.Enum):
|
|
38
|
+
"""Authentication strategy for talking to the Colab backend.
|
|
39
|
+
|
|
40
|
+
Values are the lowercase strings accepted by the global ``--auth`` flag.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
OAUTH2 = "oauth2"
|
|
44
|
+
ADC = "adc"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Standard Scopes for Colab and Drive (Public Auth)
|
|
48
|
+
PUBLIC_SCOPES = [
|
|
49
|
+
"openid",
|
|
50
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
51
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
52
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
53
|
+
"https://www.googleapis.com/auth/colaboratory",
|
|
54
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
TOKEN_CONFIG_PATH = os.getenv("COLAB_CLI_TOKEN_PATH") or os.path.expanduser("~/.config/colab-cli/token.json")
|
|
59
|
+
|
|
60
|
+
# Remote copy-paste OAuth flow.
|
|
61
|
+
#
|
|
62
|
+
# We deliberately do NOT use a localhost redirect (`run_local_server`) or the
|
|
63
|
+
# out-of-band (OOB) redirect `urn:ietf:wg:oauth:2.0:oob`. OOB was blocked by
|
|
64
|
+
# Google in 2022 ("The out-of-band (OOB) flow has been blocked in order to
|
|
65
|
+
# keep users secure") and a localhost server is environment-dependent (fails
|
|
66
|
+
# on headless/remote/container hosts, requires an auto-openable browser, etc.).
|
|
67
|
+
#
|
|
68
|
+
# Instead we use the same mechanism `gcloud auth application-default login`
|
|
69
|
+
# uses: a real registered HTTPS landing page that displays the authorization
|
|
70
|
+
# code for the user to copy & paste, combined with the `token_usage=remote`
|
|
71
|
+
# consent parameter. This works identically in local and remote environments.
|
|
72
|
+
#
|
|
73
|
+
# The landing page below is registered to Google's cloud-SDK OAuth client
|
|
74
|
+
# (`764086051850-...`), which is also the client shipped in
|
|
75
|
+
# `colab_cli/oauth_config.json`; reusing another client id with this redirect
|
|
76
|
+
# yields `redirect_uri_mismatch`.
|
|
77
|
+
REMOTE_REDIRECT_URI = "https://sdk.cloud.google.com/applicationdefaultauthcode.html"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _run_remote_flow(client_config: dict) -> Credentials:
|
|
81
|
+
"""Run the remote copy-paste OAuth2 flow.
|
|
82
|
+
|
|
83
|
+
Interactive prompt when a TTY is available. No TTY → raises error.
|
|
84
|
+
Pre-configure tokens via `refresh_tokens.py` to avoid this flow.
|
|
85
|
+
"""
|
|
86
|
+
flow = InstalledAppFlow.from_client_config(client_config, PUBLIC_SCOPES)
|
|
87
|
+
flow.redirect_uri = REMOTE_REDIRECT_URI
|
|
88
|
+
auth_url, _ = flow.authorization_url(prompt="consent", token_usage="remote")
|
|
89
|
+
|
|
90
|
+
if _DISABLE_INTERACTIVE_AUTH or not sys.stdin.isatty():
|
|
91
|
+
raise RuntimeError(
|
|
92
|
+
f"No saved token and interactive auth disabled. "
|
|
93
|
+
f"Run `refresh_tokens.py` first, or open URL manually: {auth_url}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
typer.echo("\nTo authorize colab-cli, visit this URL in any browser:\n", err=True)
|
|
97
|
+
typer.echo(" " + auth_url + "\n", err=True)
|
|
98
|
+
typer.echo("After approving, Google will display an authorization code.", err=True)
|
|
99
|
+
try:
|
|
100
|
+
code = input("Enter the authorization code: ").strip()
|
|
101
|
+
except (EOFError, OSError) as e:
|
|
102
|
+
raise RuntimeError(f"Interactive input failed: {e}")
|
|
103
|
+
|
|
104
|
+
if not code:
|
|
105
|
+
raise RuntimeError("No authorization code provided.")
|
|
106
|
+
|
|
107
|
+
flow.fetch_token(code=code)
|
|
108
|
+
return flow.credentials
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_google_auth_credentials(config_path: str) -> Credentials:
|
|
112
|
+
"""
|
|
113
|
+
Retrieves credentials using standard public OAuth2 flow.
|
|
114
|
+
"""
|
|
115
|
+
client_config = None
|
|
116
|
+
if os.path.exists(config_path):
|
|
117
|
+
with open(config_path, "r") as f:
|
|
118
|
+
client_config = json.load(f)
|
|
119
|
+
else:
|
|
120
|
+
# Try inlined config — check multiple possible package paths
|
|
121
|
+
for pkg in ("colab_cli", "app.colab_cli"):
|
|
122
|
+
try:
|
|
123
|
+
config_resource = resources.files(pkg).joinpath("oauth_config.json")
|
|
124
|
+
if config_resource.is_file():
|
|
125
|
+
client_config = json.loads(config_resource.read_text())
|
|
126
|
+
break
|
|
127
|
+
except (Exception, ModuleNotFoundError):
|
|
128
|
+
continue
|
|
129
|
+
# Fallback: direct file path
|
|
130
|
+
if not client_config:
|
|
131
|
+
bundled = os.path.join(os.path.dirname(__file__), "oauth_config.json")
|
|
132
|
+
if os.path.exists(bundled):
|
|
133
|
+
with open(bundled) as f:
|
|
134
|
+
client_config = json.load(f)
|
|
135
|
+
|
|
136
|
+
if not client_config:
|
|
137
|
+
raise FileNotFoundError(
|
|
138
|
+
f"Client OAuth config not found at {config_path} and no inlined config available. "
|
|
139
|
+
"Please provide a valid path via -c/--client-oauth-config."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
creds = None
|
|
143
|
+
|
|
144
|
+
# Ensure config directory exists for the token file
|
|
145
|
+
os.makedirs(os.path.dirname(TOKEN_CONFIG_PATH), exist_ok=True)
|
|
146
|
+
|
|
147
|
+
if os.path.exists(TOKEN_CONFIG_PATH):
|
|
148
|
+
try:
|
|
149
|
+
creds = Credentials.from_authorized_user_file(
|
|
150
|
+
TOKEN_CONFIG_PATH, PUBLIC_SCOPES
|
|
151
|
+
)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.warning(f"Failed to load token from {TOKEN_CONFIG_PATH}: {e}")
|
|
154
|
+
|
|
155
|
+
if not creds or not creds.valid:
|
|
156
|
+
if creds and creds.expired and creds.refresh_token:
|
|
157
|
+
try:
|
|
158
|
+
creds.refresh(Request())
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning(f"Failed to refresh token: {e}")
|
|
161
|
+
creds = None
|
|
162
|
+
|
|
163
|
+
if not creds:
|
|
164
|
+
creds = _run_remote_flow(client_config)
|
|
165
|
+
|
|
166
|
+
# Save the credentials for the next run
|
|
167
|
+
try:
|
|
168
|
+
with open(TOKEN_CONFIG_PATH, "w") as token_file:
|
|
169
|
+
token_file.write(creds.to_json())
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"Failed to save token to {TOKEN_CONFIG_PATH}: {e}")
|
|
172
|
+
|
|
173
|
+
return creds
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _get_adc_credentials() -> Credentials:
|
|
177
|
+
"""Retrieves credentials using Google Application Default Credentials.
|
|
178
|
+
|
|
179
|
+
Honors the standard ADC discovery chain (``GOOGLE_APPLICATION_CREDENTIALS``,
|
|
180
|
+
``gcloud auth application-default login``, GCE/GKE metadata server, etc.).
|
|
181
|
+
|
|
182
|
+
The RuntimeService at colab.pa.googleapis.com requires the
|
|
183
|
+
`colaboratory` scope (otherwise keep-alive returns 403 SCOPE_NOT_PERMITTED).
|
|
184
|
+
Most ADC credential types (service accounts, GCE/GKE, impersonated)
|
|
185
|
+
support `with_scopes`; user credentials minted by
|
|
186
|
+
`gcloud auth application-default login` do not. For the latter, the user
|
|
187
|
+
must re-run `gcloud auth application-default login` with
|
|
188
|
+
`--scopes=openid,https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/colaboratory`
|
|
189
|
+
(`openid` and `cloud-platform` are required by `gcloud` itself; `userinfo.email`
|
|
190
|
+
is required by the session backend; `colaboratory` is required by this RPC).
|
|
191
|
+
"""
|
|
192
|
+
# `google.auth._default` emits a UserWarning when ADC user credentials
|
|
193
|
+
# don't have a quota project pinned ("Your application has authenticated
|
|
194
|
+
# using end user credentials from Google Cloud SDK without a quota
|
|
195
|
+
# project. You might receive a 'quota exceeded' or 'API not enabled'
|
|
196
|
+
# error.").
|
|
197
|
+
#
|
|
198
|
+
# That heuristic does not apply to this CLI: every call we make to
|
|
199
|
+
# `colab.pa.googleapis.com` carries `X-Goog-User-Project: 1014160490159`
|
|
200
|
+
# (Colab's project id) — see AGENTS.md item 18 — so the user's
|
|
201
|
+
# quota-project setting is irrelevant. The warning shows up on every
|
|
202
|
+
# single `colab` invocation under ADC, which is pure noise. Filter it,
|
|
203
|
+
# but keep the scope as tight as possible: only this exact message,
|
|
204
|
+
# only during this one call.
|
|
205
|
+
with warnings.catch_warnings():
|
|
206
|
+
warnings.filterwarnings(
|
|
207
|
+
"ignore",
|
|
208
|
+
message=r"Your application has authenticated using end user credentials.*",
|
|
209
|
+
category=UserWarning,
|
|
210
|
+
)
|
|
211
|
+
creds, _ = google.auth.default(scopes=list(PUBLIC_SCOPES))
|
|
212
|
+
|
|
213
|
+
if not creds.valid:
|
|
214
|
+
from google.auth import compute_engine
|
|
215
|
+
|
|
216
|
+
if isinstance(creds, compute_engine.Credentials):
|
|
217
|
+
creds = None
|
|
218
|
+
else:
|
|
219
|
+
logger.warning("Failed to obtain valid ADC credentials.")
|
|
220
|
+
try:
|
|
221
|
+
logger.warning("Trying to refresh ADC credentials")
|
|
222
|
+
creds.refresh(Request())
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.warning(f"Failed to refresh token: {e}")
|
|
225
|
+
creds = None
|
|
226
|
+
|
|
227
|
+
if not creds:
|
|
228
|
+
raise RuntimeError(
|
|
229
|
+
"No valid ADC credentials found. "
|
|
230
|
+
"Set GOOGLE_APPLICATION_CREDENTIALS or run:\n"
|
|
231
|
+
" gcloud auth application-default login \\\n"
|
|
232
|
+
" --scopes=openid,"
|
|
233
|
+
"https://www.googleapis.com/auth/cloud-platform,"
|
|
234
|
+
"https://www.googleapis.com/auth/userinfo.email,"
|
|
235
|
+
"https://www.googleapis.com/auth/colaboratory"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Some credential subclasses ignore the `scopes=` kwarg in `default()`
|
|
239
|
+
# (e.g. user creds), so re-apply via `with_scopes` when supported.
|
|
240
|
+
if getattr(creds, "requires_scopes", False):
|
|
241
|
+
try:
|
|
242
|
+
creds = creds.with_scopes(list(PUBLIC_SCOPES))
|
|
243
|
+
except Exception as e: # NotImplementedError for non-scopable creds.
|
|
244
|
+
logger.debug(f"Could not augment ADC scopes via with_scopes: {e}")
|
|
245
|
+
return creds
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_credentials(
|
|
249
|
+
config_path: Optional[str] = None,
|
|
250
|
+
provider: AuthProvider = AuthProvider.OAUTH2,
|
|
251
|
+
token_path: Optional[str] = None,
|
|
252
|
+
) -> requests.AuthorizedSession:
|
|
253
|
+
"""Unified entry point for retrieving an authorized session.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
config_path: Path to the OAuth2 client config JSON.
|
|
257
|
+
provider: Authentication strategy.
|
|
258
|
+
token_path: Path to the saved token JSON file. If provided, overrides
|
|
259
|
+
the global TOKEN_CONFIG_PATH for this call.
|
|
260
|
+
"""
|
|
261
|
+
if provider == AuthProvider.OAUTH2:
|
|
262
|
+
if not config_path:
|
|
263
|
+
config_path = os.path.expanduser("~/.colab-cli-oauth-config.json")
|
|
264
|
+
if token_path:
|
|
265
|
+
orig_token_path = TOKEN_CONFIG_PATH
|
|
266
|
+
try:
|
|
267
|
+
globals()["TOKEN_CONFIG_PATH"] = token_path
|
|
268
|
+
creds = _get_google_auth_credentials(config_path)
|
|
269
|
+
finally:
|
|
270
|
+
globals()["TOKEN_CONFIG_PATH"] = orig_token_path
|
|
271
|
+
else:
|
|
272
|
+
creds = _get_google_auth_credentials(config_path)
|
|
273
|
+
elif provider == AuthProvider.ADC:
|
|
274
|
+
creds = _get_adc_credentials()
|
|
275
|
+
else:
|
|
276
|
+
raise ValueError(f"Unknown auth provider: {provider!r}")
|
|
277
|
+
|
|
278
|
+
return requests.AuthorizedSession(creds)
|