feat: add UpdateVersionAction
This commit is contained in:
parent
152546982c
commit
839f67ad0a
8 changed files with 260 additions and 139 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -88,7 +88,6 @@ celerybeat-schedule
|
|||
celerybeat.pid
|
||||
*.sage.py
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
|
|
|
|||
|
|
@ -1,24 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: huesoporro
|
||||
appVersion: 0.3.0
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
name: huesoporro
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.3.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "0.3.0"
|
||||
|
|
|
|||
|
|
@ -1,138 +1,62 @@
|
|||
# Default values for helm.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
|
||||
replicaCount: 1
|
||||
|
||||
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
|
||||
image:
|
||||
repository: git.roboces.dev/catalin/huesoporro
|
||||
# This sets the pull policy for images.
|
||||
pullPolicy: Always
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: "0.3.0"
|
||||
|
||||
# This is for the secretes for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||
imagePullSecrets: []
|
||||
# This is to override the chart name.
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
#This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Automatically mount a ServiceAccount's API credentials?
|
||||
automount: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
# This is for setting Kubernetes Annotations to a Pod.
|
||||
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||
podAnnotations: {}
|
||||
# This is for setting Kubernetes Labels to a Pod.
|
||||
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
podLabels: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
|
||||
service:
|
||||
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
|
||||
type: LoadBalancer
|
||||
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
|
||||
port: 8000
|
||||
|
||||
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
||||
ingress:
|
||||
affinity: {}
|
||||
autoscaling:
|
||||
enabled: false
|
||||
className: ""
|
||||
maxReplicas: 100
|
||||
minReplicas: 1
|
||||
targetCPUUtilizationPercentage: 80
|
||||
fullnameOverride: ''
|
||||
image:
|
||||
pullPolicy: Always
|
||||
repository: git.roboces.dev/catalin/huesoporro
|
||||
tag: 0.3.0
|
||||
imagePullSecrets: []
|
||||
ingress:
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
className: ''
|
||||
enabled: false
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
nameOverride: ''
|
||||
nodeSelector: {}
|
||||
persistence:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
annotations: {}
|
||||
enabled: false
|
||||
size: 10Gi
|
||||
storageClassName: default
|
||||
volumeOwner:
|
||||
enabled: true
|
||||
gid: 1000
|
||||
uid: 1000
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
podSecurityContext: {}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
|
||||
#This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
# Additional volumes on the output Deployment definition.
|
||||
volumes: []
|
||||
# - name: foo
|
||||
# secret:
|
||||
# secretName: mysecret
|
||||
# optional: false
|
||||
|
||||
# Additional volumeMounts on the output Deployment definition.
|
||||
volumeMounts: []
|
||||
# - name: foo
|
||||
# mountPath: "/etc/foo"
|
||||
# readOnly: true
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
persistence:
|
||||
enabled: false
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
size: 10Gi
|
||||
storageClassName: "default"
|
||||
volumeOwner:
|
||||
enabled: true
|
||||
uid: 1000
|
||||
gid: 1000
|
||||
annotations: {}
|
||||
|
||||
replicaCount: 1
|
||||
resources: {}
|
||||
secret:
|
||||
existingSecretName: huesoporro-secrets
|
||||
securityContext: {}
|
||||
service:
|
||||
port: 8000
|
||||
type: LoadBalancer
|
||||
serviceAccount:
|
||||
annotations: {}
|
||||
automount: true
|
||||
create: true
|
||||
name: ''
|
||||
tolerations: []
|
||||
volumeMounts: []
|
||||
volumes: []
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ dev-dependencies = [
|
|||
"ruff>=0.8.3",
|
||||
"pytest-coverage>=0.0",
|
||||
"polyfactory>=2.18.1",
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from loguru import logger
|
|||
from typer import Typer
|
||||
|
||||
from huesoporro.actions.import_from_vod import ImportFromVODAction
|
||||
from huesoporro.actions.misc.update_version_action import UpdateVersionAction
|
||||
from huesoporro.settings import Settings
|
||||
from huesoporro.svc.clean_cc_svc import CleanCCSvc
|
||||
from huesoporro.svc.download_closed_captions import DownloadClosedCaptionsSvc
|
||||
|
|
@ -22,3 +23,8 @@ def import_vod_cc(channel_name: str, youtube_url: str, db_path: Path | None = No
|
|||
)
|
||||
for cc_filepath in import_from_vod_action.run(channel_name, youtube_url):
|
||||
logger.info(f"Closed captions imported from {cc_filepath}")
|
||||
|
||||
|
||||
@app.command()
|
||||
def update_version(version: str, dry_run: bool = False):
|
||||
UpdateVersionAction().run(version, dry_run)
|
||||
|
|
|
|||
0
src/huesoporro/actions/misc/__init__.py
Normal file
0
src/huesoporro/actions/misc/__init__.py
Normal file
198
src/huesoporro/actions/misc/update_version_action.py
Normal file
198
src/huesoporro/actions/misc/update_version_action.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import re
|
||||
from collections.abc import Callable
|
||||
from difflib import unified_diff
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from rich import print # noqa: A004
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.syntax import Syntax
|
||||
|
||||
|
||||
class UpdateVersionAction(BaseModel):
|
||||
project_root: Path = Path(__file__).parents[4]
|
||||
files_to_update: dict[str, Callable]
|
||||
console: Console = Console()
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
def __init__(self, **data):
|
||||
files_to_update = {
|
||||
"pyproject.toml": self._update_pyproject_toml,
|
||||
"charts/huesoporro/values.yaml": self._update_values_yaml,
|
||||
"charts/huesoporro/Chart.yaml": self._update_chart_yaml,
|
||||
}
|
||||
super().__init__(**data, files_to_update=files_to_update)
|
||||
|
||||
def _read_file(self, filepath: Path) -> str:
|
||||
"""
|
||||
Read the contents of a file.
|
||||
|
||||
Args:
|
||||
filepath (Path): Path to the file to read.
|
||||
|
||||
Returns:
|
||||
str: File contents
|
||||
"""
|
||||
with filepath.open("r") as f:
|
||||
return f.read()
|
||||
|
||||
def _write_file(self, filepath: Path, content: str):
|
||||
"""
|
||||
Write content to a file.
|
||||
|
||||
Args:
|
||||
filepath (Path): Path to the file to write.
|
||||
content (str): Content to write to the file.
|
||||
"""
|
||||
with filepath.open("w") as f:
|
||||
f.write(content)
|
||||
|
||||
def _update_pyproject_toml(self, filepath: Path, new_version: str) -> str:
|
||||
"""
|
||||
Update version in pyproject.toml.
|
||||
|
||||
Args:
|
||||
filepath (Path): Path to pyproject.toml
|
||||
new_version (str): New version to set
|
||||
|
||||
Returns:
|
||||
str: Updated file content
|
||||
"""
|
||||
content = self._read_file(filepath)
|
||||
version_pattern = r'(version\s*=\s*)[\'"](.+?)[\'"]'
|
||||
return re.sub(version_pattern, rf'\1"{new_version}"', content)
|
||||
|
||||
def _update_values_yaml(self, filepath: Path, new_version: str) -> str:
|
||||
"""
|
||||
Update image tag in values.yaml.
|
||||
|
||||
Args:
|
||||
filepath (Path): Path to values.yaml
|
||||
new_version (str): New version to set
|
||||
|
||||
Returns:
|
||||
str: Updated file content
|
||||
"""
|
||||
with filepath.open("r") as file:
|
||||
values = yaml.safe_load(file)
|
||||
|
||||
# Assumes image.tag exists in the values.yaml
|
||||
values["image"]["tag"] = new_version
|
||||
|
||||
return yaml.dump(values, default_flow_style=False)
|
||||
|
||||
def _update_chart_yaml(self, filepath: Path, new_version: str) -> str:
|
||||
"""
|
||||
Update version and appVersion in Chart.yaml.
|
||||
|
||||
Args:
|
||||
filepath (Path): Path to Chart.yaml
|
||||
new_version (str): New version to set
|
||||
|
||||
Returns:
|
||||
str: Updated file content
|
||||
"""
|
||||
with filepath.open("r") as file:
|
||||
chart_data = yaml.safe_load(file)
|
||||
|
||||
chart_data["version"] = new_version
|
||||
chart_data["appVersion"] = new_version
|
||||
|
||||
return yaml.dump(chart_data, default_flow_style=False)
|
||||
|
||||
def _generate_diff(self, original: str, updated: str, filename: str) -> str:
|
||||
"""
|
||||
Generate a unified diff between original and updated content.
|
||||
|
||||
Args:
|
||||
original (str): Original file content
|
||||
updated (str): Updated file content
|
||||
filename (str): Name of the file
|
||||
|
||||
Returns:
|
||||
str: Unified diff representation
|
||||
"""
|
||||
# Split content into lines
|
||||
original_lines = original.splitlines(keepends=True)
|
||||
updated_lines = updated.splitlines(keepends=True)
|
||||
|
||||
# Generate unified diff
|
||||
diff_lines = list(
|
||||
unified_diff(
|
||||
original_lines,
|
||||
updated_lines,
|
||||
fromfile=f"a/{filename}",
|
||||
tofile=f"b/{filename}",
|
||||
lineterm="",
|
||||
)
|
||||
)
|
||||
|
||||
return "\n".join(diff_lines)
|
||||
|
||||
def _rich_display_diff(self, diff: str):
|
||||
"""
|
||||
Display diff using rich for colorful output.
|
||||
|
||||
Args:
|
||||
diff (str): Unified diff to display
|
||||
"""
|
||||
if not diff:
|
||||
return
|
||||
|
||||
# Use Syntax for syntax highlighting
|
||||
syntax = Syntax(diff, "diff", theme="ansi_dark")
|
||||
|
||||
# Create a panel with the diff
|
||||
panel = Panel(
|
||||
syntax, title="Version Update Diff", border_style="cyan", expand=False
|
||||
)
|
||||
|
||||
# Display the panel
|
||||
self.console.print(panel)
|
||||
|
||||
def run(self, new_version: str, dry_run: bool = False):
|
||||
"""
|
||||
Update version across all specified files.
|
||||
|
||||
Args:
|
||||
new_version (str): New version to set
|
||||
dry_run (bool): Dry run mode with diff display
|
||||
"""
|
||||
for relative_path, update_func in self.files_to_update.items():
|
||||
filepath = self.project_root / relative_path
|
||||
|
||||
if not filepath.exists():
|
||||
logger.warning(f"Warning: {filepath} not found. Skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Read original content
|
||||
original_content = self._read_file(filepath)
|
||||
|
||||
# Generate updated content
|
||||
updated_content = update_func(filepath, new_version)
|
||||
|
||||
if dry_run:
|
||||
# Generate and display diff
|
||||
diff = self._generate_diff(
|
||||
original_content, updated_content, str(relative_path)
|
||||
)
|
||||
|
||||
# Display the diff
|
||||
if diff:
|
||||
print(f"\nDiff for {relative_path}:")
|
||||
self._rich_display_diff(diff)
|
||||
else:
|
||||
# Write updated content
|
||||
self._write_file(filepath, updated_content)
|
||||
print(f"Updated {relative_path}")
|
||||
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error(f"Error updating {relative_path}: {exc}")
|
||||
|
||||
if dry_run:
|
||||
print("\nDry run complete. No files were modified.")
|
||||
11
uv.lock
generated
11
uv.lock
generated
|
|
@ -549,6 +549,7 @@ dev = [
|
|||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-coverage" },
|
||||
{ name = "ruff" },
|
||||
{ name = "types-pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
|
|
@ -582,6 +583,7 @@ dev = [
|
|||
{ name = "pytest-asyncio", specifier = ">=0.25.0" },
|
||||
{ name = "pytest-coverage", specifier = ">=0.0" },
|
||||
{ name = "ruff", specifier = ">=0.8.3" },
|
||||
{ name = "types-pyyaml", specifier = ">=6.0.12.20241230" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1482,6 +1484,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20241230"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue