Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ class Hello(BaseTask):
```

## Changelog

### Version 25.1.2

#### 🛠️ Enhancements
- **Added support** for the `lookup` functionality in task fields, allowing dynamic population of field values based on runtime data or external sources.

### Version 25.1.1

#### ✨ New Features
- **Introduced** a ready-to-use `dai_logger` object to simplify logging across all tasks

#### 🔧 Changes & Improvements
- **Refactored** the client creation process for the cluster

### Version 25.1.0

#### 🚨 Breaking Changes
Expand All @@ -64,5 +78,67 @@ class Hello(BaseTask):
- **Bundled `requests` library** to ensure seamless HTTP request handling.

---
**For more details, visit the [official documentation](https://docs.digital.ai/release/docs/category/python-sdk).**
## 🔁 Upgrading from `digitalai-release-sdk` 24.1.0 or 23.3.0 to 25.1.0

With the release of **digitalai-release-sdk 25.1.0**, the API stubs have been separated into a standalone package.

👉 [`digitalai-release-api-stubs`](https://pypi.org/project/digitalai-release-api-stubs/)

To upgrade your project, follow these steps:

### Step 1: Install the API Stubs Package

You must explicitly install the new API stubs package:

```bash
pip install digitalai-release-api-stubs==25.1.0
```

Or, add it to your `requirements.txt` as needed.

---

### Step 2: Update Your Code

In previous versions, API clients were created like this:

```python
# Old code (pre-25.1.0)
configuration_api = ConfigurationApi(self.get_default_api_client())
```

In version **25.1.0**, use the following approach:

```python
# New code (25.1.0)

# Create a configuration object
configuration = Configuration(
host=self.get_release_server_url(),
username=self.get_task_user().username,
password=self.get_task_user().password
)

# Instantiate the API client using the configuration
apiclient = ApiClient(configuration)

