Skip to content

feat: Add Google Drive storage backend with custom endpoint support #613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/backup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ type Config struct {
DropboxAppSecret string `split_words:"true"`
DropboxRemotePath string `split_words:"true"`
DropboxConcurrencyLevel NaturalNumber `split_words:"true" default:"6"`
GoogleDriveCredentialsJSON string `split_words:"true"`
GoogleDriveFolderID string `split_words:"true"`
GoogleDriveImpersonateSubject string `split_words:"true"`
GoogleDriveEndpoint string `split_words:"true"`
GoogleDriveTokenURL string `split_words:"true"`
source string
additionalEnvVars map[string]string
}
Expand Down
29 changes: 23 additions & 6 deletions cmd/backup/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/offen/docker-volume-backup/internal/storage"
"github.com/offen/docker-volume-backup/internal/storage/azure"
"github.com/offen/docker-volume-backup/internal/storage/dropbox"
"github.com/offen/docker-volume-backup/internal/storage/googledrive"
"github.com/offen/docker-volume-backup/internal/storage/local"
"github.com/offen/docker-volume-backup/internal/storage/s3"
"github.com/offen/docker-volume-backup/internal/storage/ssh"
Expand Down Expand Up @@ -59,12 +60,13 @@ func newScript(c *Config) *script {
StartTime: time.Now(),
LogOutput: logBuffer,
Storages: map[string]StorageStats{
"S3": {},
"WebDAV": {},
"SSH": {},
"Local": {},
"Azure": {},
"Dropbox": {},
"S3": {},
"WebDAV": {},
"SSH": {},
"Local": {},
"Azure": {},
"Dropbox": {},
"GoogleDrive": {},
},
},
}
Expand Down Expand Up @@ -225,6 +227,21 @@ func (s *script) init() error {
s.storages = append(s.storages, dropboxBackend)
}

if s.c.GoogleDriveCredentialsJSON != "" {
googleDriveConfig := googledrive.Config{
CredentialsJSON: s.c.GoogleDriveCredentialsJSON,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it also possible to pass the JSON as a string instead of as a file location here? In case yes, we could leverage the existing configuration mechanism so that:

  1. People can pass a JSON string to GOOGLE_DRIVE_CREDENTIALS_JSON if they don't want to / can't mount the file
  2. People can pass a file location using GOOGLE_DRIVE_CREDENTIALS_JSON_FILE which is automatically provided by the current configuration package

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to aim to only accept FILE, as it is an oddly long string to be putting on an envvar, but gemini got lost at some point and added it for me.
Also, I know _FILE is a pattern everywhere, but I have not seen it on other variables here, should we add it?

Let me know if you think just the JSON is enough, or if you prefer both so I an either remove that logic or fix the envvars and docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I know _FILE is a pattern everywhere, but I have not seen it on other variables here, should we add it?

_FILE is supported automatically. If you define FOO_BAR, FOO_BAR_FILE will be accepted as well. I would also think passing the entire JSON blob as an env var will rarely be done, but like this, it'd be consistent with what's already there.

FolderID: s.c.GoogleDriveFolderID,
ImpersonateSubject: s.c.GoogleDriveImpersonateSubject,
Endpoint: s.c.GoogleDriveEndpoint,
TokenURL: s.c.GoogleDriveTokenURL,
}
googleDriveBackend, err := googledrive.NewStorageBackend(googleDriveConfig, logFunc)
if err != nil {
return errwrap.Wrap(err, "error creating googledrive storage backend")
}
s.storages = append(s.storages, googleDriveBackend)
}

if s.c.EmailNotificationRecipient != "" {
emailURL := fmt.Sprintf(
"smtp://%s:%s@%s:%d/?from=%s&to=%s",
Expand Down
39 changes: 39 additions & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,45 @@ The values for each key currently match its default.

# DROPBOX_REFRESH_TOKEN=""

########### GOOGLE DRIVE STORAGE

# The JSON credentials for a Google service account with access to Google Drive. The value should be the path to a json file.
# Example: '/creds/google-credentials.json'
#
# GOOGLE_DRIVE_CREDENTIALS_JSON=""

# ---

# The ID of the Google Drive folder where backups will be uploaded.
# You can find the folder ID in the URL when viewing the folder in Google Drive.
#
# Example: "1A2B3C4D5E6F7G8H9I0J"
#
# GOOGLE_DRIVE_FOLDER_ID=""

# ---

# The email address of the user to impersonate when accessing Google Drive (domain-wide delegation).
# This is required becasue your service account needs to act on behalf of a user in your organization in order to upload files.
# How to: https://support.google.com/a/answer/162106
# Example: "user@example.com"
#
# GOOGLE_DRIVE_IMPERSONATE_SUBJECT=""

# ---

# (Optional) Custom Google Drive API endpoint. This is primarily for testing with a mock server.
# Example: "http://localhost:8080/drive/v3"
#
# GOOGLE_DRIVE_ENDPOINT=""

# ---

# (Optional) Custom token URL for Google Drive authentication. This is primarily for testing with a mock server.
# Example: "http://localhost:8080/token"
#
# GOOGLE_DRIVE_TOKEN_URL=""

########### LOCAL FILE STORAGE

# In addition to storing backups remotely, you can also keep local copies.
Expand Down
24 changes: 15 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ require (
github.com/pkg/sftp v1.13.9
github.com/robfig/cron/v3 v3.0.1
github.com/studio-b12/gowebdav v0.10.0
golang.org/x/crypto v0.38.0
golang.org/x/crypto v0.39.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
google.golang.org/api v0.242.0
mvdan.cc/sh/v3 v3.12.0
)

require (
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
Expand All @@ -40,23 +44,25 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/tinylib/msgp v1.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
)

require (
Expand Down Expand Up @@ -87,8 +93,8 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/text v0.26.0 // indirect
gotest.tools/v3 v3.0.3 // indirect
)
Loading
Loading