Skip to content

Commit

Permalink
Merge pull request #10 from essentialkaos/develop
Browse files Browse the repository at this point in the history
Version 0.0.2
  • Loading branch information
andyone authored Apr 4, 2024
2 parents c49bc5b + 75f4364 commit dbbe822
Show file tree
Hide file tree
Showing 17 changed files with 1,126 additions and 172 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/docker-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
Expand Down Expand Up @@ -136,7 +136,7 @@ jobs:
- name: Build and push Docker images (Docker)
if: ${{ steps.build_check.outputs.build == 'true' }}
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
push: true
context: .
Expand All @@ -149,7 +149,7 @@ jobs:
- name: Build and push Docker images (GHCR)
if: ${{ steps.build_check.outputs.build == 'true' }}
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
push: true
context: .
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
atlassian-cloud-backuper
atlassian-cloud-backuper.zip
vendor
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ GITREV ?= $(shell test -s $(MAKEDIR)/.git && git rev-parse --short HEAD)

all: atlassian-cloud-backuper ## Build all binaries

pack: clean ## Pack source to zip file
go build cloudfunc/ycfunc.go && rm -f ycfunc
zip atlassian-cloud-backuper -r * -x ".git/*" "vendor/*" "common/*" "atlassian-cloud-backuper.go" "*.md"

atlassian-cloud-backuper:
go build $(VERBOSE_FLAG) -ldflags="-X main.gitrev=$(GITREV)" atlassian-cloud-backuper.go

Expand Down Expand Up @@ -85,11 +89,12 @@ vet: ## Runs 'go vet' over sources

clean: ## Remove generated files
rm -f atlassian-cloud-backuper
rm -f atlassian-cloud-backuper.zip

