Skip to content
Open
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
13 changes: 13 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,16 @@ packages:
publish_workflow: release_publish.yml
publish_timeout_minutes: 10
publish_inputs: {}
snap:
package_type: snap
repo: redis/redis-snap
# homebrew has one fixed release branch: main
#ref: master
ref: release_automation
build_workflow: release_build_and_test.yml
build_timeout_minutes: 60
build_inputs: {}
publish_internal_release: yes
publish_workflow: release_publish.yml
publish_timeout_minutes: 10
publish_inputs: {}
328 changes: 2 additions & 326 deletions src/redis_release/bht/behaviours.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,9 @@
from redis_release.bht.state import reset_model_to_defaults

from ..github_client_async import GitHubClientAsync
from ..models import (
HomebrewChannel,
PackageType,
RedisVersion,
ReleaseType,
WorkflowConclusion,
WorkflowStatus,
)
from ..models import RedisVersion, ReleaseType, WorkflowConclusion, WorkflowStatus
from .logging_wrapper import PyTreesLoggerWrapper
from .state import HomebrewMeta, Package, PackageMeta, ReleaseMeta, Workflow
from .state import Package, PackageMeta, ReleaseMeta, Workflow

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -721,273 +714,6 @@ def update(self) -> Status:
return Status.SUCCESS


class DockerWorkflowInputs(ReleaseAction):
"""
Docker uses only release_tag input which is set automatically in TriggerWorkflow
"""

def __init__(
self,
name: str,
workflow: Workflow,
package_meta: PackageMeta,
release_meta: ReleaseMeta,
log_prefix: str = "",
) -> None:
self.workflow = workflow
self.package_meta = package_meta
self.release_meta = release_meta
super().__init__(name=name, log_prefix=log_prefix)

def update(self) -> Status:
return Status.SUCCESS


class HomewbrewWorkflowInputs(ReleaseAction):
def __init__(
self,
name: str,
workflow: Workflow,
package_meta: HomebrewMeta,
release_meta: ReleaseMeta,
log_prefix: str = "",
) -> None:
self.workflow = workflow
self.package_meta = package_meta
self.release_meta = release_meta
super().__init__(name=f"{name} - homebrew", log_prefix=log_prefix)

def update(self) -> Status:
if self.package_meta.release_type is not None:
self.workflow.inputs["release_type"] = self.package_meta.release_type.value
if self.release_meta.tag is not None:
self.workflow.inputs["release_tag"] = self.release_meta.tag
if self.package_meta.homebrew_channel is not None:
self.workflow.inputs["channel"] = self.package_meta.homebrew_channel.value
return Status.SUCCESS


class DetectHombrewReleaseAndChannel(ReleaseAction):
def __init__(
self,
name: str,
package_meta: HomebrewMeta,
release_meta: ReleaseMeta,
log_prefix: str = "",
) -> None:
self.package_meta = package_meta
self.release_meta = release_meta
self.release_version: Optional[RedisVersion] = None
super().__init__(name=name, log_prefix=log_prefix)

def initialise(self) -> None:
if self.release_meta.tag is None:
self.logger.error("Release tag is not set")
return
if self.release_version is not None:
return

self.feedback_message = ""
try:
self.release_version = RedisVersion.parse(self.release_meta.tag)
except ValueError as e:
self.logger.error(f"Failed to parse release tag: {e}")
return

def update(self) -> Status:
if self.release_meta.tag is None:
logger.error("Release tag is not set")
return Status.FAILURE

if (
self.package_meta.homebrew_channel is not None
and self.package_meta.release_type is not None
):
pass
else:
assert self.release_version is not None
if self.package_meta.release_type is None:
if self.release_version.is_internal:
self.package_meta.release_type = ReleaseType.INTERNAL
else:
if self.release_version.is_ga:
self.package_meta.release_type = ReleaseType.PUBLIC
elif self.release_version.is_rc:
self.package_meta.release_type = ReleaseType.PUBLIC
else:
self.package_meta.release_type = ReleaseType.INTERNAL

if self.package_meta.homebrew_channel is None:
if self.release_version.is_ga:
self.package_meta.homebrew_channel = HomebrewChannel.STABLE
else:
# RC, internal, or any other version goes to RC channel
self.package_meta.homebrew_channel = HomebrewChannel.RC
self.feedback_message = f"release_type: {self.package_meta.release_type.value}, homebrew_channel: {self.package_meta.homebrew_channel.value}"

if self.log_once(
"homebrew_channel_detected", self.package_meta.ephemeral.log_once_flags
):
self.logger.info(
f"Hombrew release_type: {self.package_meta.release_type}, homebrew_channel: {self.package_meta.homebrew_channel}"
)

return Status.SUCCESS


class ClassifyHomebrewVersion(ReleaseAction):
"""Classify Homebrew version by downloading and parsing the cask file.

This behavior downloads the appropriate Homebrew cask file (redis.rb or redis-rc.rb)
based on the homebrew_channel, extracts the version, and compares it with the
release tag version to determine if the version is acceptable.
"""

def __init__(
self,
name: str,
package_meta: HomebrewMeta,
release_meta: ReleaseMeta,
github_client: GitHubClientAsync,
log_prefix: str = "",
) -> None:
self.package_meta = package_meta
self.release_meta = release_meta
self.github_client = github_client
self.task: Optional[asyncio.Task] = None
self.release_version: Optional[RedisVersion] = None
self.cask_version: Optional[RedisVersion] = None
super().__init__(name=name, log_prefix=log_prefix)

