Skip to content

Commit c824744

Browse files
committed
Custom release_tags support
Make all packages ignore unknown tags Introduce specific meta types for docker Add module_versions into args and docker meta Make docker accept arbitrary release tag
1 parent 7769984 commit c824744

File tree

10 files changed

+289
-125
lines changed

10 files changed

+289
-125
lines changed

src/redis_release/bht/behaviours_docker.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1+
from pydoc import Doc
12
from typing import Optional
23

34
from py_trees.common import Status
45

5-
from redis_release.bht.behaviours import LoggingAction, ReleaseAction
6-
from redis_release.bht.state import PackageMeta, ReleaseMeta, Workflow
7-
from redis_release.models import RedisVersion, ReleaseType
6+
from ..models import RedisVersion, ReleaseType
7+
from .behaviours import LoggingAction, ReleaseAction
8+
from .state import DockerMeta, ReleaseMeta, Workflow
89

910

1011
class DockerWorkflowInputs(ReleaseAction):
11-
"""
12-
Docker uses only release_tag input which is set automatically in TriggerWorkflow
13-
"""
14-
1512
def __init__(
1613
self,
1714
name: str,
1815
workflow: Workflow,
19-
package_meta: PackageMeta,
16+
package_meta: DockerMeta,
2017
release_meta: ReleaseMeta,
2118
log_prefix: str = "",
2219
) -> None:
@@ -26,6 +23,9 @@ def __init__(
2623
super().__init__(name=name, log_prefix=log_prefix)
2724

2825
def update(self) -> Status:
26+
if self.package_meta.module_versions is not None:
27+
for module, version in self.package_meta.module_versions.items():
28+
self.workflow.inputs[f"{module.value}_version"] = version
2929
return Status.SUCCESS
3030

3131

@@ -35,7 +35,7 @@ class DetectReleaseTypeDocker(LoggingAction):
3535
def __init__(
3636
self,
3737
name: str,
38-
package_meta: PackageMeta,
38+
package_meta: DockerMeta,
3939
release_meta: ReleaseMeta,
4040
log_prefix: str = "",
4141
) -> None:
@@ -52,7 +52,14 @@ def initialise(self) -> None:
5252
return
5353
if self.release_meta.tag == "unstable":
5454
return
55-
self.release_version = RedisVersion.parse(self.release_meta.tag)
55+
try:
56+
self.release_version = RedisVersion.parse(self.release_meta.tag)
57+
except ValueError as e:
58+
if self.release_meta.tag != "":
59+
self.logger.info(
60+
f"Failed to parse release tag: {e}, assuming custom release with tag {self.release_meta.tag}"
61+
)
62+
return
5663

5764
def update(self) -> Status:
5865
result: Status = Status.FAILURE
@@ -70,8 +77,9 @@ def update(self) -> Status:
7077
f"Detected release type for docker: {self.package_meta.release_type}"
7178
)
7279
else:
73-
self.feedback_message = "Failed to detect release type"
74-
result = Status.FAILURE
80+
self.package_meta.release_type = ReleaseType.INTERNAL
81+
self.feedback_message = "Set release type to internal for custom build"
82+
result = Status.SUCCESS
7583

7684
if self.log_once(
7785
"release_type_detected", self.package_meta.ephemeral.log_once_flags
@@ -92,7 +100,7 @@ class NeedToReleaseDocker(LoggingAction):
92100
def __init__(
93101
self,
94102
name: str,
95-
package_meta: PackageMeta,
103+
package_meta: DockerMeta,
96104
release_meta: ReleaseMeta,
97105
log_prefix: str = "",
98106
) -> None:
@@ -123,9 +131,6 @@ def update(self) -> Status:
123131
if self.release_meta.tag is None:
124132
self.feedback_message = "Release tag is not set"
125133
result = Status.FAILURE
126-
if self.release_meta.tag == "unstable":
127-
self.feedback_message = "Skip unstable release for docker"
128-
result = Status.FAILURE
129134

130135
if self.release_version is not None:
131136
if self.release_version.major < 8:
@@ -138,6 +143,9 @@ def update(self) -> Status:
138143
f"Need to release docker version {str(self.release_version)}"
139144
)
140145
result = Status.SUCCESS
146+
else:
147+
self.feedback_message = "Custom build, need to release"
148+
result = Status.SUCCESS
141149

142150
if self.log_once("need_to_release", self.package_meta.ephemeral.log_once_flags):
143151
color_open = "" if result == Status.SUCCESS else "[yellow]"

src/redis_release/bht/behaviours_homebrew.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ def update(self) -> Status:
8282
self.logger.info(self.feedback_message)
8383
return Status.SUCCESS
8484

85+
if self.release_version is None and self.release_meta.tag != "":
86+
self.logger.info(
87+
f"Release version is not set, skipping probably custom release {self.release_meta.tag}"
88+
)
89+
return Status.SUCCESS
90+
8591
assert self.release_version is not None
8692
if self.package_meta.release_type is None:
8793
if self.release_version.is_internal:
@@ -147,6 +153,12 @@ def initialise(self) -> None:
147153
self.package_meta.remote_version = "unstable"
148154
return
149155