help: ## Show this info
@echo -e '\n\033[1mTargets:\033[0m\n'
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[33m%-26s\033[0m %s\n", $$1, $$2}'
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[33m%-10s\033[0m %s\n", $$1, $$2}'
@echo -e '\n\033[1mVariables:\033[0m\n'
@grep -E '^ifdef [A-Z_]+ .*?## .*$$' $(abspath $(lastword $(MAKEFILE_LIST))) \
| sed 's/ifdef //' \
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ bash <(curl -fsSL https://apps.kaos.st/get) atlassian-cloud-backuper

The latest version of `atlassian-cloud-backuper` also available as container image on [GitHub Container Registry](https://kaos.sh/p/atlassian-cloud-backuper) and [Docker Hub](https://kaos.sh/d/atlassian-cloud-backuper).

#### Cloud/serverless function

You can use `atlassian-cloud-backuper` as a serverless function on Yandex.Cloud. More information about function configuration can be found in [this documentation](cloudfunc/README.md).

### Usage

#### Standalone
Expand Down Expand Up @@ -62,7 +66,6 @@ Options
--no-color, -nc Disable colors in output
--help, -h Show this help message
--version, -v Show version
--access-account name Account name (ACCESS_ACCOUNT)
--access-email email User email with access to API (ACCESS_EMAIL)
--access-api-key key API key (ACCESS_API_KEY)
Expand All @@ -75,6 +78,7 @@ Options
--storage-sftp-path path Path on SFTP (STORAGE_SFTP_PATH)
--storage-sftp-mode mode File mode on SFTP (STORAGE_SFTP_MODE)
--storage-s3-host host S3 host (STORAGE_S3_HOST)
--storage-s3-region region S3 region (STORAGE_S3_REGION)
--storage-s3-access-key id S3 access key ID (STORAGE_S3_ACCESS_KEY)
--storage-s3-secret-key key S3 access secret key (STORAGE_S3_SECRET_KEY)
--storage-s3-bucket name S3 bucket (STORAGE_S3_BUCKET)
Expand All @@ -90,6 +94,12 @@ Options
--log-level level Log level (LOG_LEVEL)
```

You can force output of this information by passing `container` to `--help` option:

```bash
atlassian-cloud-backuper --help container
```

### CI Status

| Branch | Status |
Expand Down
26 changes: 21 additions & 5 deletions backuper/backuper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package backuper

import (
"fmt"
"io"

"github.com/essentialkaos/ek/v12/events"
)
Expand All @@ -26,11 +27,29 @@ const (

// Backuper is generic backuper interface
type Backuper interface {
// Backup starts backup process
Backup() error
// Backup runs backup process
Backup(outputFile string) error

// SetDispatcher sets events dispatcher
SetDispatcher(d *events.Dispatcher)

// Start creates task for backuping data
Start() (string, error)

// Progress monitors backup creation progress
Progress(taskID string) (string, error)

// Download downloads backup file
Download(backupFile, outputFile string) error

// GetReader returns reader for given backup file
GetReader(backupFile string) (io.ReadCloser, error)

// GetBackupFile returns name of created backup file
GetBackupFile() (string, error)

// IsBackupCreated returns true if backup created and ready for download
IsBackupCreated() (bool, error)
}

// ////////////////////////////////////////////////////////////////////////////////// //
Expand All @@ -40,7 +59,6 @@ type Config struct {
Account string
Email string
APIKey string
OutputFile string
WithAttachments bool
ForCloud bool
}
Expand Down Expand Up @@ -70,8 +88,6 @@ func (c Config) Validate() error {
return ErrEmptyEmail
case c.APIKey == "":
return ErrEmptyAPIKey
case c.OutputFile == "":
return ErrEmptyOutputFile
}

return nil
Expand Down
122 changes: 94 additions & 28 deletions backuper/confluence/confluence-backuper.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ type BackupProgressInfo struct {

// ////////////////////////////////////////////////////////////////////////////////// //

// validate backuper interface
var _ backuper.Backuper = (*ConfluenceBackuper)(nil)

// ////////////////////////////////////////////////////////////////////////////////// //

func NewBackuper(config *backuper.Config) (*ConfluenceBackuper, error) {
err := config.Validate()

Expand All @@ -69,30 +74,51 @@ func (b *ConfluenceBackuper) SetDispatcher(d *events.Dispatcher) {
}

// Backup starts backup process
func (b *ConfluenceBackuper) Backup() error {
var err error
var backupFile string
func (b *ConfluenceBackuper) Backup(outputFile string) error {
_, err := b.Start()

if err != nil {
return err
}

backupFileURL, err := b.Progress("")

if err != nil {
return err
}

return b.Download(backupFileURL, outputFile)
}

// Start creates task for backuping data
func (b *ConfluenceBackuper) Start() (string, error) {
log.Info("Starting Confluence backup process for account %s…", b.config.Account)
log.Info("Checking for existing backup task…")

start := time.Now()
info, _ := b.getBackupProgress()

if info != nil && !info.IsOutdated {
log.Info("Found previously created backup task")
} else {
err = b.startBackup()
err := b.startBackup()

if err != nil {
return fmt.Errorf("Can't start backup: %w", err)
return "", fmt.Errorf("Can't start backup: %w", err)
}
}

b.dispatcher.DispatchAndWait(backuper.EVENT_BACKUP_STARTED, nil)

return "", nil
}

// Progress monitors backup creation progress
func (b *ConfluenceBackuper) Progress(taskID string) (string, error) {
var backupFileURL string

errNum := 0
lastProgress := ""
start := time.Now()

for range time.NewTicker(15 * time.Second).C {
progressInfo, err := b.getBackupProgress()
Expand All @@ -102,14 +128,14 @@ func (b *ConfluenceBackuper) Backup() error {
errNum++

if errNum > 10 {
return fmt.Errorf("Can't download backup: too much errors")
return "", fmt.Errorf("Can't download backup: too much errors")
}
} else {
errNum = 0
}

if time.Since(start) > 6*time.Hour {
return fmt.Errorf("Can't download backup: backup task took too much time")
return "", fmt.Errorf("Can't download backup: backup task took too much time")
}

b.dispatcher.Dispatch(backuper.EVENT_BACKUP_PROGRESS, b.convertProgressInfo(progressInfo))
Expand All @@ -124,17 +150,44 @@ func (b *ConfluenceBackuper) Backup() error {
}

if progressInfo.Size != 0 && progressInfo.Filename != "" {
backupFile = progressInfo.Filename
backupFileURL = progressInfo.Filename
break
}
}

return backupFileURL, nil
}

// IsBackupCreated returns true if backup created and ready for download
func (b *ConfluenceBackuper) IsBackupCreated() (bool, error) {
progressInfo, err := b.getBackupProgress()

if err != nil {
return false, err
}

return progressInfo.Size != 0 && progressInfo.Filename != "", nil
}

// GetBackupFile returns name of created backup file
func (b *ConfluenceBackuper) GetBackupFile() (string, error) {
progressInfo, err := b.getBackupProgress()

if err != nil {
return "", err
}

return progressInfo.Filename, nil
}

// Download downloads backup file
func (b *ConfluenceBackuper) Download(backupFile, outputFile string) error {
log.Info("Backup is ready for download, fetching file…")
log.Info("Writing backup file into %s", b.config.OutputFile)
log.Info("Writing backup file into %s", outputFile)

b.dispatcher.DispatchAndWait(backuper.EVENT_BACKUP_SAVING, nil)

err = b.downloadBackup(backupFile)
err := b.downloadBackup(backupFile, outputFile)

if err != nil {
return err
Expand All @@ -144,12 +197,36 @@ func (b *ConfluenceBackuper) Backup() error {

log.Info(
"Backup successfully saved (size: %s)",
fmtutil.PrettySize(fsutil.GetSize(b.config.OutputFile)),
fmtutil.PrettySize(fsutil.GetSize(outputFile)),
)

return nil
}

// GetReader returns reader for given backup file
func (b *ConfluenceBackuper) GetReader(backupFile string) (io.ReadCloser, error) {
backupFileURL := b.config.AccountURL() + "/wiki/download/" + backupFile

log.Debug("Downloading file from %s", backupFileURL)

resp, err := req.Request{
URL: backupFileURL,
BasicAuthUsername: b.config.Email,
BasicAuthPassword: b.config.APIKey,
AutoDiscard: true,
}.Get()

if err != nil {
return nil, err
}

if resp.StatusCode != 200 {
return nil, fmt.Errorf("API returned non-ok status code (%d)", resp.StatusCode)
}

return resp.Body, nil
}

// ////////////////////////////////////////////////////////////////////////////////// //

// startBackup starts backup process
Expand Down Expand Up @@ -224,27 +301,16 @@ func (b *ConfluenceBackuper) convertProgressInfo(i *BackupProgressInfo) *backupe
}

// downloadBackup downloads backup and saves it as a file
func (b *ConfluenceBackuper) downloadBackup(backupFile string) error {
backupFileURL := b.config.AccountURL() + "/wiki/download/" + backupFile

log.Debug("Downloading file from %s", backupFileURL)

resp, err := req.Request{
URL: backupFileURL,
BasicAuthUsername: b.config.Email,
BasicAuthPassword: b.config.APIKey,
AutoDiscard: true,
}.Get()
func (b *ConfluenceBackuper) downloadBackup(backupFile, outputFile string) error {
r, err := b.GetReader(backupFile)

if err != nil {
return err
}

if resp.StatusCode != 200 {
return fmt.Errorf("API returned non-ok status code (%d)", resp.StatusCode)
}
defer r.Close()

fd, err := os.OpenFile(b.config.OutputFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
fd, err := os.OpenFile(outputFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)

if err != nil {
return fmt.Errorf("Can't open file for saving data: %w", err)
Expand All @@ -253,7 +319,7 @@ func (b *ConfluenceBackuper) downloadBackup(backupFile string) error {
defer fd.Close()

w := bufio.NewWriter(fd)
_, err = io.Copy(w, resp.Body)
_, err = io.Copy(w, r)

if err != nil {
return fmt.Errorf("File writing error: %w", err)
Expand Down
Loading

0 comments on commit dbbe822

Please sign in to comment.