autobots-devtools-shared-lib 0.3.2__tar.gz → 0.3.4__tar.gz
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.
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/PKG-INFO +1 -1
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/pyproject.toml +1 -1
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/config/jenkins_config.py +22 -10
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/config/jenkins_constants.py +65 -0
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/config/jenkins_loader.py +75 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/otel_fastapi.py +12 -6
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/trace_propagation.py +37 -5
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/tools/jenkins_builtin_tools.py +35 -0
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/tools/jenkins_pipeline_tools.py +117 -0
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/utils/jenkins_builtin_utils.py +97 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/utils/jenkins_http_utils.py +15 -6
- autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/utils/jenkins_pipeline_utils.py +217 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/batch.py +6 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/invocation_utils.py +147 -105
- autobots_devtools_shared_lib-0.3.2/src/autobots_devtools_shared_lib/common/config/jenkins_loader.py +0 -47
- autobots_devtools_shared_lib-0.3.2/src/autobots_devtools_shared_lib/common/tools/jenkins_builtin_tools.py +0 -89
- autobots_devtools_shared_lib-0.3.2/src/autobots_devtools_shared_lib/common/tools/jenkins_pipeline_tools.py +0 -167
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/README.md +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/config/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/logging_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/trace_metadata.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/observability/tracing.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/fileserver/README.md +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/fileserver/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/fileserver/app.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/fileserver/config.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/servers/fileserver/models.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/README.md +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/cache_backed.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/db_repository.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/factory.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/in_memory.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/redis_store.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/services/context/store.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/tools/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/tools/context_tools.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/tools/format_tools.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/tools/fserver_client_tools.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/utils/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/utils/context_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/utils/format_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/common/utils/fserver_client_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/agent_config_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/agent_meta.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/base_agent.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/agents/middleware.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/config/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/config/dynagent_settings.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/llm/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/llm/llm.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/models/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/models/state.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/services/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/services/structured_converter.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/tools/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/tools/state_tools.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/tools/tool_registry.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/ui/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/ui/default_ui.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/ui/ui_utils.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/dynagent/utils/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.3.2 → autobots_devtools_shared_lib-0.3.4}/src/autobots_devtools_shared_lib/py.typed +0 -0
|
@@ -5,16 +5,28 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
7
|
|
|
8
|
+
from autobots_devtools_shared_lib.common.config.jenkins_constants import (
|
|
9
|
+
DEFAULT_API_TOKEN_ENV,
|
|
10
|
+
DEFAULT_MAX_WAIT_SECONDS,
|
|
11
|
+
DEFAULT_PARAM_REQUIRED,
|
|
12
|
+
DEFAULT_PARAM_TYPE,
|
|
13
|
+
DEFAULT_POLL_INTERVAL_SECONDS,
|
|
14
|
+
DEFAULT_QUEUE_MAX_RETRIES,
|
|
15
|
+
DEFAULT_QUEUE_RETRY_DELAY_SECONDS,
|
|
16
|
+
DEFAULT_USERNAME_ENV,
|
|
17
|
+
DEFAULT_WAIT_FOR_COMPLETION,
|
|
18
|
+
)
|
|
19
|
+
|
|
8
20
|
|
|
9
21
|
class JenkinsAuthConfig(BaseModel):
|
|
10
22
|
"""Credentials resolved from environment variables at runtime."""
|
|
11
23
|
|
|
12
24
|
username_env: str = Field(
|
|
13
|
-
default=
|
|
25
|
+
default=DEFAULT_USERNAME_ENV,
|
|
14
26
|
description="Name of the env var holding the Jenkins username",
|
|
15
27
|
)
|
|
16
28
|
token_env: str = Field(
|
|
17
|
-
default=
|
|
29
|
+
default=DEFAULT_API_TOKEN_ENV,
|
|
18
30
|
description="Name of the env var holding the Jenkins API token",
|
|
19
31
|
)
|
|
20
32
|
|
|
@@ -23,23 +35,23 @@ class JenkinsPollingConfig(BaseModel):
|
|
|
23
35
|
"""Controls whether and how long to wait for a triggered build to complete."""
|
|
24
36
|
|
|
25
37
|
wait_for_completion: bool = Field(
|
|
26
|
-
default=
|
|
38
|
+
default=DEFAULT_WAIT_FOR_COMPLETION,
|
|
27
39
|
description="Whether to block until the build finishes",
|
|
28
40
|
)
|
|
29
41
|
poll_interval_seconds: int = Field(
|
|
30
|
-
default=
|
|
42
|
+
default=DEFAULT_POLL_INTERVAL_SECONDS,
|
|
31
43
|
description="Seconds to wait between build-status polls",
|
|
32
44
|
)
|
|
33
45
|
max_wait_seconds: int = Field(
|
|
34
|
-
default=
|
|
46
|
+
default=DEFAULT_MAX_WAIT_SECONDS,
|
|
35
47
|
description="Maximum seconds to wait before giving up",
|
|
36
48
|
)
|
|
37
49
|
queue_max_retries: int = Field(
|
|
38
|
-
default=
|
|
50
|
+
default=DEFAULT_QUEUE_MAX_RETRIES,
|
|
39
51
|
description="Max attempts to resolve a build number from the Jenkins queue",
|
|
40
52
|
)
|
|
41
53
|
queue_retry_delay_seconds: int = Field(
|
|
42
|
-
default=
|
|
54
|
+
default=DEFAULT_QUEUE_RETRY_DELAY_SECONDS,
|
|
43
55
|
description="Seconds to wait between queue-item polls",
|
|
44
56
|
)
|
|
45
57
|
|
|
@@ -48,7 +60,7 @@ class JenkinsParameterConfig(BaseModel):
|
|
|
48
60
|
"""A single parameter that will be forwarded as a query string to Jenkins."""
|
|
49
61
|
|
|
50
62
|
type: str = Field(
|
|
51
|
-
default=
|
|
63
|
+
default=DEFAULT_PARAM_TYPE,
|
|
52
64
|
description="Parameter type: 'string', 'boolean', or 'integer'",
|
|
53
65
|
)
|
|
54
66
|
description: str = Field(
|
|
@@ -56,7 +68,7 @@ class JenkinsParameterConfig(BaseModel):
|
|
|
56
68
|
description="LLM-facing hint describing what value to provide",
|
|
57
69
|
)
|
|
58
70
|
required: bool = Field(
|
|
59
|
-
default=
|
|
71
|
+
default=DEFAULT_PARAM_REQUIRED,
|
|
60
72
|
description="Whether the LLM must always supply this parameter",
|
|
61
73
|
)
|
|
62
74
|
|
|
@@ -64,7 +76,7 @@ class JenkinsParameterConfig(BaseModel):
|
|
|
64
76
|
class JenkinsPipelineConfig(BaseModel):
|
|
65
77
|
"""Configuration for a single Jenkins pipeline entry.
|
|
66
78
|
|
|
67
|
-
The framework registers this as a LangChain tool named ``{key}
|
|
79
|
+
The framework registers this as a LangChain tool named ``{key}{TOOL_NAME_SUFFIX}``,
|
|
68
80
|
so pipeline keys in jenkins.yaml should not carry a ``_tool`` suffix.
|
|
69
81
|
"""
|
|
70
82
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ABOUTME: Shared constants for all Jenkins integration files.
|
|
2
|
+
# ABOUTME: Single source of truth for default values, magic numbers, and string literals.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# Authentication defaults
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
DEFAULT_USERNAME_ENV: str = "JENKINS_USERNAME"
|
|
11
|
+
DEFAULT_API_TOKEN_ENV: str = "JENKINS_API_TOKEN" # noqa: S105
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Polling defaults
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
DEFAULT_WAIT_FOR_COMPLETION: bool = True
|
|
18
|
+
DEFAULT_POLL_INTERVAL_SECONDS: int = 10
|
|
19
|
+
DEFAULT_MAX_WAIT_SECONDS: int = 300
|
|
20
|
+
DEFAULT_QUEUE_MAX_RETRIES: int = 5
|
|
21
|
+
DEFAULT_QUEUE_RETRY_DELAY_SECONDS: int = 2
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# HTTP
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
HTTP_TIMEOUT_SECONDS: int = 30
|
|
28
|
+
QUEUE_INITIAL_DELAY_SECONDS: int = 1
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Jenkins API paths / URL segments
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
JENKINS_CONFIG_FILENAME: str = "jenkins.yaml"
|
|
35
|
+
API_JSON_SUFFIX: str = "api/json"
|
|
36
|
+
JOB_URL_SEGMENT: str = "job"
|
|
37
|
+
LAST_BUILD_SEGMENT: str = "lastBuild"
|
|
38
|
+
CONSOLE_LOG_PATH: str = "logText/progressiveText"
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# URL templates (use .format(base_url=..., job_name=..., build_number=..., start=...))
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
_BASE_JOB_PREFIX: str = "{base_url}/" + JOB_URL_SEGMENT + "/{job_name}/"
|
|
45
|
+
|
|
46
|
+
BUILD_STATUS_LATEST_URL: str = _BASE_JOB_PREFIX + LAST_BUILD_SEGMENT + "/" + API_JSON_SUFFIX
|
|
47
|
+
BUILD_STATUS_URL: str = _BASE_JOB_PREFIX + "{build_number}/" + API_JSON_SUFFIX
|
|
48
|
+
CONSOLE_LOG_LATEST_URL: str = (
|
|
49
|
+
_BASE_JOB_PREFIX + LAST_BUILD_SEGMENT + "/" + CONSOLE_LOG_PATH + "?start={start}"
|
|
50
|
+
)
|
|
51
|
+
CONSOLE_LOG_URL: str = _BASE_JOB_PREFIX + "{build_number}/" + CONSOLE_LOG_PATH + "?start={start}"
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Tool naming
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
TOOL_NAME_SUFFIX: str = "_tool"
|
|
58
|
+
ARGS_SCHEMA_SUFFIX: str = "_args"
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Parameter config defaults
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
DEFAULT_PARAM_TYPE: str = "string"
|
|
65
|
+
DEFAULT_PARAM_REQUIRED: bool = True
|
autobots_devtools_shared_lib-0.3.4/src/autobots_devtools_shared_lib/common/config/jenkins_loader.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ABOUTME: Loads jenkins.yaml from the same config directory as agents.yaml.
|
|
2
|
+
# ABOUTME: Returns None silently when jenkins.yaml is absent (feature is opt-in).
|
|
3
|
+
# ABOUTME: Owns the JenkinsConfig singleton — get_jenkins_config() is the shared access point.
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from autobots_devtools_shared_lib.common.config.jenkins_config import JenkinsConfig
|
|
10
|
+
from autobots_devtools_shared_lib.common.config.jenkins_constants import JENKINS_CONFIG_FILENAME
|
|
11
|
+
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
_config: JenkinsConfig | None = None
|
|
16
|
+
_config_loaded: bool = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_jenkins_config(config: JenkinsConfig | None) -> None:
|
|
20
|
+
"""Explicitly set the Jenkins config singleton (useful for tests or custom injection)."""
|
|
21
|
+
global _config, _config_loaded
|
|
22
|
+
_config = config
|
|
23
|
+
_config_loaded = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_jenkins_config() -> JenkinsConfig | None:
|
|
27
|
+
"""Return the cached JenkinsConfig, loading from disk on the first call.
|
|
28
|
+
|
|
29
|
+
Jenkins config is optional — returns None when jenkins.yaml is absent.
|
|
30
|
+
The result (including None) is cached after the first call so the filesystem
|
|
31
|
+
is only read once regardless of outcome. Subsequent calls return immediately.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
yaml.YAMLError: If the file contains invalid YAML (first call only).
|
|
35
|
+
pydantic.ValidationError: If the YAML structure does not match the schema (first call only).
|
|
36
|
+
"""
|
|
37
|
+
global _config, _config_loaded
|
|
38
|
+
if not _config_loaded:
|
|
39
|
+
set_jenkins_config(_load_jenkins_config())
|
|
40
|
+
return _config
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load_jenkins_config() -> JenkinsConfig | None:
|
|
44
|
+
"""Load and validate jenkins.yaml from the active config directory.
|
|
45
|
+
|
|
46
|
+
Internal implementation — always reads from disk without caching.
|
|
47
|
+
Use get_jenkins_config() for the cached singleton instead.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Validated JenkinsConfig or None if the file does not exist.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
yaml.YAMLError: If the file contains invalid YAML.
|
|
54
|
+
pydantic.ValidationError: If the YAML structure does not match the schema.
|
|
55
|
+
"""
|
|
56
|
+
from autobots_devtools_shared_lib.dynagent.agents.agent_config_utils import get_config_dir
|
|
57
|
+
|
|
58
|
+
config_dir = get_config_dir()
|
|
59
|
+
jenkins_path = config_dir / JENKINS_CONFIG_FILENAME
|
|
60
|
+
|
|
61
|
+
if not jenkins_path.exists():
|
|
62
|
+
logger.debug(
|
|
63
|
+
f"No jenkins.yaml found at {jenkins_path}; Jenkins tools will not be registered"
|
|
64
|
+
)
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
logger.info(f"Loading Jenkins config from {jenkins_path}")
|
|
68
|
+
with open(jenkins_path) as f: # noqa: PTH123
|
|
69
|
+
raw = yaml.safe_load(f)
|
|
70
|
+
|
|
71
|
+
config = JenkinsConfig.model_validate(raw["jenkins_config"])
|
|
72
|
+
logger.info(
|
|
73
|
+
f"Loaded Jenkins config with {len(config.pipelines)} pipeline(s): {list(config.pipelines)}"
|
|
74
|
+
)
|
|
75
|
+
return config
|
|
@@ -34,14 +34,20 @@ class OtelCaptureConfig:
|
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
# Default Langfuse cloud host when LANGFUSE_HOST is not set (same as trace_propagation.py).
|
|
38
|
+
_DEFAULT_LANGFUSE_HOST = "https://cloud.langfuse.com"
|
|
39
|
+
|
|
40
|
+
|
|
37
41
|
def _configure_langfuse_otlp() -> bool:
|
|
38
42
|
"""If Langfuse keys are set, configure env for OTLP export to Langfuse. Returns True if set."""
|
|
39
43
|
pk = os.getenv("LANGFUSE_PUBLIC_KEY", "").strip()
|
|
40
44
|
sk = os.getenv("LANGFUSE_SECRET_KEY", "").strip()
|
|
41
45
|
if not pk or not sk:
|
|
42
46
|
return False
|
|
43
|
-
host = os.getenv("
|
|
44
|
-
|
|
47
|
+
host = os.getenv("LANGFUSE_BASE_URL", _DEFAULT_LANGFUSE_HOST).strip()
|
|
48
|
+
if not host:
|
|
49
|
+
return False
|
|
50
|
+
endpoint = f"{host.rstrip('/')}/api/public/otel"
|
|
45
51
|
auth = base64.b64encode(f"{pk}:{sk}".encode()).decode()
|
|
46
52
|
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint
|
|
47
53
|
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {auth}"
|
|
@@ -207,12 +213,12 @@ def _set_span_attributes(
|
|
|
207
213
|
input_data["body"] = req_truncated["content"]
|
|
208
214
|
input_data["body_size"] = req_truncated["size_bytes"]
|
|
209
215
|
|
|
210
|
-
# Extract
|
|
216
|
+
# Extract session_id and set as langfuse.session.id
|
|
211
217
|
try:
|
|
212
218
|
req_json = json.loads(req_body.decode("utf-8"))
|
|
213
|
-
|
|
214
|
-
if
|
|
215
|
-
span.set_attribute("langfuse.session.id", str(
|
|
219
|
+
session_id = req_json.get("session_id")
|
|
220
|
+
if session_id:
|
|
221
|
+
span.set_attribute("langfuse.session.id", str(session_id))
|
|
216
222
|
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
217
223
|
# Not JSON or invalid UTF-8, skip session linking
|
|
218
224
|
pass
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
OpenTelemetry trace propagation helpers for HTTP client calls.
|
|
3
3
|
|
|
4
4
|
Provides W3C traceparent header injection and session linking for fileserver HTTP calls.
|
|
5
|
-
Reuses Langfuse configuration (LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY,
|
|
5
|
+
Reuses Langfuse configuration (LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASE_URL).
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
@@ -11,7 +11,7 @@ import base64
|
|
|
11
11
|
import os
|
|
12
12
|
import sys
|
|
13
13
|
from contextlib import contextmanager
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from collections.abc import Generator
|
|
@@ -43,8 +43,8 @@ def _ensure_tracer_provider() -> bool:
|
|
|
43
43
|
try:
|
|
44
44
|
# Check if OTEL packages available
|
|
45
45
|
from opentelemetry import trace
|
|
46
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
47
|
-
OTLPSpanExporter,
|
|
46
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( # pyright: ignore[reportMissingImports]
|
|
47
|
+
OTLPSpanExporter,
|
|
48
48
|
)
|
|
49
49
|
from opentelemetry.sdk.resources import Resource
|
|
50
50
|
from opentelemetry.sdk.trace import TracerProvider
|
|
@@ -80,7 +80,7 @@ def _ensure_tracer_provider() -> bool:
|
|
|
80
80
|
|
|
81
81
|
try:
|
|
82
82
|
# Configure OTLP endpoint (same pattern as otel_fastapi.py)
|
|
83
|
-
host = os.getenv("
|
|
83
|
+
host = os.getenv("LANGFUSE_BASE_URL", "https://cloud.langfuse.com")
|
|
84
84
|
endpoint = f"{host}/api/public/otel"
|
|
85
85
|
auth = base64.b64encode(f"{pk}:{sk}".encode()).decode()
|
|
86
86
|
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint
|
|
@@ -115,6 +115,35 @@ def _ensure_tracer_provider() -> bool:
|
|
|
115
115
|
return False
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def ensure_tracer_provider() -> bool:
|
|
119
|
+
"""Public wrapper — returns True when an OTel tracer provider is ready."""
|
|
120
|
+
return _ensure_tracer_provider()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def otel_span(name: str, kind: str = "SERVER") -> Any:
|
|
124
|
+
"""Return an OTel span context manager, or nullcontext if OTel is unavailable.
|
|
125
|
+
|
|
126
|
+
Use this to create a root span that encompasses an entire orchestration step
|
|
127
|
+
(agent invocation + downstream file I/O) so all child spans share one trace.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
name: Span name shown in Langfuse.
|
|
131
|
+
kind: OTel SpanKind string — "SERVER" (default) or "CLIENT".
|
|
132
|
+
"""
|
|
133
|
+
from contextlib import nullcontext
|
|
134
|
+
|
|
135
|
+
if not _ensure_tracer_provider():
|
|
136
|
+
return nullcontext()
|
|
137
|
+
try:
|
|
138
|
+
from opentelemetry import trace
|
|
139
|
+
from opentelemetry.trace import SpanKind
|
|
140
|
+
|
|
141
|
+
span_kind = SpanKind.CLIENT if kind == "CLIENT" else SpanKind.SERVER
|
|
142
|
+
return trace.get_tracer(__name__).start_as_current_span(name, kind=span_kind)
|
|
143
|
+
except Exception:
|
|
144
|
+
return nullcontext()
|
|
145
|
+
|
|
146
|
+
|
|
118
147
|
@contextmanager
|
|
119
148
|
def traced_http_call(
|
|
120
149
|
operation: str,
|
|
@@ -125,6 +154,9 @@ def traced_http_call(
|
|
|
125
154
|
Context manager that creates an OTEL client span and injects W3C traceparent header.
|
|
126
155
|
|
|
127
156
|
Links HTTP calls to Langfuse sessions via langfuse.session.id attribute.
|
|
157
|
+
When the same session_id is used for the parent (e.g. nurture trigger / agent
|
|
158
|
+
invocation) and for file client calls, Langfuse groups them in the Session view
|
|
159
|
+
so http.client.readFile / http.client.writeFile appear with the agent trace.
|
|
128
160
|
Gracefully degrades to empty headers if OTEL unavailable.
|
|
129
161
|
|
|
130
162
|
Args:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# ABOUTME: Thin LangChain agent layer for Jenkins observability tools.
|
|
2
|
+
# ABOUTME: Wraps get_build_status() and get_console_log() from jenkins_builtin_utils as @tools.
|
|
3
|
+
# ABOUTME: All HTTP logic lives in common/utils/jenkins_builtin_utils.py.
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from langchain.tools import tool
|
|
8
|
+
|
|
9
|
+
from autobots_devtools_shared_lib.common.utils.jenkins_builtin_utils import (
|
|
10
|
+
get_build_status,
|
|
11
|
+
get_console_log,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@tool
|
|
16
|
+
def get_jenkins_build_status(job_name: str, build_number: int | None = None) -> str:
|
|
17
|
+
"""Get the current status of a Jenkins build.
|
|
18
|
+
|
|
19
|
+
Returns a concise string with job name, build number,
|
|
20
|
+
status (SUCCESS / FAILURE / IN_PROGRESS), and build URL.
|
|
21
|
+
Omit build_number to check the latest build.
|
|
22
|
+
"""
|
|
23
|
+
return get_build_status(job_name, build_number)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@tool
|
|
27
|
+
def get_jenkins_console_log(job_name: str, build_number: int | None = None, start: int = 0) -> str:
|
|
28
|
+
"""Retrieve the console log output for a Jenkins build.
|
|
29
|
+
|
|
30
|
+
Returns the log text from the given byte offset.
|
|
31
|
+
Use start=0 (default) for the full log.
|
|
32
|
+
Omit build_number to use the latest build.
|
|
33
|
+
Useful for diagnosing pipeline failures.
|
|
34
|
+
"""
|
|
35
|
+
return get_console_log(job_name, build_number, start)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ABOUTME: Thin LangChain agent layer — wraps JenkinsPipelineRunner as StructuredTools.
|
|
2
|
+
# ABOUTME: All execution logic lives in common/utils/jenkins_pipeline_utils.py.
|
|
3
|
+
# ABOUTME: register_pipeline_tools() is the single entry point for agent tool registration.
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from langchain_core.tools import StructuredTool
|
|
10
|
+
from pydantic import BaseModel, Field, create_model
|
|
11
|
+
|
|
12
|
+
from autobots_devtools_shared_lib.common.config.jenkins_constants import (
|
|
13
|
+
ARGS_SCHEMA_SUFFIX,
|
|
14
|
+
TOOL_NAME_SUFFIX,
|
|
15
|
+
)
|
|
16
|
+
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
17
|
+
from autobots_devtools_shared_lib.common.tools.jenkins_builtin_tools import (
|
|
18
|
+
get_jenkins_build_status,
|
|
19
|
+
get_jenkins_console_log,
|
|
20
|
+
)
|
|
21
|
+
from autobots_devtools_shared_lib.common.utils.jenkins_pipeline_utils import get_pipeline_runner
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from autobots_devtools_shared_lib.common.config.jenkins_config import JenkinsPipelineConfig
|
|
25
|
+
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
_PYTHON_TYPE_MAP: dict[str, type] = {
|
|
29
|
+
"string": str,
|
|
30
|
+
"str": str,
|
|
31
|
+
"boolean": bool,
|
|
32
|
+
"bool": bool,
|
|
33
|
+
"integer": int,
|
|
34
|
+
"int": int,
|
|
35
|
+
"float": float,
|
|
36
|
+
"number": float,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_args_schema(tool_name: str, pipeline_cfg: JenkinsPipelineConfig) -> type[BaseModel]:
|
|
41
|
+
"""Build a dynamic Pydantic model from a pipeline's parameters.
|
|
42
|
+
|
|
43
|
+
Required parameters get a plain Field; optional ones get ``type | None`` with default None.
|
|
44
|
+
"""
|
|
45
|
+
field_definitions: dict[str, Any] = {}
|
|
46
|
+
for param_name, param_cfg in pipeline_cfg.parameters.items():
|
|
47
|
+
py_type = _PYTHON_TYPE_MAP.get(param_cfg.type.lower(), str)
|
|
48
|
+
if param_cfg.required:
|
|
49
|
+
field_definitions[param_name] = (py_type, Field(description=param_cfg.description))
|
|
50
|
+
else:
|
|
51
|
+
field_definitions[param_name] = (
|
|
52
|
+
py_type | None,
|
|
53
|
+
Field(default=None, description=param_cfg.description),
|
|
54
|
+
)
|
|
55
|
+
return create_model(f"{tool_name}{ARGS_SCHEMA_SUFFIX}", **field_definitions)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_jenkins_tools() -> list[Any]:
|
|
59
|
+
"""Generate LangChain StructuredTools from the loaded JenkinsConfig.
|
|
60
|
+
|
|
61
|
+
For each entry in ``config.pipelines`` a StructuredTool is created whose
|
|
62
|
+
name is ``{pipeline_key}_tool`` (e.g. pipeline key ``create_workspace``
|
|
63
|
+
→ tool name ``create_workspace_tool``).
|
|
64
|
+
|
|
65
|
+
Config is loaded lazily via get_pipeline_runner() — no explicit config
|
|
66
|
+
argument needed. Call register_pipeline_tools() as the higher-level entry
|
|
67
|
+
point which also includes the builtin observability tools.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
List of pipeline StructuredTools ready for ``register_usecase_tools()``.
|
|
71
|
+
The builtin observability tools are not included here.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
RuntimeError: If jenkins.yaml cannot be found.
|
|
75
|
+
"""
|
|
76
|
+
runner = get_pipeline_runner()
|
|
77
|
+
tools: list[Any] = []
|
|
78
|
+
|
|
79
|
+
for pipeline_name, pipeline_cfg in runner._config.pipelines.items():
|
|
80
|
+
tool_name = f"{pipeline_name}{TOOL_NAME_SUFFIX}"
|
|
81
|
+
args_schema = _build_args_schema(tool_name, pipeline_cfg)
|
|
82
|
+
description = pipeline_cfg.description or f"Trigger Jenkins pipeline: {pipeline_cfg.uri}"
|
|
83
|
+
|
|
84
|
+
tools.append(
|
|
85
|
+
StructuredTool.from_function(
|
|
86
|
+
func=runner.get_callable(pipeline_name),
|
|
87
|
+
name=tool_name,
|
|
88
|
+
description=description,
|
|
89
|
+
args_schema=args_schema,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
logger.info(f"Registered Jenkins tool '{tool_name}' → {pipeline_cfg.uri}")
|
|
93
|
+
|
|
94
|
+
return tools
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def register_pipeline_tools() -> list[Any]:
|
|
98
|
+
"""Load jenkins.yaml and return all Jenkins tools (pipeline + builtin observability).
|
|
99
|
+
|
|
100
|
+
Returns an empty list when jenkins.yaml is absent — Jenkins integration is optional.
|
|
101
|
+
Config loading and caching is handled internally; no setup required before calling.
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
from autobots_devtools_shared_lib.common.config.jenkins_loader import get_jenkins_config
|
|
105
|
+
|
|
106
|
+
if get_jenkins_config() is not None:
|
|
107
|
+
logger.info("Loading Jenkins pipeline tools")
|
|
108
|
+
pipeline_tools = create_jenkins_tools()
|
|
109
|
+
all_tools = [get_jenkins_build_status, get_jenkins_console_log, *pipeline_tools]
|
|
110
|
+
logger.info(
|
|
111
|
+
f"Loaded {len(pipeline_tools)} pipeline tool(s) + 2 builtin Jenkins tools"
|
|
112
|
+
" from jenkins.yaml"
|
|
113
|
+
)
|
|
114
|
+
return all_tools
|
|
115
|
+
except Exception:
|
|
116
|
+
logger.exception("Failed to load Jenkins pipeline tools — continuing without them")
|
|
117
|
+
return []
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ABOUTME: Deterministic Jenkins observability utilities — pure Python, no LangChain dependency.
|
|
2
|
+
# ABOUTME: Provides get_build_status() and get_console_log() for direct/deterministic use.
|
|
3
|
+
# ABOUTME: LangChain @tool wrappers live in common/tools/jenkins_builtin_tools.py.
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from autobots_devtools_shared_lib.common.config.jenkins_constants import (
|
|
10
|
+
BUILD_STATUS_LATEST_URL,
|
|
11
|
+
BUILD_STATUS_URL,
|
|
12
|
+
CONSOLE_LOG_LATEST_URL,
|
|
13
|
+
CONSOLE_LOG_URL,
|
|
14
|
+
HTTP_TIMEOUT_SECONDS,
|
|
15
|
+
)
|
|
16
|
+
from autobots_devtools_shared_lib.common.config.jenkins_loader import get_jenkins_config
|
|
17
|
+
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
18
|
+
from autobots_devtools_shared_lib.common.utils.jenkins_http_utils import get_auth
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_build_status(job_name: str, build_number: int | None = None) -> str:
|
|
24
|
+
"""Get the current status of a Jenkins build.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
job_name: Jenkins job name.
|
|
28
|
+
build_number: Specific build number; omit to check the latest build.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A concise string: ``job=<name> build=<n> status=<STATUS> url=<url>``.
|
|
32
|
+
Returns an error string if Jenkins is not configured or the request fails.
|
|
33
|
+
"""
|
|
34
|
+
config = get_jenkins_config()
|
|
35
|
+
if config is None:
|
|
36
|
+
return "Error: Jenkins is not configured (jenkins.yaml not found)"
|
|
37
|
+
|
|
38
|
+
base_url = config.base_url.rstrip("/")
|
|
39
|
+
auth = get_auth(config)
|
|
40
|
+
if build_number is None:
|
|
41
|
+
api_url = BUILD_STATUS_LATEST_URL.format(base_url=base_url, job_name=job_name)
|
|
42
|
+
else:
|
|
43
|
+
api_url = BUILD_STATUS_URL.format(
|
|
44
|
+
base_url=base_url, job_name=job_name, build_number=build_number
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger.info("Getting build status for job='%s' build=%s", job_name, build_number)
|
|
48
|
+
try:
|
|
49
|
+
resp = requests.get(api_url, auth=auth, timeout=HTTP_TIMEOUT_SECONDS)
|
|
50
|
+
resp.raise_for_status()
|
|
51
|
+
data = resp.json()
|
|
52
|
+
number = data.get("number")
|
|
53
|
+
building = data.get("building", False)
|
|
54
|
+
result = data.get("result")
|
|
55
|
+
url = data.get("url", "")
|
|
56
|
+
status = "IN_PROGRESS" if (building or result is None) else str(result).upper()
|
|
57
|
+
except requests.RequestException as exc:
|
|
58
|
+
logger.exception("Error getting build status for '%s'", job_name)
|
|
59
|
+
return f"Error getting build status: {exc}"
|
|
60
|
+
else:
|
|
61
|
+
return f"job={job_name} build={number} status={status} url={url}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_console_log(job_name: str, build_number: int | None = None, start: int = 0) -> str:
|
|
65
|
+
"""Retrieve the console log output for a Jenkins build.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
job_name: Jenkins job name.
|
|
69
|
+
build_number: Specific build number; omit to use the latest build.
|
|
70
|
+
start: Byte offset to start reading from (0 for the full log).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Log text as a string.
|
|
74
|
+
Returns an error string if Jenkins is not configured or the request fails.
|
|
75
|
+
"""
|
|
76
|
+
config = get_jenkins_config()
|
|
77
|
+
if config is None:
|
|
78
|
+
return "Error: Jenkins is not configured (jenkins.yaml not found)"
|
|
79
|
+
|
|
80
|
+
base_url = config.base_url.rstrip("/")
|
|
81
|
+
auth = get_auth(config)
|
|
82
|
+
if build_number is None:
|
|
83
|
+
log_url = CONSOLE_LOG_LATEST_URL.format(base_url=base_url, job_name=job_name, start=start)
|
|
84
|
+
else:
|
|
85
|
+
log_url = CONSOLE_LOG_URL.format(
|
|
86
|
+
base_url=base_url, job_name=job_name, build_number=build_number, start=start
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
logger.info("Getting console log for job='%s' build=%s start=%s", job_name, build_number, start)
|
|
90
|
+
try:
|
|
91
|
+
resp = requests.get(log_url, auth=auth, timeout=HTTP_TIMEOUT_SECONDS)
|
|
92
|
+
resp.raise_for_status()
|
|
93
|
+
except requests.RequestException as exc:
|
|
94
|
+
logger.exception("Error getting console log for '%s'", job_name)
|
|
95
|
+
return f"Error getting console log: {exc}"
|
|
96
|
+
else:
|
|
97
|
+
return resp.text
|
|
@@ -9,6 +9,13 @@ from typing import TYPE_CHECKING, Any
|
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
|
+
from autobots_devtools_shared_lib.common.config.jenkins_constants import (
|
|
13
|
+
API_JSON_SUFFIX,
|
|
14
|
+
BUILD_STATUS_URL,
|
|
15
|
+
HTTP_TIMEOUT_SECONDS,
|
|
16
|
+
JOB_URL_SEGMENT,
|
|
17
|
+
QUEUE_INITIAL_DELAY_SECONDS,
|
|
18
|
+
)
|
|
12
19
|
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
13
20
|
|
|
14
21
|
if TYPE_CHECKING:
|
|
@@ -51,13 +58,13 @@ def poll_queue_for_build_number(
|
|
|
51
58
|
logger.error(msg)
|
|
52
59
|
return {"status": "error", "message": msg, "build_number": None, "build_url": None}
|
|
53
60
|
|
|
54
|
-
time.sleep(
|
|
55
|
-
queue_api_url = f"{queue_location}
|
|
61
|
+
time.sleep(QUEUE_INITIAL_DELAY_SECONDS)
|
|
62
|
+
queue_api_url = f"{queue_location}{API_JSON_SUFFIX}"
|
|
56
63
|
logger.info(f"Polling Jenkins queue: {queue_api_url}")
|
|
57
64
|
|
|
58
65
|
for attempt in range(polling.queue_max_retries):
|
|
59
66
|
try:
|
|
60
|
-
resp = requests.get(queue_api_url, auth=auth, timeout=
|
|
67
|
+
resp = requests.get(queue_api_url, auth=auth, timeout=HTTP_TIMEOUT_SECONDS)
|
|
61
68
|
resp.raise_for_status()
|
|
62
69
|
data = resp.json()
|
|
63
70
|
executable = data.get("executable")
|
|
@@ -106,10 +113,12 @@ def wait_for_build(
|
|
|
106
113
|
) -> str:
|
|
107
114
|
"""Poll the build API until the result is non-null or the timeout is reached."""
|
|
108
115
|
elapsed = 0
|
|
109
|
-
api_url =
|
|
116
|
+
api_url = BUILD_STATUS_URL.format(
|
|
117
|
+
base_url=base_url, job_name=job_name, build_number=build_number
|
|
118
|
+
)
|
|
110
119
|
while elapsed < polling.max_wait_seconds:
|
|
111
120
|
try:
|
|
112
|
-
resp = requests.get(api_url, auth=auth, timeout=
|
|
121
|
+
resp = requests.get(api_url, auth=auth, timeout=HTTP_TIMEOUT_SECONDS)
|
|
113
122
|
resp.raise_for_status()
|
|
114
123
|
data = resp.json()
|
|
115
124
|
building = data.get("building", False)
|
|
@@ -148,7 +157,7 @@ def extract_job_name_from_url(url: str) -> str:
|
|
|
148
157
|
"""
|
|
149
158
|
parts = [p for p in url.split("/") if p]
|
|
150
159
|
try:
|
|
151
|
-
job_idx = parts.index(
|
|
160
|
+
job_idx = parts.index(JOB_URL_SEGMENT)
|
|
152
161
|
return parts[job_idx + 1]
|
|
153
162
|
except (ValueError, IndexError):
|
|
154
163
|
return url
|