pltr-cli 0.1.0__py3-none-any.whl
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.
- pltr/__init__.py +1 -0
- pltr/auth/__init__.py +1 -0
- pltr/auth/base.py +49 -0
- pltr/auth/manager.py +129 -0
- pltr/auth/oauth.py +83 -0
- pltr/auth/storage.py +87 -0
- pltr/auth/token.py +55 -0
- pltr/cli.py +54 -0
- pltr/commands/__init__.py +1 -0
- pltr/commands/configure.py +151 -0
- pltr/commands/dataset.py +98 -0
- pltr/commands/verify.py +185 -0
- pltr/config/__init__.py +1 -0
- pltr/config/profiles.py +124 -0
- pltr/config/settings.py +103 -0
- pltr/services/__init__.py +1 -0
- pltr/services/base.py +62 -0
- pltr/services/dataset.py +90 -0
- pltr/services/dataset_full.py +302 -0
- pltr/services/dataset_v2.py +128 -0
- pltr/utils/__init__.py +1 -0
- pltr/utils/formatting.py +331 -0
- pltr/utils/progress.py +328 -0
- pltr_cli-0.1.0.dist-info/METADATA +203 -0
- pltr_cli-0.1.0.dist-info/RECORD +28 -0
- pltr_cli-0.1.0.dist-info/WHEEL +4 -0
- pltr_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pltr_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
pltr/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
pltr/auth/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Authentication module for pltr."""
|
pltr/auth/base.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base authentication classes for Palantir Foundry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthProvider(ABC):
|
|
10
|
+
"""Abstract base class for authentication providers."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def get_client(self) -> Any:
|
|
14
|
+
"""Return an authenticated Foundry client."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def validate(self) -> bool:
|
|
19
|
+
"""Validate authentication credentials."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def get_config(self) -> Dict[str, Any]:
|
|
24
|
+
"""Return authentication configuration."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AuthError(Exception):
|
|
29
|
+
"""Base exception for authentication errors."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvalidCredentialsError(AuthError):
|
|
35
|
+
"""Raised when credentials are invalid."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MissingCredentialsError(AuthError):
|
|
41
|
+
"""Raised when required credentials are missing."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProfileNotFoundError(AuthError):
|
|
47
|
+
"""Raised when a profile cannot be found."""
|
|
48
|
+
|
|
49
|
+
pass
|
pltr/auth/manager.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication manager for getting configured Foundry clients.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from .base import AuthProvider, ProfileNotFoundError, MissingCredentialsError
|
|
8
|
+
from .storage import CredentialStorage
|
|
9
|
+
from .token import TokenAuthProvider
|
|
10
|
+
from .oauth import OAuthClientProvider
|
|
11
|
+
from ..config.profiles import ProfileManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthManager:
|
|
15
|
+
"""Manages authentication and provides configured Foundry clients."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize authentication manager."""
|
|
19
|
+
self.storage = CredentialStorage()
|
|
20
|
+
self.profile_manager = ProfileManager()
|
|
21
|
+
|
|
22
|
+
def get_client(self, profile: Optional[str] = None) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
Get an authenticated Foundry client for a profile.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
profile: Profile name (uses active profile if not specified)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Configured FoundryClient instance
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ProfileNotFoundError: If profile doesn't exist
|
|
34
|
+
MissingCredentialsError: If credentials are incomplete
|
|
35
|
+
"""
|
|
36
|
+
# Determine which profile to use
|
|
37
|
+
if not profile:
|
|
38
|
+
profile = self.profile_manager.get_active_profile()
|
|
39
|
+
if not profile:
|
|
40
|
+
raise ProfileNotFoundError(
|
|
41
|
+
"No profile specified and no default profile configured. "
|
|
42
|
+
"Run 'pltr configure configure' to set up authentication."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Get credentials for the profile
|
|
46
|
+
try:
|
|
47
|
+
credentials = self.storage.get_profile(profile)
|
|
48
|
+
except ProfileNotFoundError:
|
|
49
|
+
raise ProfileNotFoundError(
|
|
50
|
+
f"Profile '{profile}' not found. "
|
|
51
|
+
f"Run 'pltr configure configure --profile {profile}' to set it up."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Create appropriate auth provider
|
|
55
|
+
provider = self._create_provider(credentials)
|
|
56
|
+
|
|
57
|
+
# Return authenticated client
|
|
58
|
+
return provider.get_client()
|
|
59
|
+
|
|
60
|
+
def _create_provider(self, credentials: dict) -> AuthProvider:
|
|
61
|
+
"""
|
|
62
|
+
Create an auth provider from credentials.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
credentials: Dictionary containing auth configuration
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Configured AuthProvider instance
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
MissingCredentialsError: If auth type is unknown or credentials incomplete
|
|
72
|
+
"""
|
|
73
|
+
auth_type = credentials.get("auth_type")
|
|
74
|
+
host = credentials.get("host")
|
|
75
|
+
|
|
76
|
+
if not auth_type:
|
|
77
|
+
raise MissingCredentialsError(
|
|
78
|
+
"Authentication type not specified in credentials"
|
|
79
|
+
)
|
|
80
|
+
if not host:
|
|
81
|
+
raise MissingCredentialsError("Host URL not specified in credentials")
|
|
82
|
+
|
|
83
|
+
if auth_type == "token":
|
|
84
|
+
token = credentials.get("token")
|
|
85
|
+
if not token:
|
|
86
|
+
raise MissingCredentialsError("Token not found in credentials")
|
|
87
|
+
return TokenAuthProvider(token=token, host=host)
|
|
88
|
+
|
|
89
|
+
elif auth_type == "oauth":
|
|
90
|
+
client_id = credentials.get("client_id")
|
|
91
|
+
client_secret = credentials.get("client_secret")
|
|
92
|
+
if not client_id or not client_secret:
|
|
93
|
+
raise MissingCredentialsError("OAuth client credentials incomplete")
|
|
94
|
+
return OAuthClientProvider(
|
|
95
|
+
client_id=client_id,
|
|
96
|
+
client_secret=client_secret,
|
|
97
|
+
host=host,
|
|
98
|
+
scopes=credentials.get("scopes", []),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
raise MissingCredentialsError(f"Unknown authentication type: {auth_type}")
|
|
103
|
+
|
|
104
|
+
def get_current_profile(self) -> Optional[str]:
|
|
105
|
+
"""
|
|
106
|
+
Get the name of the currently active profile.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Profile name or None if no profile is configured
|
|
110
|
+
"""
|
|
111
|
+
return self.profile_manager.get_active_profile()
|
|
112
|
+
|
|
113
|
+
def validate_profile(self, profile: Optional[str] = None) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Validate that a profile's credentials work.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
profile: Profile name (uses active profile if not specified)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if credentials are valid
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
Exception if validation fails
|
|
125
|
+
"""
|
|
126
|
+
self.get_client(profile)
|
|
127
|
+
# The actual validation will happen when we try to use the client
|
|
128
|
+
# in the verify command
|
|
129
|
+
return True
|
pltr/auth/oauth.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OAuth2 client authentication for Palantir Foundry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Dict, Optional, Any
|
|
7
|
+
|
|
8
|
+
from .base import AuthProvider, InvalidCredentialsError, MissingCredentialsError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OAuthClientProvider(AuthProvider):
|
|
12
|
+
"""Authentication provider using OAuth2 client credentials."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
client_id: Optional[str] = None,
|
|
17
|
+
client_secret: Optional[str] = None,
|
|
18
|
+
host: Optional[str] = None,
|
|
19
|
+
scopes: Optional[list] = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Initialize OAuth2 client authentication.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
client_id: OAuth2 client ID
|
|
26
|
+
client_secret: OAuth2 client secret
|
|
27
|
+
host: Foundry host URL (e.g., 'https://your-stack.palantirfoundry.com')
|
|
28
|
+
scopes: List of scopes to request
|
|
29
|
+
"""
|
|
30
|
+
self.client_id = client_id or os.environ.get("FOUNDRY_CLIENT_ID")
|
|
31
|
+
self.client_secret = client_secret or os.environ.get("FOUNDRY_CLIENT_SECRET")
|
|
32
|
+
self.host = host or os.environ.get("FOUNDRY_HOST")
|
|
33
|
+
self.scopes = scopes or []
|
|
34
|
+
|
|
35
|
+
if not self.client_id:
|
|
36
|
+
raise MissingCredentialsError(
|
|
37
|
+
"Client ID is required for OAuth authentication"
|
|
38
|
+
)
|
|
39
|
+
if not self.client_secret:
|
|
40
|
+
raise MissingCredentialsError(
|
|
41
|
+
"Client secret is required for OAuth authentication"
|
|
42
|
+
)
|
|
43
|
+
if not self.host:
|
|
44
|
+
raise MissingCredentialsError(
|
|
45
|
+
"Host URL is required for OAuth authentication"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def get_client(self) -> Any:
|
|
49
|
+
"""Return an authenticated Foundry client."""
|
|
50
|
+
from foundry_sdk import FoundryClient, ConfidentialClientAuth
|
|
51
|
+
|
|
52
|
+
auth = ConfidentialClientAuth(
|
|
53
|
+
client_id=self.client_id, # type: ignore
|
|
54
|
+
client_secret=self.client_secret, # type: ignore
|
|
55
|
+
scopes=self.scopes,
|
|
56
|
+
)
|
|
57
|
+
return FoundryClient(auth=auth, hostname=self.host)
|
|
58
|
+
|
|
59
|
+
def validate(self) -> bool:
|
|
60
|
+
"""Validate authentication credentials."""
|
|
61
|
+
try:
|
|
62
|
+
self.get_client()
|
|
63
|
+
# Try to make a simple API call to validate the credentials
|
|
64
|
+
# This would need to be replaced with an actual validation call
|
|
65
|
+
# when the SDK provides one
|
|
66
|
+
return True
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise InvalidCredentialsError(f"OAuth validation failed: {e}")
|
|
69
|
+
|
|
70
|
+
def get_config(self) -> Dict[str, Any]:
|
|
71
|
+
"""Return authentication configuration."""
|
|
72
|
+
return {
|
|
73
|
+
"type": "oauth",
|
|
74
|
+
"host": self.host,
|
|
75
|
+
"client_id": self.client_id,
|
|
76
|
+
"client_secret": "***"
|
|
77
|
+
+ (
|
|
78
|
+
self.client_secret[-4:]
|
|
79
|
+
if self.client_secret and len(self.client_secret) > 4
|
|
80
|
+
else ""
|
|
81
|
+
),
|
|
82
|
+
"scopes": self.scopes,
|
|
83
|
+
}
|
pltr/auth/storage.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secure credential storage using keyring.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import keyring
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
|
|
9
|
+
from .base import ProfileNotFoundError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CredentialStorage:
|
|
13
|
+
"""Manages secure storage of authentication credentials."""
|
|
14
|
+
|
|
15
|
+
SERVICE_NAME = "pltr-cli"
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize credential storage."""
|
|
19
|
+
self.keyring = keyring
|
|
20
|
+
|
|
21
|
+
def save_profile(self, profile: str, credentials: Dict[str, Any]) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Save credentials for a profile.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
profile: Profile name
|
|
27
|
+
credentials: Dictionary of credentials to store
|
|
28
|
+
"""
|
|
29
|
+
# Convert credentials to JSON string for storage
|
|
30
|
+
credentials_json = json.dumps(credentials)
|
|
31
|
+
self.keyring.set_password(self.SERVICE_NAME, profile, credentials_json)
|
|
32
|
+
|
|
33
|
+
def get_profile(self, profile: str) -> Dict[str, Any]:
|
|
34
|
+
"""
|
|
35
|
+
Retrieve credentials for a profile.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
profile: Profile name
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary of stored credentials
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ProfileNotFoundError: If profile doesn't exist
|
|
45
|
+
"""
|
|
46
|
+
credentials_json = self.keyring.get_password(self.SERVICE_NAME, profile)
|
|
47
|
+
if not credentials_json:
|
|
48
|
+
raise ProfileNotFoundError(f"Profile '{profile}' not found")
|
|
49
|
+
|
|
50
|
+
return json.loads(credentials_json)
|
|
51
|
+
|
|
52
|
+
def delete_profile(self, profile: str) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Delete a profile's credentials.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
profile: Profile name
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
self.keyring.delete_password(self.SERVICE_NAME, profile)
|
|
61
|
+
except keyring.errors.PasswordDeleteError:
|
|
62
|
+
raise ProfileNotFoundError(f"Profile '{profile}' not found")
|
|
63
|
+
|
|
64
|
+
def list_profiles(self) -> list:
|
|
65
|
+
"""
|
|
66
|
+
List all available profiles.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of profile names
|
|
70
|
+
"""
|
|
71
|
+
# Note: keyring doesn't provide a direct way to list all usernames
|
|
72
|
+
# for a service, so we'll need to store this separately in config
|
|
73
|
+
# This is a placeholder that will be integrated with config/profiles.py
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
def profile_exists(self, profile: str) -> bool:
|
|
77
|
+
"""
|
|
78
|
+
Check if a profile exists.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
profile: Profile name
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if profile exists, False otherwise
|
|
85
|
+
"""
|
|
86
|
+
credentials = self.keyring.get_password(self.SERVICE_NAME, profile)
|
|
87
|
+
return credentials is not None
|
pltr/auth/token.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Token-based authentication for Palantir Foundry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Dict, Optional, Any
|
|
7
|
+
|
|
8
|
+
from .base import AuthProvider, InvalidCredentialsError, MissingCredentialsError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TokenAuthProvider(AuthProvider):
|
|
12
|
+
"""Authentication provider using bearer tokens."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, token: Optional[str] = None, host: Optional[str] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize token authentication.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
token: Bearer token for authentication
|
|
20
|
+
host: Foundry host URL (e.g., 'https://your-stack.palantirfoundry.com')
|
|
21
|
+
"""
|
|
22
|
+
self.token = token or os.environ.get("FOUNDRY_TOKEN")
|
|
23
|
+
self.host = host or os.environ.get("FOUNDRY_HOST")
|
|
24
|
+
|
|
25
|
+
if not self.token:
|
|
26
|
+
raise MissingCredentialsError("Token is required for authentication")
|
|
27
|
+
if not self.host:
|
|
28
|
+
raise MissingCredentialsError("Host URL is required for authentication")
|
|
29
|
+
|
|
30
|
+
def get_client(self) -> Any:
|
|
31
|
+
"""Return an authenticated Foundry client."""
|
|
32
|
+
from foundry_sdk import FoundryClient, UserTokenAuth
|
|
33
|
+
|
|
34
|
+
auth = UserTokenAuth(token=self.token) # type: ignore
|
|
35
|
+
return FoundryClient(auth=auth, hostname=self.host)
|
|
36
|
+
|
|
37
|
+
def validate(self) -> bool:
|
|
38
|
+
"""Validate authentication credentials."""
|
|
39
|
+
try:
|
|
40
|
+
self.get_client()
|
|
41
|
+
# Try to make a simple API call to validate the token
|
|
42
|
+
# This would need to be replaced with an actual validation call
|
|
43
|
+
# when the SDK provides one
|
|
44
|
+
return True
|
|
45
|
+
except Exception as e:
|
|
46
|
+
raise InvalidCredentialsError(f"Token validation failed: {e}")
|
|
47
|
+
|
|
48
|
+
def get_config(self) -> Dict[str, Any]:
|
|
49
|
+
"""Return authentication configuration."""
|
|
50
|
+
return {
|
|
51
|
+
"type": "token",
|
|
52
|
+
"host": self.host,
|
|
53
|
+
"token": "***"
|
|
54
|
+
+ (self.token[-4:] if self.token and len(self.token) > 4 else ""),
|
|
55
|
+
}
|
pltr/cli.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI entry point for pltr.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing_extensions import Annotated
|
|
7
|
+
|
|
8
|
+
from pltr import __version__
|
|
9
|
+
from pltr.commands import configure, verify, dataset
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="pltr",
|
|
13
|
+
help="Command-line interface for Palantir Foundry APIs",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Add subcommands
|
|
18
|
+
app.add_typer(configure.app, name="configure", help="Manage authentication profiles")
|
|
19
|
+
app.add_typer(verify.app, name="verify", help="Verify authentication")
|
|
20
|
+
app.add_typer(dataset.app, name="dataset", help="Manage datasets")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def version_callback(value: bool):
|
|
24
|
+
"""Show version and exit."""
|
|
25
|
+
if value:
|
|
26
|
+
typer.echo(f"pltr {__version__}")
|
|
27
|
+
raise typer.Exit()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.callback()
|
|
31
|
+
def main(
|
|
32
|
+
version: Annotated[
|
|
33
|
+
bool, typer.Option("--version", callback=version_callback, help="Show version")
|
|
34
|
+
] = False,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Command-line interface for Palantir Foundry APIs.
|
|
38
|
+
|
|
39
|
+
Built on top of the official foundry-platform-sdk, pltr provides
|
|
40
|
+
intuitive commands for dataset management, ontology operations,
|
|
41
|
+
SQL queries, and more.
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.command()
|
|
47
|
+
def hello():
|
|
48
|
+
"""Test command to verify CLI is working."""
|
|
49
|
+
typer.echo("Hello from pltr! 🚀")
|
|
50
|
+
typer.echo("CLI is working correctly.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands for pltr."""
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration commands for pltr CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.prompt import Prompt, Confirm
|
|
9
|
+
|
|
10
|
+
from ..auth.storage import CredentialStorage
|
|
11
|
+
from ..auth.base import ProfileNotFoundError
|
|
12
|
+
from ..config.profiles import ProfileManager
|
|
13
|
+
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def configure(
|
|
20
|
+
profile: Optional[str] = typer.Option(
|
|
21
|
+
"default", "--profile", "-p", help="Profile name"
|
|
22
|
+
),
|
|
23
|
+
auth_type: Optional[str] = typer.Option(
|
|
24
|
+
None, "--auth-type", help="Authentication type (token or oauth)"
|
|
25
|
+
),
|
|
26
|
+
host: Optional[str] = typer.Option(None, "--host", help="Foundry host URL"),
|
|
27
|
+
token: Optional[str] = typer.Option(
|
|
28
|
+
None, "--token", help="Bearer token (for token auth)"
|
|
29
|
+
),
|
|
30
|
+
client_id: Optional[str] = typer.Option(
|
|
31
|
+
None, "--client-id", help="OAuth client ID"
|
|
32
|
+
),
|
|
33
|
+
client_secret: Optional[str] = typer.Option(
|
|
34
|
+
None, "--client-secret", help="OAuth client secret"
|
|
35
|
+
),
|
|
36
|
+
):
|
|
37
|
+
"""Configure authentication for Palantir Foundry."""
|
|
38
|
+
storage = CredentialStorage()
|
|
39
|
+
profile_manager = ProfileManager()
|
|
40
|
+
|
|
41
|
+
# Ensure profile is not None
|
|
42
|
+
if not profile:
|
|
43
|
+
profile = "default"
|
|
44
|
+
|
|
45
|
+
# Check if profile already exists
|
|
46
|
+
if storage.profile_exists(profile):
|
|
47
|
+
if not Confirm.ask(f"Profile '{profile}' already exists. Overwrite?"):
|
|
48
|
+
console.print("[yellow]Configuration cancelled.[/yellow]")
|
|
49
|
+
raise typer.Exit()
|
|
50
|
+
|
|
51
|
+
# Interactive mode if no auth type specified
|
|
52
|
+
if not auth_type:
|
|
53
|
+
auth_type = Prompt.ask(
|
|
54
|
+
"Authentication type", choices=["token", "oauth"], default="token"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Get host URL
|
|
58
|
+
if not host:
|
|
59
|
+
host = Prompt.ask(
|
|
60
|
+
"Foundry host URL", default="https://your-stack.palantirfoundry.com"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
credentials = {"auth_type": auth_type, "host": host}
|
|
64
|
+
|
|
65
|
+
if auth_type == "token":
|
|
66
|
+
# Token authentication
|
|
67
|
+
if not token:
|
|
68
|
+
token = Prompt.ask("Bearer token", password=True)
|
|
69
|
+
credentials["token"] = token
|
|
70
|
+
|
|
71
|
+
elif auth_type == "oauth":
|
|
72
|
+
# OAuth authentication
|
|
73
|
+
if not client_id:
|
|
74
|
+
client_id = Prompt.ask("OAuth client ID")
|
|
75
|
+
if not client_secret:
|
|
76
|
+
client_secret = Prompt.ask("OAuth client secret", password=True)
|
|
77
|
+
|
|
78
|
+
credentials["client_id"] = client_id
|
|
79
|
+
credentials["client_secret"] = client_secret
|
|
80
|
+
|
|
81
|
+
# Save credentials
|
|
82
|
+
storage.save_profile(profile, credentials)
|
|
83
|
+
profile_manager.add_profile(profile)
|
|
84
|
+
|
|
85
|
+
# Set as default if it's the first profile
|
|
86
|
+
if len(profile_manager.list_profiles()) == 1:
|
|
87
|
+
profile_manager.set_default(profile)
|
|
88
|
+
|
|
89
|
+
console.print(f"[green]✓[/green] Profile '{profile}' configured successfully")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command()
|
|
93
|
+
def list_profiles():
|
|
94
|
+
"""List all configured profiles."""
|
|
95
|
+
profile_manager = ProfileManager()
|
|
96
|
+
profiles = profile_manager.list_profiles()
|
|
97
|
+
default = profile_manager.get_default()
|
|
98
|
+
|
|
99
|
+
if not profiles:
|
|
100
|
+
console.print("[yellow]No profiles configured.[/yellow]")
|
|
101
|
+
console.print("Run 'pltr configure' to set up your first profile.")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
console.print("[bold]Configured profiles:[/bold]")
|
|
105
|
+
for profile in profiles:
|
|
106
|
+
if profile == default:
|
|
107
|
+
console.print(f" * {profile} [green](default)[/green]")
|
|
108
|
+
else:
|
|
109
|
+
console.print(f" {profile}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command()
|
|
113
|
+
def set_default(
|
|
114
|
+
profile: str = typer.Argument(..., help="Profile name to set as default"),
|
|
115
|
+
):
|
|
116
|
+
"""Set a profile as the default."""
|
|
117
|
+
profile_manager = ProfileManager()
|
|
118
|
+
|
|
119
|
+
if profile not in profile_manager.list_profiles():
|
|
120
|
+
console.print(f"[red]Error:[/red] Profile '{profile}' not found")
|
|
121
|
+
raise typer.Exit(1)
|
|
122
|
+
|
|
123
|
+
profile_manager.set_default(profile)
|
|
124
|
+
console.print(f"[green]✓[/green] Profile '{profile}' set as default")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command()
|
|
128
|
+
def delete(
|
|
129
|
+
profile: str = typer.Argument(..., help="Profile name to delete"),
|
|
130
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
131
|
+
):
|
|
132
|
+
"""Delete a profile."""
|
|
133
|
+
storage = CredentialStorage()
|
|
134
|
+
profile_manager = ProfileManager()
|
|
135
|
+
|
|
136
|
+
if profile not in profile_manager.list_profiles():
|
|
137
|
+
console.print(f"[red]Error:[/red] Profile '{profile}' not found")
|
|
138
|
+
raise typer.Exit(1)
|
|
139
|
+
|
|
140
|
+
if not force:
|
|
141
|
+
if not Confirm.ask(f"Delete profile '{profile}'?"):
|
|
142
|
+
console.print("[yellow]Deletion cancelled.[/yellow]")
|
|
143
|
+
raise typer.Exit()
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
storage.delete_profile(profile)
|
|
147
|
+
profile_manager.remove_profile(profile)
|
|
148
|
+
console.print(f"[green]✓[/green] Profile '{profile}' deleted")
|
|
149
|
+
except ProfileNotFoundError:
|
|
150
|
+
console.print(f"[red]Error:[/red] Could not delete profile '{profile}'")
|
|
151
|
+
raise typer.Exit(1)
|