Skip to content

Commit

Permalink
Merge pull request #106 from rafsaf/add-azure-upload-provider
Browse files Browse the repository at this point in the history
add azure upload provider
  • Loading branch information
rafsaf authored Aug 27, 2023
2 parents 1c8f1f7 + 7b72fee commit eac4d72
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ A tool for performing scheduled database backups and transferring encrypted data

- Google Cloud Storage bucket
- AWS S3 bucket
- Azure Blob Storage
- Debug (local)

## Notifications
Expand Down
1 change: 1 addition & 0 deletions backuper/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class UploadProviderEnum(StrEnum):
LOCAL_FILES_DEBUG = "debug"
GOOGLE_CLOUD_STORAGE = "gcs"
AWS_S3 = "aws"
AZURE = "azure"


class BackupTargetEnum(StrEnum):
Expand Down
5 changes: 5 additions & 0 deletions backuper/models/upload_provider_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ class AWSProviderModel(ProviderModel):
key_secret: SecretStr
region: str
max_bandwidth: int | None = None


class AzureProviderModel(ProviderModel):
container_name: str
connect_string: SecretStr
6 changes: 4 additions & 2 deletions backuper/upload_providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from .aws_s3 import UploadProviderAWS
from .azure import UploadProviderAzure
from .base_provider import BaseUploadProvider
from .google_cloud_storage import UploadProviderGCS
from .debug import UploadProviderLocalDebug
from .aws_s3 import UploadProviderAWS
from .google_cloud_storage import UploadProviderGCS

__all__ = [
"BaseUploadProvider",
"UploadProviderAWS",
"UploadProviderGCS",
"UploadProviderLocalDebug",
"UploadProviderAzure",
]
92 changes: 92 additions & 0 deletions backuper/upload_providers/azure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import logging
from pathlib import Path

from azure.storage.blob import BlobServiceClient
from pydantic import SecretStr

from backuper import config, core
from backuper.upload_providers.base_provider import BaseUploadProvider

log = logging.getLogger(__name__)


class UploadProviderAzure(
BaseUploadProvider,
name=config.UploadProviderEnum.AZURE,
):
"""Azure blob storage for storing backups"""

def __init__(
self,
container_name: str,
connect_string: SecretStr,
**kwargs: str,
) -> None:
self.container_name = container_name
self.connect_str = connect_string

blob_service_client = BlobServiceClient.from_connection_string(
connect_string.get_secret_value()
)
self.container_client = blob_service_client.get_container_client(
container=self.container_name
)

def _post_save(self, backup_file: Path) -> str:
zip_backup_file = core.run_create_zip_archive(backup_file=backup_file)

backup_dest_in_azure_container = "{}/{}".format(
zip_backup_file.parent.name,
zip_backup_file.name,
)
blob_client = self.container_client.get_blob_client(
blob=backup_dest_in_azure_container
)

log.info(
"start uploading %s to %s", zip_backup_file, backup_dest_in_azure_container
)

with open(file=zip_backup_file, mode="rb") as data:
blob_client.upload_blob(data=data)

log.info(
"uploaded %s to %s in %s",
zip_backup_file,
backup_dest_in_azure_container,
self.container_name,
)
return backup_dest_in_azure_container

def _clean(
self, backup_file: Path, max_backups: int, min_retention_days: int
) -> None:
for backup_path in backup_file.parent.iterdir():
core.remove_path(backup_path)
log.info("removed %s from local disk", backup_path)

backup_list_cloud: list[str] = []
for blob in self.container_client.list_blobs(
name_starts_with=backup_file.parent.name
):
backup_list_cloud.append(blob.name)

# remove oldest
backup_list_cloud.sort(reverse=True)

while len(backup_list_cloud) > max_backups:
backup_to_remove = backup_list_cloud.pop()
file_name = backup_to_remove.split("/")[-1]
if core.file_before_retention_period_ends(
backup_name=file_name, min_retention_days=min_retention_days
):
log.info(
"there are more backups than max_backups (%s/%s), "
"but oldest cannot be removed due to min retention days",
len(backup_list_cloud),
max_backups,
)
break

self.container_client.delete_blob(blob=backup_to_remove)
log.info("deleted backup %s from azure blob storage", backup_to_remove)
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ A tool for performing scheduled database backups and transferring encrypted data

- Google Cloud Storage bucket
- AWS S3 bucket
- Azure Blob Storage
- Debug (local)

## Notifications
Expand Down
46 changes: 46 additions & 0 deletions docs/providers/azure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
hide:
- toc
---

# Azure Blob Storage

## Environment variable

```bash
BACKUP_PROVIDER="name=azure container_name=my-backuper-instance connect_string=DefaultEndpointsProtocol=https;AccountName=accname;AccountKey=secret;EndpointSuffix=core.windows.net"
```

Uses Azure Blob Storage for storing backups.

!!! note
_There can be only one upload provider defined per app, using **BACKUP_PROVIDER** environemnt variable_. It's type is guessed by using `name`, in this case `name=azure`. Params must be included in value, splited by single space for example "value1=1 value2=foo".

## Params

| Name | Type | Description | Default |
| :------------- | :------------------- | :----------------------------------------------------------------------------------------------- | :------ |
| name | string[**requried**] | Must be set literaly to string `azure` to use Google Cloud Storage. | - |
| container_name | string[**requried**] | Storage account container name. It must be already created, backuper won't create new container. | - |
| connect_string | string[**requried**] | Connection string copied from your storage account "Access keys" section. | - |

## Examples

```bash
# 1. Storage account accname and container name my-backuper-instance
BACKUP_PROVIDER="name=azure container_name=my-backuper-instance connect_string=DefaultEndpointsProtocol=https;AccountName=accname;AccountKey=secret;EndpointSuffix=core.windows.net"

# 2. Storage account birds and container name birds
BACKUP_PROVIDER="name=azure container_name=birds connect_string=DefaultEndpointsProtocol=https;AccountName=birds;AccountKey=secret;EndpointSuffix=core.windows.net"
```


## Resources

#### Creating azure storage account

[https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal)


<br>
<br>
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ nav:
- Upload providers:
- providers/google_cloud_storage.md
- providers/aws_s3.md
- providers/azure.md
- providers/debug.md
- Backup targets:
- backup_targets/postgresql.md
Expand Down
Loading

0 comments on commit eac4d72

Please sign in to comment.