156+
if self.release_meta.tag != "":
157+
self.package_meta.ephemeral.is_version_acceptable = False
158+
# we need to set remote version to not None as it is a sign of successful classify step
159+
self.package_meta.remote_version = "custom"
160+
return
161+
150162
self.feedback_message = ""
151163
# Validate homebrew_channel is set
152164
if self.package_meta.homebrew_channel is None:

src/redis_release/bht/behaviours_snap.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ def update(self) -> Status:
8686
self.package_meta.release_type = ReleaseType.PUBLIC
8787
self.package_meta.snap_risk_level = SnapRiskLevel.EDGE
8888
else:
89+
if self.release_version is None and self.release_meta.tag != "":
90+
self.logger.info(
91+
f"Release version is not set, skipping probably custom release: {self.release_meta.tag}"
92+
)
93+
return Status.SUCCESS
8994
assert self.release_version is not None
9095
if self.package_meta.release_type is None:
9196
if self.release_version.is_internal:
@@ -144,6 +149,18 @@ def initialise(self) -> None:
144149
if self.package_meta.ephemeral.is_version_acceptable is not None:
145150
return
146151

152+
# TODO: don't like this expresion, should be better way to handle custom releases skipping
153+
if (
154+
self.release_meta.tag is not None
155+
and self.release_meta.tag != ""
156+
and self.release_meta.tag != "unstable"
157+
and self.package_meta.snap_risk_level is None
158+
):
159+
self.package_meta.ephemeral.is_version_acceptable = False
160+
# we need to set remote version to not None as it is a sign of successful classify step
161+
self.package_meta.remote_version = "custom"
162+
return
163+
147164
self.feedback_message = ""
148165
# Validate snap_risk_level is set
149166
if self.package_meta.snap_risk_level is None:

src/redis_release/bht/state.py

Lines changed: 83 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,60 @@
1+
"""State of release - central data model for the release process.
2+
3+
Used by the behavior tree to keep track of the release progress, plays
4+
blackboard role for behavior tree.
5+
6+
Ephemeral and persistent fields
7+
--------------------------------
8+
9+
The main purpose of ephemeral fields is to prevent retry loops and to allow
10+
extensive status reporting.
11+
12+
Each workflow step has a pair of fields indicating the step status:
13+
One ephemeral field is set when the step is attempted. It may have four states:
14+
- `None` (default): Step has not been attempted
15+
- `common.Status.RUNNING`: Step is currently running
16+
- `common.Status.FAILURE`: Step has been attempted and failed
17+
- `common.Status.SUCCESS`: Step has been attempted and succeeded
18+
19+
Ephemeral fields are reset on each run. Their values are persisted but only until
20+
next run is started.
21+
So they indicate either current (if run is in progress) or last run state.
22+
23+
The other field indicates the step result, it may either have some value or be empty.
24+
This field is persisted across runs.
25+
26+
For example for trigger step we have `trigger_workflow` ephemeral
27+
and `triggered_at` result fields.
28+
29+
Optional message field may be used to provide additional information about the step.
30+
For example wait_for_completion_message may contain information about timeout.
31+
32+
Given combination of ephemeral and result fields we can determine step status.
33+
Each step may be in one of the following states:
34+
Not started
35+
Failed
36+
Succeeded or OK
37+
Incorrect (this shouln't happen)
38+
39+
The following decision table show how step status is determined for trigger step.
40+
In general this is applicable to all steps.
41+
42+
tigger_workflow -> | None (default) | Running | Failure | Success |
43+
triggered_at: | | | | |
44+
None | Not started | In progress | Failed | Incorrect |
45+
Has value | OK | Incorrect | Incorrect | OK |
46+
47+
The result field (triggered_at in this case) should not be set while step is
48+
running, if step was not started or if it's failed.
49+
And it should be set if trigger_workflow is successful.
50+
It may be set if trigger_workflow is None, which is the case when release
51+
process was restarted and all ephemeral fields are reset, but the particular
52+
step was successful in previous run.
53+
54+
Correct values are not eforced it's up to the implementation to correctly
55+
set the fields.
56+
"""
57+
158
import json
259
import logging
360
from datetime import datetime
@@ -8,72 +65,25 @@
865
from py_trees.common import Status
966
from pydantic import BaseModel, Field
1067