def initialise(self) -> None:
"""Initialize by validating inputs and starting download task."""
if self.package_meta.ephemeral.is_version_acceptable is not None:
return

self.feedback_message = ""
# Validate homebrew_channel is set
if self.package_meta.homebrew_channel is None:
self.logger.error("Homebrew channel is not set")
return

# Validate repo and ref are set
if not self.package_meta.repo:
self.logger.error("Package repository is not set")
return

if not self.package_meta.ref:
self.logger.error("Package ref is not set")
return

# Parse release version from tag
if self.release_meta.tag is None:
self.logger.error("Release tag is not set")
return

if self.package_meta.release_type is None:
self.logger.error("Package release type is not set")
return

try:
self.release_version = RedisVersion.parse(self.release_meta.tag)
self.logger.debug(f"Parsed release version: {self.release_version}")
except ValueError as e:
self.logger.error(f"Failed to parse release tag: {e}")
return

# Determine which cask file to download based on channel
if self.package_meta.homebrew_channel == HomebrewChannel.STABLE:
cask_file = "Casks/redis.rb"
elif self.package_meta.homebrew_channel == HomebrewChannel.RC:
cask_file = "Casks/redis-rc.rb"
else:
self.logger.error(
f"Unknown homebrew channel: {self.package_meta.homebrew_channel}"
)
return

self.logger.debug(
f"Downloading cask file: {cask_file} from {self.package_meta.repo}@{self.package_meta.ref}"
)

# Start async task to download the cask file from package repo and ref
self.task = asyncio.create_task(
self.github_client.download_file(
self.package_meta.repo, cask_file, self.package_meta.ref
)
)

def update(self) -> Status:
"""Process downloaded cask file and classify version."""
if self.package_meta.ephemeral.is_version_acceptable is not None:
return Status.SUCCESS

try:
assert self.task is not None

# Wait for download to complete
if not self.task.done():
return Status.RUNNING

# Get the downloaded content
cask_content = self.task.result()
if cask_content is None:
self.logger.error("Failed to download cask file")
return Status.FAILURE

# Parse version from cask file
# Look for: version "X.Y.Z"
version_match = re.search(
r'^\s*version\s+"([^"]+)"', cask_content, re.MULTILINE
)
if not version_match:
self.logger.error("Could not find version declaration in cask file")
return Status.FAILURE

version_str = version_match.group(1)
self.logger.debug(f"Found version in cask file: {version_str}")

# Parse the cask version
try:
self.cask_version = RedisVersion.parse(version_str)
self.logger.info(
f"Cask version: {self.cask_version}, Release version: {self.release_version}"
)
except ValueError as e:
self.logger.error(f"Failed to parse cask version '{version_str}': {e}")
return Status.FAILURE

# Compare versions: cask version >= release version means acceptable
assert self.release_version is not None
self.package_meta.remote_version = str(self.cask_version)
log_prepend = ""
prepend_color = "green"
if self.release_version >= self.cask_version:
self.package_meta.ephemeral.is_version_acceptable = True
self.feedback_message = (
f"release {self.release_version} >= cask {self.cask_version}"
)
log_prepend = "Version acceptable: "
else:
self.package_meta.ephemeral.is_version_acceptable = False
log_prepend = "Version NOT acceptable: "
prepend_color = "yellow"
self.feedback_message = (
f"release {self.release_version} < cask {self.cask_version}"
)
if self.log_once(
"homebrew_version_classified",
self.package_meta.ephemeral.log_once_flags,
):
self.logger.info(
f"[{prepend_color}]{log_prepend}{self.feedback_message}[/]"
)
return Status.SUCCESS

except Exception as e:
return self.log_exception_and_return_failure(e)


### Conditions ###


Expand Down Expand Up @@ -1138,38 +864,6 @@ def update(self) -> Status:
return Status.SUCCESS


class DetectReleaseType(LoggingAction):
def __init__(
self,
name: str,
package_meta: PackageMeta,
release_meta: ReleaseMeta,
log_prefix: str = "",
) -> None:
self.release_meta = release_meta
self.package_meta = package_meta
super().__init__(name=name, log_prefix=log_prefix)

def update(self) -> Status:
if self.package_meta.release_type is not None:
if self.log_once(
"release_type_detected", self.package_meta.ephemeral.log_once_flags
):
self.logger.info(
f"Detected release type: {self.package_meta.release_type}"
)
return Status.SUCCESS
if self.release_meta.tag and re.search(r"-int\d*$", self.release_meta.tag):
self.package_meta.release_type = ReleaseType.INTERNAL
else:
self.package_meta.release_type = ReleaseType.PUBLIC
self.log_once(
"release_type_detected", self.release_meta.ephemeral.log_once_flags
)
self.logger.info(f"Detected release type: {self.package_meta.release_type}")
return Status.SUCCESS


class IsForceRebuild(LoggingAction):
def __init__(
self, name: str, package_meta: PackageMeta, log_prefix: str = ""
Expand All @@ -1181,21 +875,3 @@ def update(self) -> Status:
if self.package_meta.ephemeral.force_rebuild:
return Status.SUCCESS
return Status.FAILURE


class NeedToReleaseHomebrew(LoggingAction):
def __init__(
self,
name: str,
package_meta: HomebrewMeta,
release_meta: ReleaseMeta,
log_prefix: str = "",
) -> None:
self.package_meta = package_meta
self.release_meta = release_meta
super().__init__(name=name, log_prefix=log_prefix)

def update(self) -> Status:
if self.package_meta.ephemeral.is_version_acceptable is True:
return Status.SUCCESS
return Status.FAILURE
Loading