# Create the Configuration API client
configuration_api = ConfigurationApi(apiclient)
```

This pattern should be used for all API clients, such as `TemplateApi`, `TaskApi`, etc.

---

## 🔗 Related Resources

- 🧪 **Python Template Project**: [release-integration-template-python](https://github.com/digital-ai/release-integration-template-python)
A starting point for building custom integrations using Digital.ai Release and Python.

- 📘 **Official Documentation**: [Digital.ai Release Python SDK Docs](https://docs.digital.ai/release/docs/category/python-sdk)
Comprehensive guide to using the Python SDK and building custom tasks.

- 📦 **Digital.ai Release Python SDK**: [digitalai-release-sdk on PyPI](https://pypi.org/project/digitalai-release-sdk/)
The official SDK package for integrating with Digital.ai Release.


1 change: 1 addition & 0 deletions digitalai/release/integration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from .output_context import OutputContext
from .exceptions import AbortException
from .reporting_records import BuildRecord, PlanRecord, ItsmRecord,CodeComplianceRecord, DeploymentRecord
from .logger import dai_logger
11 changes: 5 additions & 6 deletions digitalai/release/integration/base_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
from .input_context import AutomatedTaskAsUserContext
from .output_context import OutputContext
from .exceptions import AbortException
from .logger import dai_logger
from digitalai.release.release_api_client import ReleaseAPIClient

logger = logging.getLogger("Digitalai")


class BaseTask(ABC):
"""
Expand All @@ -33,12 +32,12 @@ def execute_task(self) -> None:
self.output_context = OutputContext(0, "", {}, [])
self.execute()
except AbortException:
logger.debug("Abort requested")
dai_logger.info("Abort requested")
self.set_exit_code(1)
self.set_error_message("Abort requested")
sys.exit(1)
except Exception as e:
logger.error("Unexpected error occurred.", exc_info=True)
dai_logger.error("Unexpected error occurred", exc_info=True)
self.set_exit_code(1)
self.set_error_message(str(e))

Expand Down Expand Up @@ -109,13 +108,13 @@ def add_comment(self, comment: str) -> None:
"""
Logs a comment of the task.
"""
logger.debug(f"##[start: comment]{comment}##[end: comment]")
dai_logger.debug(f"##[start: comment]{comment}##[end: comment]")

def set_status_line(self, status_line: str) -> None:
"""
Set the status of the task.
"""
logger.debug(f"##[start: status]{status_line}##[end: status]")
dai_logger.debug(f"##[start: status]{status_line}##[end: status]")

def add_reporting_record(self, reporting_record: Any) -> None:
"""
Expand Down
21 changes: 11 additions & 10 deletions digitalai/release/integration/input_context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Any, List, Dict, Optional
from dataclasses_json import dataclass_json, LetterCase

Expand Down Expand Up @@ -62,9 +62,9 @@ class CiDefinition:
- type (str): Type of the CI.
- properties (List[PropertyDefinition]): List of properties for the CI.
"""
id: str
type: str
properties: List[PropertyDefinition]
id: Optional[str] = None
type: Optional[str] = None
properties: List[PropertyDefinition] = field(default_factory=list)


@dataclass_json
Expand Down Expand Up @@ -129,8 +129,8 @@ class AutomatedTaskAsUserContext:
- username (str): The username to run the task as.
- password (str): The password for the user.
"""
username: Optional[str]
password: Optional[str]
username: Optional[str] = None
password: Optional[str] = None


@dataclass_json(letter_case=LetterCase.CAMEL)
Expand All @@ -144,8 +144,8 @@ class ReleaseContext:
- automated_task_as_user (AutomatedTaskAsUserContext): Context for running
an automated task as a specific user.
"""
id: str
automated_task_as_user: AutomatedTaskAsUserContext
id: Optional[str] = None
automated_task_as_user: Optional[AutomatedTaskAsUserContext] = field(default_factory=AutomatedTaskAsUserContext)


@dataclass_json()
Expand All @@ -158,6 +158,7 @@ class InputContext:
- release (ReleaseContext): Context of the release.
- task (TaskContext): Context of the task.
"""
release: ReleaseContext
task: TaskContext
release: Optional[ReleaseContext] = None
task: Optional[TaskContext] = None


16 changes: 15 additions & 1 deletion digitalai/release/integration/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from kubernetes import client, config
from kubernetes.client import CoreV1Api
from kubernetes.config.config_exception import ConfigException
from .logger import dai_logger

kubernetes_client: CoreV1Api = None
lock = threading.Lock()
Expand All @@ -13,8 +15,20 @@ def get_client():
if not kubernetes_client:
with lock:
if not kubernetes_client:
config.load_config()
try:
#dai_logger.info("Attempting to load in-cluster config")
config.load_incluster_config()
#dai_logger.info("Successfully loaded in-cluster config")
except ConfigException:
#dai_logger.warning("In-cluster config failed, attempting default load_config")
try:
config.load_config()
#dai_logger.info("Successfully loaded config using load_config")
except Exception:
dai_logger.exception("Failed to load any Kubernetes config")
raise RuntimeError("Could not configure Kubernetes client")
kubernetes_client = client.CoreV1Api()
#dai_logger.info("Kubernetes client created successfully")

return kubernetes_client

Expand Down
20 changes: 20 additions & 0 deletions digitalai/release/integration/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging
import sys

# Define the log format (with milliseconds) and date format
LOG_FORMAT = "%(asctime)s.%(msecs)03d %(levelname)s [%(filename)s:%(lineno)d] - %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

# Create a formatter
_formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=DATE_FORMAT)

# Create a stream handler (to stdout) and attach the formatter
_handler = logging.StreamHandler(sys.stdout)
_handler.setFormatter(_formatter)

# Get your “dai” logger
dai_logger = logging.getLogger("digital_ai")
dai_logger.setLevel(logging.DEBUG)
dai_logger.propagate = False
if not dai_logger.handlers:
dai_logger.addHandler(_handler)
35 changes: 0 additions & 35 deletions digitalai/release/integration/logging_config.py

This file was deleted.

13 changes: 5 additions & 8 deletions digitalai/release/integration/watcher.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import logging
import os
import threading

from kubernetes import watch

from .logger import dai_logger
from digitalai.release.integration import k8s

logger = logging.getLogger("Digitalai")


def start_input_context_watcher(on_input_context_update_func):
logger.debug("Input context watcher started")
dai_logger.info("Input context watcher started")

stop = threading.Event()

try:
start_input_secret_watcher(on_input_context_update_func, stop)
except Exception:
logger.error("Unexpected error occurred.", exc_info=True)
dai_logger.error("Unexpected error occurred.", exc_info=True)
return

# Wait until the watcher is stopped
stop.wait()


def start_input_secret_watcher(on_input_context_update_func, stop):
logger.debug("Input secret watcher started")
dai_logger.info("Input secret watcher started")

kubernetes_client = k8s.get_client()
field_selector = "metadata.name=" + os.getenv("INPUT_CONTEXT_SECRET")
Expand All @@ -39,7 +36,7 @@ def start_input_secret_watcher(on_input_context_update_func, stop):

# Checking if 'session-key' field has changed
if old_session_key and old_session_key != new_session_key:
logger.info("Detected input context value change")
dai_logger.info("Detected input context value change")
on_input_context_update_func()

# Set old session-key value
Expand Down
Loading