opsmith-cli 0.1.2a0__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.
- opsmith/__init__.py +0 -0
- opsmith/agent.py +243 -0
- opsmith/cloud_providers/__init__.py +10 -0
- opsmith/cloud_providers/aws.py +46 -0
- opsmith/cloud_providers/base.py +57 -0
- opsmith/cloud_providers/gcp.py +67 -0
- opsmith/constants.py +175 -0
- opsmith/deployer.py +236 -0
- opsmith/git_repo.py +66 -0
- opsmith/main.py +269 -0
- opsmith/prompts.py +76 -0
- opsmith/queries/tree-sitter-languages/README.md +7 -0
- opsmith/queries/tree-sitter-languages/arduino-tags.scm +5 -0
- opsmith/queries/tree-sitter-languages/c-tags.scm +9 -0
- opsmith/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- opsmith/queries/tree-sitter-languages/chatito-tags.scm +16 -0
- opsmith/queries/tree-sitter-languages/commonlisp-tags.scm +122 -0
- opsmith/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- opsmith/queries/tree-sitter-languages/csharp-tags.scm +26 -0
- opsmith/queries/tree-sitter-languages/d-tags.scm +26 -0
- opsmith/queries/tree-sitter-languages/dart-tags.scm +92 -0
- opsmith/queries/tree-sitter-languages/elisp-tags.scm +5 -0
- opsmith/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- opsmith/queries/tree-sitter-languages/elm-tags.scm +19 -0
- opsmith/queries/tree-sitter-languages/gleam-tags.scm +41 -0
- opsmith/queries/tree-sitter-languages/go-tags.scm +42 -0
- opsmith/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- opsmith/queries/tree-sitter-languages/java-tags.scm +20 -0
- opsmith/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- opsmith/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- opsmith/queries/tree-sitter-languages/lua-tags.scm +34 -0
- opsmith/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- opsmith/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- opsmith/queries/tree-sitter-languages/php-tags.scm +26 -0
- opsmith/queries/tree-sitter-languages/pony-tags.scm +39 -0
- opsmith/queries/tree-sitter-languages/properties-tags.scm +5 -0
- opsmith/queries/tree-sitter-languages/python-tags.scm +14 -0
- opsmith/queries/tree-sitter-languages/ql-tags.scm +26 -0
- opsmith/queries/tree-sitter-languages/r-tags.scm +21 -0
- opsmith/queries/tree-sitter-languages/racket-tags.scm +12 -0
- opsmith/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- opsmith/queries/tree-sitter-languages/rust-tags.scm +60 -0
- opsmith/queries/tree-sitter-languages/scala-tags.scm +65 -0
- opsmith/queries/tree-sitter-languages/solidity-tags.scm +43 -0
- opsmith/queries/tree-sitter-languages/swift-tags.scm +51 -0
- opsmith/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- opsmith/queries/tree-sitter-languages/udev-tags.scm +20 -0
- opsmith/repo_map.py +572 -0
- opsmith/settings.py +26 -0
- opsmith/spinner.py +221 -0
- opsmith_cli-0.1.2a0.dist-info/METADATA +31 -0
- opsmith_cli-0.1.2a0.dist-info/RECORD +55 -0
- opsmith_cli-0.1.2a0.dist-info/WHEEL +4 -0
- opsmith_cli-0.1.2a0.dist-info/entry_points.txt +2 -0
- opsmith_cli-0.1.2a0.dist-info/licenses/LICENSE.txt +674 -0
opsmith/__init__.py
ADDED
|
File without changes
|
opsmith/agent.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
import uuid # Added for unique naming
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from pydantic_ai import Agent, ModelRetry, RunContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelConfig(BaseModel):
|
|
16
|
+
provider: Literal["openai", "anthropic", "google-gla"]
|
|
17
|
+
model: str
|
|
18
|
+
api_key_prefix: str
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def model_name_abs(self):
|
|
22
|
+
return f"{self.provider}:{self.model}"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def api_key_env_var(self):
|
|
26
|
+
return f"{self.api_key_prefix}_API_KEY"
|
|
27
|
+
|
|
28
|
+
def ensure_auth(self, api_key: str):
|
|
29
|
+
os.environ[self.api_key_env_var] = api_key.strip()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
AVAILABLE_MODELS = [
|
|
33
|
+
ModelConfig(provider="openai", model="gpt-4.1", api_key_prefix="OPENAI"),
|
|
34
|
+
ModelConfig(
|
|
35
|
+
provider="anthropic", model="claude-3-7-sonnet-20250219", api_key_prefix="ANTHROPIC"
|
|
36
|
+
),
|
|
37
|
+
ModelConfig(provider="anthropic", model="claude-sonnet-4-20250514", api_key_prefix="ANTHROPIC"),
|
|
38
|
+
ModelConfig(
|
|
39
|
+
provider="google-gla", model="gemini-2.5-pro-preview-05-06", api_key_prefix="GEMINI"
|
|
40
|
+
),
|
|
41
|
+
ModelConfig(
|
|
42
|
+
provider="google-gla", model="gemini-2.5-pro-preview-06-05", api_key_prefix="GEMINI"
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
AVAILABLE_MODELS_XREF = {model.model_name_abs: model for model in AVAILABLE_MODELS}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class AgentDeps:
|
|
51
|
+
src_dir: Path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_duplicate_tool_call(ctx: RunContext[AgentDeps], tool_name: str) -> bool:
|
|
55
|
+
""""""
|
|
56
|
+
tool_calls = set()
|
|
57
|
+
message_parts = [item for message in ctx.messages for item in message.parts]
|
|
58
|
+
for part in message_parts:
|
|
59
|
+
if part.part_kind == "tool-call" and part.tool_name == tool_name:
|
|
60
|
+
if isinstance(part.args, dict):
|
|
61
|
+
tool_args = json.dumps(part.args, sort_keys=True)
|
|
62
|
+
else:
|
|
63
|
+
tool_args = part.args
|
|
64
|
+
if tool_args in tool_calls:
|
|
65
|
+
return True
|
|
66
|
+
else:
|
|
67
|
+
# logger.debug(f"Tool {tool_def.name} called with arguments: {tool_args}")
|
|
68
|
+
tool_calls.add(tool_args)
|
|
69
|
+
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_agent(model_config: ModelConfig, instrument: bool = False) -> Agent:
|
|
74
|
+
agent = Agent(
|
|
75
|
+
model=model_config.model_name_abs,
|
|
76
|
+
# instructions=SYSTEM_PROMPT,
|
|
77
|
+
instrument=instrument,
|
|
78
|
+
deps_type=AgentDeps,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@agent.tool(retries=5)
|
|
82
|
+
def read_file_content(ctx: RunContext[AgentDeps], filenames: List[str]) -> List[str]:
|
|
83
|
+
"""
|
|
84
|
+
Reads and returns the content of specified files from the repository.
|
|
85
|
+
Use this to understand file structures, dependencies, or specific configurations.
|
|
86
|
+
Provide the relative file paths from the repository root.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
ctx: The run context object containing the dependencies of the agent.
|
|
90
|
+
filenames: A list of relative paths to the files from the repository root.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A list of strings, where each string is the content of the corresponding file.
|
|
94
|
+
The order of contents in the list matches the order of filenames in the input.
|
|
95
|
+
"""
|
|
96
|
+
if is_duplicate_tool_call(ctx, "read_file_content"):
|
|
97
|
+
raise ModelRetry(
|
|
98
|
+
"The tool 'read_file_content' has already been called with the exact same list of "
|
|
99
|
+
"files in this conversation."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
contents = []
|
|
103
|
+
for filename in filenames:
|
|
104
|
+
if Path(filename).is_absolute():
|
|
105
|
+
raise ModelRetry(
|
|
106
|
+
f"Absolute file paths are not allowed for '{filename}'. Please provide a"
|
|
107
|
+
" relative path."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
absolute_file_path = ctx.deps.src_dir.joinpath(filename).resolve()
|
|
111
|
+
|
|
112
|
+
if not str(absolute_file_path).startswith(str(ctx.deps.src_dir)):
|
|
113
|
+
raise ModelRetry(
|
|
114
|
+
f"Access denied. File '{filename}' is outside the repository root."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if not absolute_file_path.is_file():
|
|
118
|
+
raise ModelRetry(f"File '{filename}' not found or is not a regular file.")
|
|
119
|
+
|
|
120
|
+
with open(absolute_file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
121
|
+
content = f.read()
|
|
122
|
+
contents.append(content)
|
|
123
|
+
return contents
|
|
124
|
+
|
|
125
|
+
@agent.tool(retries=3)
|
|
126
|
+
def build_dockerfile(
|
|
127
|
+
ctx: RunContext[AgentDeps],
|
|
128
|
+
dockerfile_content: str,
|
|
129
|
+
) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Writes the provided Dockerfile content to a temporary file, then attempts to build a Docker image
|
|
132
|
+
using that Dockerfile and the repository root as the build context.
|
|
133
|
+
If the build is successful, it attempts to run the built image with a short timeout.
|
|
134
|
+
Returns the combined output (stdout and stderr) of the 'docker build' and 'docker run' commands.
|
|
135
|
+
|
|
136
|
+
Use this tool to validate a generated Dockerfile.
|
|
137
|
+
- If the build fails, analyze the build output, revise the Dockerfile content, and call this tool again.
|
|
138
|
+
- If the build succeeds but the image run fails due to issues like missing packages or invalid commands
|
|
139
|
+
(ignoring errors related to missing environment variables or connection issues),
|
|
140
|
+
analyze the run output, revise the Dockerfile, and call this tool again.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
ctx: The run context object.
|
|
144
|
+
dockerfile_content: The string content of the Dockerfile to be built and run.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
A string containing the combined stdout and stderr from the 'docker build'
|
|
148
|
+
and 'docker run' commands. If 'docker' command is not found, an error message is returned.
|
|
149
|
+
"""
|
|
150
|
+
repo_root = ctx.deps.src_dir.resolve()
|
|
151
|
+
image_tag = f"opsmith-build-test-{uuid.uuid4()}"
|
|
152
|
+
build_output_str = ""
|
|
153
|
+
|
|
154
|
+
# Check if docker command is available
|
|
155
|
+
if not shutil.which("docker"):
|
|
156
|
+
raise ModelRetry(
|
|
157
|
+
"Error: Docker command not found. Please ensure Docker is installed and in your"
|
|
158
|
+
" PATH."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# Create temporary directory for Dockerfile
|
|
163
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
164
|
+
temp_dockerfile = Path(temp_dir) / "Dockerfile"
|
|
165
|
+
# Write Dockerfile content
|
|
166
|
+
with open(temp_dockerfile, "w", encoding="utf-8") as f:
|
|
167
|
+
f.write(dockerfile_content)
|
|
168
|
+
|
|
169
|
+
# Execute docker build command
|
|
170
|
+
build_process = subprocess.run(
|
|
171
|
+
[
|
|
172
|
+
"docker",
|
|
173
|
+
"build",
|
|
174
|
+
"-f",
|
|
175
|
+
str(temp_dockerfile),
|
|
176
|
+
"-t",
|
|
177
|
+
image_tag,
|
|
178
|
+
str(repo_root),
|
|
179
|
+
],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
)
|
|
183
|
+
build_output_str = (
|
|
184
|
+
f"Build Output (stdout):\n{build_process.stdout}\nBuild Output"
|
|
185
|
+
f" (stderr):\n{build_process.stderr}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if build_process.returncode != 0:
|
|
189
|
+
# Build failed, instruct LLM to retry with build output
|
|
190
|
+
raise ModelRetry(
|
|
191
|
+
"Dockerfile build failed. Analyze the following output and revise the"
|
|
192
|
+
f" Dockerfile:\n{build_output_str}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Build successful, now try to run the image
|
|
196
|
+
# Using --rm to automatically remove the container when it exits.
|
|
197
|
+
run_process = subprocess.run(
|
|
198
|
+
[
|
|
199
|
+
"docker",
|
|
200
|
+
"run",
|
|
201
|
+
"--rm",
|
|
202
|
+
image_tag,
|
|
203
|
+
], # Removed explicit container name as --rm handles it
|
|
204
|
+
capture_output=True,
|
|
205
|
+
text=True,
|
|
206
|
+
timeout=30, # Timeout for the run command to prevent indefinite hangs
|
|
207
|
+
)
|
|
208
|
+
run_output_str = (
|
|
209
|
+
f"Run Output (stdout):\n{run_process.stdout}\nRun Output"
|
|
210
|
+
f" (stderr):\n{run_process.stderr}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if run_process.returncode != 0:
|
|
214
|
+
# Run failed. Raise ModelRetry with the run output for the LLM to analyze.
|
|
215
|
+
# The LLM will decide if the error is due to the Dockerfile or an ignorable runtime issue.
|
|
216
|
+
raise ModelRetry(
|
|
217
|
+
"Dockerfile built successfully, but running the image failed. Analyze the"
|
|
218
|
+
" following run output to determine if the Dockerfile needs revision (e.g.,"
|
|
219
|
+
" for missing packages, command errors) or if the error is ignorable (e.g.,"
|
|
220
|
+
" missing environment variables, connection issues).\n\nRun"
|
|
221
|
+
f" Output:\n{run_output_str}\n\nOriginal Build Output was:\n{build_output_str}"
|
|
222
|
+
)
|
|
223
|
+
finally:
|
|
224
|
+
# Clean up image
|
|
225
|
+
if image_tag: # ensure image_tag was set before trying to remove
|
|
226
|
+
cleanup_image_process = subprocess.run(
|
|
227
|
+
["docker", "rmi", "-f", image_tag], capture_output=True, text=True
|
|
228
|
+
)
|
|
229
|
+
if (
|
|
230
|
+
cleanup_image_process.returncode != 0
|
|
231
|
+
and "no such image" not in cleanup_image_process.stderr.lower()
|
|
232
|
+
):
|
|
233
|
+
# Log or handle image removal failure if necessary, but don't fail the tool for this
|
|
234
|
+
# This could happen if the build failed before the image was tagged,
|
|
235
|
+
# or if another process removed it.
|
|
236
|
+
print(
|
|
237
|
+
f"Warning: Failed to remove Docker image {image_tag}:"
|
|
238
|
+
f" {cleanup_image_process.stderr.strip()}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return f"{build_output_str}\n\n{run_output_str}".strip()
|
|
242
|
+
|
|
243
|
+
return agent
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Dict, Type
|
|
2
|
+
|
|
3
|
+
from .aws import AWSProvider
|
|
4
|
+
from .base import BaseCloudProvider, CloudProviderEnum
|
|
5
|
+
from .gcp import GCPProvider
|
|
6
|
+
|
|
7
|
+
CLOUD_PROVIDER_REGISTRY: Dict[CloudProviderEnum, Type[BaseCloudProvider]] = {
|
|
8
|
+
CloudProviderEnum.AWS: AWSProvider,
|
|
9
|
+
CloudProviderEnum.GCP: GCPProvider,
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
3
|
+
|
|
4
|
+
from opsmith.cloud_providers.base import (
|
|
5
|
+
AWSCloudDetail,
|
|
6
|
+
BaseCloudProvider,
|
|
7
|
+
CloudCredentialsError,
|
|
8
|
+
CloudProviderEnum,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AWSProvider(BaseCloudProvider):
|
|
13
|
+
"""AWS cloud provider implementation."""
|
|
14
|
+
|
|
15
|
+
def get_account_details(self) -> AWSCloudDetail:
|
|
16
|
+
"""
|
|
17
|
+
Retrieves structured AWS account details.
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
sts_client = boto3.client("sts")
|
|
21
|
+
identity = sts_client.get_caller_identity()
|
|
22
|
+
account_id = identity.get("Account")
|
|
23
|
+
if not account_id:
|
|
24
|
+
raise CloudCredentialsError(
|
|
25
|
+
message=(
|
|
26
|
+
"AWS account ID could not be determined. This might indicate an issue with"
|
|
27
|
+
" the credentials or permissions."
|
|
28
|
+
),
|
|
29
|
+
help_url="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html",
|
|
30
|
+
)
|
|
31
|
+
return AWSCloudDetail(name=CloudProviderEnum.AWS.name, account_id=account_id)
|
|
32
|
+
except (NoCredentialsError, ClientError) as e:
|
|
33
|
+
raise CloudCredentialsError(
|
|
34
|
+
message=f"AWS credentials error: {e}",
|
|
35
|
+
help_url=(
|
|
36
|
+
"https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html"
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
# Catching other unexpected exceptions during AWS interaction
|
|
41
|
+
raise CloudCredentialsError(
|
|
42
|
+
message=f"An unexpected error occurred while fetching AWS account details: {e}",
|
|
43
|
+
help_url=(
|
|
44
|
+
"https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html"
|
|
45
|
+
),
|
|
46
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import enum
|
|
3
|
+
from typing import Literal, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CloudProviderEnum(enum.Enum):
|
|
9
|
+
AWS = "AWS"
|
|
10
|
+
GCP = "GCP"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AWSCloudDetail(BaseModel):
|
|
14
|
+
name: Literal[CloudProviderEnum.AWS.name] = Field(
|
|
15
|
+
default=CloudProviderEnum.AWS.name, description="Provider name, 'aws'"
|
|
16
|
+
)
|
|
17
|
+
account_id: str = Field(..., description="AWS Account ID.")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GCPCloudDetail(BaseModel):
|
|
21
|
+
name: Literal[CloudProviderEnum.GCP.name] = Field(
|
|
22
|
+
default=CloudProviderEnum.GCP.name, description="Provider name, 'gcp'"
|
|
23
|
+
)
|
|
24
|
+
project_id: str = Field(..., description="GCP Project ID.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CloudProviderDetail = Union[AWSCloudDetail, GCPCloudDetail]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CloudCredentialsError(Exception):
|
|
31
|
+
"""Custom exception for cloud credential errors."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message: str, help_url: str):
|
|
34
|
+
self.message = message
|
|
35
|
+
self.help_url = help_url
|
|
36
|
+
super().__init__(
|
|
37
|
+
f"{self.message}\nPlease ensure your credentials are set up correctly. For more"
|
|
38
|
+
f" information, visit: {self.help_url}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BaseCloudProvider(abc.ABC):
|
|
43
|
+
"""Abstract base class for cloud providers."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
"""
|
|
47
|
+
Initializes the cloud provider.
|
|
48
|
+
Subclasses should implement specific authentication and setup.
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abc.abstractmethod
|
|
53
|
+
def get_account_details(self) -> CloudProviderDetail:
|
|
54
|
+
"""
|
|
55
|
+
Retrieves structured account details for the cloud provider.
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import google.auth
|
|
2
|
+
import inquirer
|
|
3
|
+
from google.auth.credentials import Credentials
|
|
4
|
+
from google.auth.exceptions import DefaultCredentialsError
|
|
5
|
+
|
|
6
|
+
from opsmith.cloud_providers import CloudProviderEnum
|
|
7
|
+
from opsmith.cloud_providers.base import (
|
|
8
|
+
BaseCloudProvider,
|
|
9
|
+
CloudCredentialsError,
|
|
10
|
+
GCPCloudDetail,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GCPProvider(BaseCloudProvider):
|
|
15
|
+
"""GCP cloud provider implementation."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
self._credentials = None
|
|
20
|
+
|
|
21
|
+
def get_credentials(self) -> Credentials:
|
|
22
|
+
"""
|
|
23
|
+
Provides the functionality to retrieve cached credentials or obtain default
|
|
24
|
+
Google credentials if none are available.
|
|
25
|
+
|
|
26
|
+
:raises google.auth.exceptions.GoogleAuthError: If authentication fails or
|
|
27
|
+
valid credentials could not be obtained.
|
|
28
|
+
:rtype: google.auth.credentials.Credentials
|
|
29
|
+
:return: Returns the cached credentials if available, otherwise retrieves
|
|
30
|
+
and returns the default credentials via Google's authentication library.
|
|
31
|
+
"""
|
|
32
|
+
if not self._credentials:
|
|
33
|
+
self._credentials, _ = google.auth.default()
|
|
34
|
+
return self._credentials
|
|
35
|
+
|
|
36
|
+
def get_account_details(self) -> GCPCloudDetail:
|
|
37
|
+
"""
|
|
38
|
+
Retrieves structured GCP account details by listing available projects
|
|
39
|
+
and prompting the user for selection.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
self.get_credentials()
|
|
43
|
+
|
|
44
|
+
questions = [
|
|
45
|
+
inquirer.Text(
|
|
46
|
+
name="project_id",
|
|
47
|
+
message="Enter the GCP project you want to use",
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
answers = inquirer.prompt(questions)
|
|
52
|
+
if not answers or not answers.get("project_id"):
|
|
53
|
+
raise ValueError("GCP project selection is required. Aborting.")
|
|
54
|
+
|
|
55
|
+
selected_project_id = answers["project_id"]
|
|
56
|
+
return GCPCloudDetail(name=CloudProviderEnum.GCP.name, project_id=selected_project_id)
|
|
57
|
+
|
|
58
|
+
except DefaultCredentialsError as e:
|
|
59
|
+
raise CloudCredentialsError(
|
|
60
|
+
message=f"GCP Application Default Credentials error: {e}",
|
|
61
|
+
help_url="https://cloud.google.com/docs/authentication/provide-credentials-adc",
|
|
62
|
+
)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise CloudCredentialsError(
|
|
65
|
+
message=f"An unexpected error occurred while fetching GCP project list: {e}",
|
|
66
|
+
help_url="https://cloud.google.com/docs/authentication/provide-credentials-adc",
|
|
67
|
+
)
|
opsmith/constants.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
ROOT_IMPORTANT_FILES = [
|
|
2
|
+
# Version Control
|
|
3
|
+
".gitignore",
|
|
4
|
+
".gitattributes",
|
|
5
|
+
# Documentation
|
|
6
|
+
"README",
|
|
7
|
+
"README.md",
|
|
8
|
+
"README.txt",
|
|
9
|
+
"README.rst",
|
|
10
|
+
"CONTRIBUTING",
|
|
11
|
+
"CONTRIBUTING.md",
|
|
12
|
+
"CONTRIBUTING.txt",
|
|
13
|
+
"CONTRIBUTING.rst",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"LICENSE.md",
|
|
16
|
+
"LICENSE.txt",
|
|
17
|
+
"CHANGELOG",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"CHANGELOG.txt",
|
|
20
|
+
"CHANGELOG.rst",
|
|
21
|
+
"SECURITY",
|
|
22
|
+
"SECURITY.md",
|
|
23
|
+
"SECURITY.txt",
|
|
24
|
+
"CODEOWNERS",
|
|
25
|
+
# Package Management and Dependencies
|
|
26
|
+
"requirements.txt",
|
|
27
|
+
"Pipfile",
|
|
28
|
+
"Pipfile.lock",
|
|
29
|
+
"pyproject.toml",
|
|
30
|
+
"setup.py",
|
|
31
|
+
"setup.cfg",
|
|
32
|
+
"package.json",
|
|
33
|
+
"package-lock.json",
|
|
34
|
+
"yarn.lock",
|
|
35
|
+
"npm-shrinkwrap.json",
|
|
36
|
+
"Gemfile",
|
|
37
|
+
"Gemfile.lock",
|
|
38
|
+
"composer.json",
|
|
39
|
+
"composer.lock",
|
|
40
|
+
"pom.xml",
|
|
41
|
+
"build.gradle",
|
|
42
|
+
"build.gradle.kts",
|
|
43
|
+
"build.sbt",
|
|
44
|
+
"go.mod",
|
|
45
|
+
"go.sum",
|
|
46
|
+
"Cargo.toml",
|
|
47
|
+
"Cargo.lock",
|
|
48
|
+
"mix.exs",
|
|
49
|
+
"rebar.config",
|
|
50
|
+
"project.clj",
|
|
51
|
+
"Podfile",
|
|
52
|
+
"Cartfile",
|
|
53
|
+
"dub.json",
|
|
54
|
+
"dub.sdl",
|
|
55
|
+
# Configuration and Settings
|
|
56
|
+
".env",
|
|
57
|
+
".env.example",
|
|
58
|
+
".editorconfig",
|
|
59
|
+
"tsconfig.json",
|
|
60
|
+
"jsconfig.json",
|
|
61
|
+
".babelrc",
|
|
62
|
+
"babel.config.js",
|
|
63
|
+
".eslintrc",
|
|
64
|
+
".eslintignore",
|
|
65
|
+
".prettierrc",
|
|
66
|
+
".stylelintrc",
|
|
67
|
+
"tslint.json",
|
|
68
|
+
".pylintrc",
|
|
69
|
+
".flake8",
|
|
70
|
+
".rubocop.yml",
|
|
71
|
+
".scalafmt.conf",
|
|
72
|
+
".dockerignore",
|
|
73
|
+
".gitpod.yml",
|
|
74
|
+
"sonar-project.properties",
|
|
75
|
+
"renovate.json",
|
|
76
|
+
"dependabot.yml",
|
|
77
|
+
".pre-commit-config.yaml",
|
|
78
|
+
"mypy.ini",
|
|
79
|
+
"tox.ini",
|
|
80
|
+
".yamllint",
|
|
81
|
+
"pyrightconfig.json",
|
|
82
|
+
# Build and Compilation
|
|
83
|
+
"webpack.config.js",
|
|
84
|
+
"rollup.config.js",
|
|
85
|
+
"parcel.config.js",
|
|
86
|
+
"gulpfile.js",
|
|
87
|
+
"Gruntfile.js",
|
|
88
|
+
"build.xml",
|
|
89
|
+
"build.boot",
|
|
90
|
+
"project.json",
|
|
91
|
+
"build.cake",
|
|
92
|
+
"MANIFEST.in",
|
|
93
|
+
# Testing
|
|
94
|
+
"pytest.ini",
|
|
95
|
+
"phpunit.xml",
|
|
96
|
+
"karma.conf.js",
|
|
97
|
+
"jest.config.js",
|
|
98
|
+
"cypress.json",
|
|
99
|
+
".nycrc",
|
|
100
|
+
".nycrc.json",
|
|
101
|
+
# CI/CD
|
|
102
|
+
".travis.yml",
|
|
103
|
+
".gitlab-ci.yml",
|
|
104
|
+
"Jenkinsfile",
|
|
105
|
+
"azure-pipelines.yml",
|
|
106
|
+
"bitbucket-pipelines.yml",
|
|
107
|
+
"appveyor.yml",
|
|
108
|
+
"circle.yml",
|
|
109
|
+
".circleci/config.yml",
|
|
110
|
+
".github/dependabot.yml",
|
|
111
|
+
"codecov.yml",
|
|
112
|
+
".coveragerc",
|
|
113
|
+
# Docker and Containers
|
|
114
|
+
"Dockerfile",
|
|
115
|
+
"docker-compose.yml",
|
|
116
|
+
"docker-compose.override.yml",
|
|
117
|
+
# Cloud and Serverless
|
|
118
|
+
"serverless.yml",
|
|
119
|
+
"firebase.json",
|
|
120
|
+
"now.json",
|
|
121
|
+
"netlify.toml",
|
|
122
|
+
"vercel.json",
|
|
123
|
+
"app.yaml",
|
|
124
|
+
"terraform.tf",
|
|
125
|
+
"main.tf",
|
|
126
|
+
"cloudformation.yaml",
|
|
127
|
+
"cloudformation.json",
|
|
128
|
+
"ansible.cfg",
|
|
129
|
+
"kubernetes.yaml",
|
|
130
|
+
"k8s.yaml",
|
|
131
|
+
# Database
|
|
132
|
+
"schema.sql",
|
|
133
|
+
"liquibase.properties",
|
|
134
|
+
"flyway.conf",
|
|
135
|
+
# Framework-specific
|
|
136
|
+
"next.config.js",
|
|
137
|
+
"nuxt.config.js",
|
|
138
|
+
"vue.config.js",
|
|
139
|
+
"angular.json",
|
|
140
|
+
"gatsby-config.js",
|
|
141
|
+
"gridsome.config.js",
|
|
142
|
+
# API Documentation
|
|
143
|
+
"swagger.yaml",
|
|
144
|
+
"swagger.json",
|
|
145
|
+
"openapi.yaml",
|
|
146
|
+
"openapi.json",
|
|
147
|
+
# Development environment
|
|
148
|
+
".nvmrc",
|
|
149
|
+
".ruby-version",
|
|
150
|
+
".python-version",
|
|
151
|
+
"Vagrantfile",
|
|
152
|
+
# Quality and metrics
|
|
153
|
+
".codeclimate.yml",
|
|
154
|
+
"codecov.yml",
|
|
155
|
+
# Documentation
|
|
156
|
+
"mkdocs.yml",
|
|
157
|
+
"_config.yml",
|
|
158
|
+
"book.toml",
|
|
159
|
+
"readthedocs.yml",
|
|
160
|
+
".readthedocs.yaml",
|
|
161
|
+
# Package registries
|
|
162
|
+
".npmrc",
|
|
163
|
+
".yarnrc",
|
|
164
|
+
# Linting and formatting
|
|
165
|
+
".isort.cfg",
|
|
166
|
+
".markdownlint.json",
|
|
167
|
+
".markdownlint.yaml",
|
|
168
|
+
# Security
|
|
169
|
+
".bandit",
|
|
170
|
+
".secrets.baseline",
|
|
171
|
+
# Misc
|
|
172
|
+
".pypirc",
|
|
173
|
+
".gitkeep",
|
|
174
|
+
".npmignore",
|
|
175
|
+
]
|