11-
from redis_release.models import (
68+
from ..config import Config, PackageConfig
69+
from ..models import (
1270
HomebrewChannel,
1371
PackageType,
72+
RedisModule,
1473
ReleaseType,
1574
SnapRiskLevel,
1675
WorkflowConclusion,
1776
WorkflowStatus,
1877
WorkflowType,
1978
)
2079

21-
from ..config import Config, PackageConfig
22-
2380
logger = logging.getLogger(__name__)
2481

25-
SUPPORTED_STATE_VERSION = 3
82+
SUPPORTED_STATE_VERSION = 4
2683

2784

2885
class WorkflowEphemeral(BaseModel):
29-
"""Ephemeral workflow state. Reset on each run.
30-
31-
The main purpose of ephemeral fields is to prevent retry loops and to allow extensive status reporting.
32-
33-
Each workflow step has a pair of fields indicating the step status:
34-
One ephemeral field is set when the step is attempted. It may have four states:
35-
- `None` (default): Step has not been attempted
36-
- `common.Status.RUNNING`: Step is currently running
37-
- `common.Status.FAILURE`: Step has been attempted and failed
38-
- `common.Status.SUCCESS`: Step has been attempted and succeeded
39-
40-
Ephemeral fields are reset on each run. Their values are persisted but only until
41-
next run is started.
42-
So they indicate either current (if run is in progress) or last run state.
43-
44-
The other field indicates the step result, it may either have some value or be empty.
45-
46-
For example for trigger step we have `trigger_workflow` ephemeral
47-
and `triggered_at` result fields.
48-
49-
Optional message field may be used to provide additional information about the step.
50-
For example wait_for_completion_message may contain information about timeout.
51-
52-
Given combination of ephemeral and result fields we can determine step status.
53-
Each step may be in one of the following states:
54-
Not started
55-
Failed
56-
Succeeded or OK
57-
Incorrect (this shouln't happen)
58-
59-
The following decision table show how step status is determined for trigger step.
60-
In general this is applicable to all steps.
61-
62-
tigger_workflow -> | None (default) | Running | Failure | Success |
63-
triggered_at: | | | | |
64-
None | Not started | In progress | Failed | Incorrect |
65-
Has value | OK | Incorrect | Incorrect | OK |
66-
67-
The result field (triggered_at in this case) should not be set while step is
68-
running, if step was not started or if it's failed.
69-
And it should be set if trigger_workflow is successful.
70-
It may be set if trigger_workflow is None, which is the case when release
71-
process was restarted and all ephemeral fields are reset, but the particular
72-
step was successful in previous run.
73-
74-
Correct values are not eforced it's up to the implementation to correctly
75-
set the fields.
76-
"""
86+
"""Ephemeral workflow state. Reset on each run."""
7787

7888
identify_workflow: Optional[common.Status] = None
7989
trigger_workflow: Optional[common.Status] = None
@@ -138,6 +148,10 @@ class SnapMetaEphemeral(PackageMetaEphemeral):
138148
pass
139149

140150

151+
class DockerMetaEphemeral(PackageMetaEphemeral):
152+
pass
153+
154+
141155
class PackageMeta(BaseModel):
142156
"""Metadata for a package (base/generic type)."""
143157

@@ -176,6 +190,14 @@ class SnapMeta(PackageMeta):
176190
ephemeral: SnapMetaEphemeral = Field(default_factory=SnapMetaEphemeral) # type: ignore[assignment]
177191

178192

193+
class DockerMeta(PackageMeta):
194+
"""Metadata for Docker package."""
195+
196+
serialization_hint: Literal["docker"] = "docker" # type: ignore[assignment]
197+
module_versions: Optional[Dict[RedisModule, str]] = None
198+
ephemeral: DockerMetaEphemeral = Field(default_factory=DockerMetaEphemeral) # type: ignore[assignment]
199+
200+
179201
class Package(BaseModel):
180202
"""State for a package in the release.
181203
@@ -186,7 +208,7 @@ class Package(BaseModel):
186208
- serialization_hint="snap" -> SnapMeta
187209
"""
188210

189-
meta: Union[HomebrewMeta, SnapMeta, PackageMeta] = Field(
211+
meta: Union[HomebrewMeta, SnapMeta, PackageMeta, DockerMeta] = Field(
190212
default_factory=PackageMeta, discriminator="serialization_hint"
191213
)
192214
build: Workflow = Field(default_factory=Workflow)
@@ -227,7 +249,7 @@ def _create_package_meta_from_config(
227249
package_config: Package configuration
228250
229251
Returns:
230-
PackageMeta subclass instance (HomebrewMeta, SnapMeta, or PackageMeta)
252+
PackageMeta subclass instance (HomebrewMeta, SnapMeta, DockerMeta or PackageMeta)
231253
232254
Raises:
233255
ValueError: If package_type is None
@@ -246,6 +268,13 @@ def _create_package_meta_from_config(
246268
package_type=package_config.package_type,
247269
publish_internal_release=package_config.publish_internal_release,
248270
)
271+
elif package_config.package_type == PackageType.DOCKER:
272+
return DockerMeta(
273+
repo=package_config.repo,
274+
ref=package_config.ref,
275+
package_type=package_config.package_type,
276+
publish_internal_release=package_config.publish_internal_release,
277+
)
249278
elif package_config.package_type is not None:
250279
return PackageMeta(
251280
repo=package_config.repo,

0 commit comments

Comments
 (0)