Skip to content

Commit

Permalink
NAS-132068 / 25.04 / Convert cloud_backup to new api style (#15024)
Browse files Browse the repository at this point in the history
* convert to new api

* add defaults
  • Loading branch information
creatorcary authored Nov 25, 2024
1 parent 7d85d22 commit ec6e6b2
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 78 deletions.
4 changes: 3 additions & 1 deletion src/middlewared/middlewared/api/base/types/base/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from typing_extensions import Annotated

from middlewared.utils.lang import undefined
from middlewared.validators import Time

__all__ = ["HttpUrl", "LongString", "NonEmptyString", "LongNonEmptyString", "SECRET_VALUE"]
__all__ = ["HttpUrl", "LongString", "NonEmptyString", "LongNonEmptyString", "SECRET_VALUE", "TimeString"]

HttpUrl = Annotated[_HttpUrl, AfterValidator(str)]

Expand Down Expand Up @@ -55,5 +56,6 @@ def __get_pydantic_core_schema__(

NonEmptyString = Annotated[str, Field(min_length=1)]
LongNonEmptyString = Annotated[LongString, Field(min_length=1)]
TimeString = Annotated[str, AfterValidator(Time())]

SECRET_VALUE = "********"
1 change: 1 addition & 0 deletions src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .auth import * # noqa
from .boot_environments import * # noqa
from .catalog import * # noqa
from .cloud_backup import * # noqa
from .cloud_sync import * # noqa
from .common import * # noqa
from .core import * # noqa
Expand Down
174 changes: 174 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/cloud_backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
from datetime import datetime
from typing import Literal

from pydantic import Field, PositiveInt, Secret

from middlewared.api.base import BaseModel, ForUpdateMetaclass, LongString, NonEmptyString
from .cloud_sync import CloudCredentialEntry
from .common import CronModel


__all__ = [
"CloudBackupEntry", "CloudBackupTransferSettingChoicesArgs", "CloudBackupTransferSettingChoicesResult",
"CloudBackupCreateArgs", "CloudBackupCreateResult", "CloudBackupUpdateArgs", "CloudBackupUpdateResult",
"CloudBackupDeleteArgs", "CloudBackupDeleteResult", "CloudBackupRestoreArgs", "CloudBackupRestoreResult",
"CloudBackupListSnapshotsArgs", "CloudBackupListSnapshotsResult", "CloudBackupListSnapshotDirectoryArgs",
"CloudBackupListSnapshotDirectoryResult", "CloudBackupDeleteSnapshotArgs", "CloudBackupDeleteSnapshotResult",
"CloudBackupSyncArgs", "CloudBackupSyncResult", "CloudBackupAbortArgs", "CloudBackupAbortResult",
]


class CloudBackupCron(CronModel):
minute: str = "00"


class CloudBackupCreate(BaseModel):
description: str = ""
path: str
credentials: int
attributes: dict
schedule: CloudBackupCron
pre_script: LongString = ""
post_script: LongString = ""
snapshot: bool = False
include: list[NonEmptyString]
exclude: list[NonEmptyString]
args: LongString = ""
enabled: bool = True

password: Secret[NonEmptyString]
keep_last: PositiveInt
transfer_setting: Literal["DEFAULT", "PERFORMANCE", "FAST_STORAGE"] = "DEFAULT"


class CloudBackupEntry(CloudBackupCreate):
id: int
credentials: CloudCredentialEntry
job: dict | None
locked: bool


class CloudBackupUpdate(CloudBackupCreate, metaclass=ForUpdateMetaclass):
pass


class CloudBackupRestoreOptions(BaseModel):
exclude: list[str] = []
include: list[str] = []


class CloudBackupSnapshot(BaseModel):
id: str
hostname: str
time: datetime
paths: list[str]

class Config:
extra = "allow"


class CloudBackupSnapshotItem(BaseModel):
name: str
path: str
type: Literal["dir", "file"]
size: int
mtime: datetime

class Config:
extra = "allow"


class CloudBackupSyncOptions(BaseModel):
dry_run: bool = False


############### Args and Results ###############


class CloudBackupTransferSettingChoicesArgs(BaseModel):
pass


class CloudBackupTransferSettingChoicesResult(BaseModel):
result: list[Literal["DEFAULT", "PERFORMANCE", "FAST_STORAGE"]]


class CloudBackupCreateArgs(BaseModel):
cloud_backup: CloudBackupCreate


class CloudBackupCreateResult(BaseModel):
result: CloudBackupEntry


class CloudBackupUpdateArgs(BaseModel):
id_: int
data: CloudBackupUpdate


class CloudBackupUpdateResult(BaseModel):
result: CloudBackupEntry


class CloudBackupDeleteArgs(BaseModel):
id_: int


class CloudBackupDeleteResult(BaseModel):
result: Literal[True]


class CloudBackupRestoreArgs(BaseModel):
id_: int
snapshot_id: str = Field(pattern=r"^[^-]")
subfolder: str
destination_path: str
options: CloudBackupRestoreOptions


class CloudBackupRestoreResult(BaseModel):
result: None


class CloudBackupListSnapshotsArgs(BaseModel):
id_: int


class CloudBackupListSnapshotsResult(BaseModel):
result: list[CloudBackupSnapshot]


class CloudBackupListSnapshotDirectoryArgs(BaseModel):
id_: int
snapshot_id: str = Field(pattern=r"^[^-]")
path: str = Field(pattern=r"^[^-]")


class CloudBackupListSnapshotDirectoryResult(BaseModel):
result: list[CloudBackupSnapshotItem]


class CloudBackupDeleteSnapshotArgs(BaseModel):
id_: int
snapshot_id: str = Field(pattern=r"^[^-]")


class CloudBackupDeleteSnapshotResult(BaseModel):
result: None


class CloudBackupSyncArgs(BaseModel):
id_: int
options: CloudBackupSyncOptions = Field(default_factory=CloudBackupSyncOptions)


class CloudBackupSyncResult(BaseModel):
result: None


class CloudBackupAbortArgs(BaseModel):
id_: int


class CloudBackupAbortResult(BaseModel):
result: bool
8 changes: 8 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ class QueryArgs(BaseModel):

class GenericQueryResult(BaseModel):
result: list[dict] | dict | int


class CronModel(BaseModel):
minute: str = "*"
hour: str = "*"
dom: str = "*"
month: str = "*"
dow: str = "*"
36 changes: 13 additions & 23 deletions src/middlewared/middlewared/plugins/cloud_backup/crud.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from middlewared.alert.base import Alert, AlertCategory, AlertClass, AlertLevel, OneShotAlertClass
from middlewared.api import api_method
from middlewared.api.current import (
CloudBackupEntry, CloudBackupTransferSettingChoicesArgs, CloudBackupTransferSettingChoicesResult,
CloudBackupCreateArgs, CloudBackupCreateResult, CloudBackupUpdateArgs, CloudBackupUpdateResult,
CloudBackupDeleteArgs, CloudBackupDeleteResult
)
from middlewared.common.attachment import LockableFSAttachmentDelegate
from middlewared.plugins.cloud.crud import CloudTaskServiceMixin
from middlewared.plugins.cloud.model import CloudTaskModelMixin, cloud_task_schema
from middlewared.schema import accepts, Bool, Cron, Dict, Int, Password, Patch, Str
from middlewared.plugins.cloud.model import CloudTaskModelMixin
from middlewared.schema import Cron
from middlewared.service import pass_app, private, TaskPathService, ValidationErrors
import middlewared.sqlalchemy as sa
from middlewared.utils.path import FSLocation
from middlewared.utils.service.task_state import TaskStateMixin
from middlewared.validators import Range
from .init import IncorrectPassword


Expand All @@ -33,15 +38,7 @@ class Config:
cli_namespace = "task.cloud_backup"
namespace = "cloud_backup"
role_prefix = "CLOUD_BACKUP"

ENTRY = Patch(
'cloud_backup_create',
'cloud_backup_entry',
('add', Int('id')),
("replace", Dict("credentials", additional_attrs=True, private_keys=["provider"])),
("add", Dict("job", additional_attrs=True, null=True)),
("add", Bool("locked")),
)
entry = CloudBackupEntry

@private
def transfer_setting_args(self):
Expand Down Expand Up @@ -81,19 +78,12 @@ async def _compress(self, cloud_backup):

return cloud_backup

@accepts()
@api_method(CloudBackupTransferSettingChoicesArgs, CloudBackupTransferSettingChoicesResult)
def transfer_setting_choices(self):
args = self.transfer_setting_args()
return list(args.keys())

@accepts(Dict(
"cloud_backup_create",
*cloud_task_schema,
Password("password", required=True, empty=False),
Int("keep_last", required=True, validators=[Range(min_=1)]),
Str("transfer_setting", enum=["DEFAULT", "PERFORMANCE", "FAST_STORAGE"], default="DEFAULT"),
register=True,
))
@api_method(CloudBackupCreateArgs, CloudBackupCreateResult)
@pass_app(rest=True)
async def do_create(self, app, cloud_backup):
"""
Expand All @@ -109,7 +99,7 @@ async def do_create(self, app, cloud_backup):

return await self.get_instance(cloud_backup["id"])

@accepts(Int("id"), Patch("cloud_backup_create", "cloud_backup_update", ("attr", {"update": True})))
@api_method(CloudBackupUpdateArgs, CloudBackupUpdateResult)
@pass_app(rest=True)
async def do_update(self, app, id_, data):
"""
Expand All @@ -136,7 +126,7 @@ async def do_update(self, app, id_, data):

return await self.get_instance(id_)

@accepts(Int("id"))
@api_method(CloudBackupDeleteArgs, CloudBackupDeleteResult)
async def do_delete(self, id_):
"""
Deletes cloud backup entry `id`.
Expand Down
16 changes: 3 additions & 13 deletions src/middlewared/middlewared/plugins/cloud_backup/restore.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from middlewared.api import api_method
from middlewared.api.current import CloudBackupRestoreArgs, CloudBackupRestoreResult
from middlewared.async_validators import check_path_resides_within_volume
from middlewared.plugins.cloud_backup.restic import get_restic_config, run_restic
from middlewared.schema import accepts, Dict, Int, List, Str
from middlewared.service import job, Service, ValidationErrors
from middlewared.validators import NotMatch


class CloudBackupService(Service):
Expand All @@ -11,17 +11,7 @@ class Config:
cli_namespace = "task.cloud_backup"
namespace = "cloud_backup"

@accepts(
Int("id"),
Str("snapshot_id", validators=[NotMatch(r"^-")]),
Str("subfolder"),
Str("destination_path"),
Dict(
"options",
List("exclude", items=[Str("item")]),
List("include", items=[Str("item")]),
),
)
@api_method(CloudBackupRestoreArgs, CloudBackupRestoreResult)
@job(logs=True)
async def restore(self, job, id_, snapshot_id, subfolder, destination_path, options):
"""
Expand Down
39 changes: 8 additions & 31 deletions src/middlewared/middlewared/plugins/cloud_backup/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import json
import subprocess

from middlewared.api import api_method
from middlewared.api.current import (
CloudBackupListSnapshotsArgs, CloudBackupListSnapshotsResult, CloudBackupListSnapshotDirectoryArgs,
CloudBackupListSnapshotDirectoryResult, CloudBackupDeleteSnapshotArgs, CloudBackupDeleteSnapshotResult
)
from middlewared.plugins.cloud_backup.restic import get_restic_config
from middlewared.schema import accepts, Datetime, Dict, Int, List, returns, Str
from middlewared.service import CallError, job, Service
from middlewared.validators import NotMatch


class CloudBackupService(Service):
Expand All @@ -14,19 +17,7 @@ class Config:
cli_namespace = "task.cloud_backup"
namespace = "cloud_backup"

@accepts(Int("id"))
@returns(
List("cloud_backup_snapshots", items=[
Dict(
"cloud_backup_snapshot",
Str("id"),
Str("hostname"),
Datetime("time"),
List("paths", items=[Str("path")]),
additional_attrs=True,
),
]),
)
@api_method(CloudBackupListSnapshotsArgs, CloudBackupListSnapshotsResult)
def list_snapshots(self, id_):
"""
List existing snapshots for the cloud backup job `id`.
Expand All @@ -53,20 +44,7 @@ def list_snapshots(self, id_):

return snapshots

@accepts(Int("id"), Str("snapshot_id", validators=[NotMatch(r"^-")]), Str("path", validators=[NotMatch(r"^-")]))
@returns(
List("cloud_backup_snapshot_items", items=[
Dict(
"cloud_backup_snapshot_item",
Str("name"),
Str("path"),
Str("type", enum=["dir", "file"]),
Int("size"),
Datetime("mtime"),
additional_attrs=True,
),
]),
)
@api_method(CloudBackupListSnapshotDirectoryArgs, CloudBackupListSnapshotDirectoryResult)
def list_snapshot_directory(self, id_, snapshot_id, path):
"""
List files in the directory `path` of the `snapshot_id` created by the cloud backup job `id`.
Expand Down Expand Up @@ -100,8 +78,7 @@ def list_snapshot_directory(self, id_, snapshot_id, path):

return contents

@accepts(Int("id"), Str("snapshot_id", validators=[NotMatch(r"^-")]))
@returns()
@api_method(CloudBackupDeleteSnapshotArgs, CloudBackupDeleteSnapshotResult)
@job(lock=lambda args: "cloud_backup:{}".format(args[-1]), lock_queue_size=1)
def delete_snapshot(self, job, id_, snapshot_id):
"""
Expand Down
Loading

0 comments on commit ec6e6b2

Please sign in to comment.