Repository: benbjohnson/litestream

Issue:
Allow 'gs' as Google Cloud Storage replica type string
I'm trying to setup LiteStream for Google Cloud Storage.
My config is quite simple

```yaml
dbs:
  - path: /path/to/prod.db
    replicas:
      - url: gs://BUCKET/prod.db
```

Got an error saying `unknown replica type in config: ""`.
Realized that LiteStream currently expects `gcs` (and not `gs`) after looking at the source.

Issues

- GCS seems to officially use `gs` in their URI [(docs)](https://cloud.google.com/bigquery-transfer/docs/cloud-storage-transfer-overview) Should Litestream not support `gs` as well?
- [Config docs](https://litestream.io/reference/config/)  only mention "s3", "abs", "file". The [gcs guide](https://litestream.io/guides/gcs/) does mention it, though it wasn't clear to me that we _had_ to use `gcs`. 
- The error message was not clear.

I'm happy to help with a PR for some of these items (maybe next week).

Discussion:
@benbjohnson: @febkor Yeah, I agree. `gs` seems like a better choice. Litestream is still pre-1.0 so I don't think it's worth trying to support both `gs` & `gcs` for backwards compatibility. If you want to PR up the change to make it `gs` then I'll approve that. 👍 @jamesnicolas: Did this get changed back to gcs? Not sure why it's gcs again instead of gs@angusjf: @jamesnicolas @benbjohnson looks like the above PR #344 was removed / missing / force pushed over in prod... could it be re-applied?

Solution patch:
diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml
index e1b2119..7a97545 100644
--- a/.github/workflows/integration_test.yml
+++ b/.github/workflows/integration_test.yml
@@ -52,10 +52,10 @@ jobs:
         env:
           GOOGLE_APPLICATION_CREDENTIALS: ${{secrets.GOOGLE_APPLICATION_CREDENTIALS}}
 
-      - run: go test -v -run=TestReplicaClient ./integration -replica-type gcs
+      - run: go test -v -run=TestReplicaClient ./integration -replica-type gs
         env:
           GOOGLE_APPLICATION_CREDENTIALS:  /opt/gcp.json
-          LITESTREAM_GCS_BUCKET:           integration.litestream.io
+          LITESTREAM_GS_BUCKET:            integration.litestream.io
 
   abs-integration-test:
     name: Run Azure Blob Store Integration Tests
diff --git a/gcs/replica_client.go b/gs/replica_client.go
similarity index 97%
rename from gcs/replica_client.go
rename to gs/replica_client.go
index 7f5d91c..efd7bc5 100644
--- a/gcs/replica_client.go
+++ b/gs/replica_client.go
@@ -1,4 +1,4 @@
-package gcs
+package gs
 
 import (
 	"context"
@@ -17,17 +17,17 @@ import (
 )
 
 // ReplicaClientType is the client type for this package.
-const ReplicaClientType = "gcs"
+const ReplicaClientType = "gs"
 
 var _ litestream.ReplicaClient = (*ReplicaClient)(nil)
 
 // ReplicaClient is a client for writing snapshots & WAL segments to disk.
 type ReplicaClient struct {
 	mu     sync.Mutex
-	client *storage.Client       // gcs client
-	bkt    *storage.BucketHandle // gcs bucket handle
+	client *storage.Client       // gs client
+	bkt    *storage.BucketHandle // gs bucket handle
 
-	// GCS bucket information
+	// GS bucket information
 	Bucket string
 	Path   string
 }
@@ -37,12 +37,12 @@ func NewReplicaClient() *ReplicaClient {
 	return &ReplicaClient{}
 }
 
-// Type returns "gcs" as the client type.
+// Type returns "gs" as the client type.
 func (c *ReplicaClient) Type() string {
 	return ReplicaClientType
 }
 
-// Init initializes the connection to GCS. No-op if already initialized.
+// Init initializes the connection to GS. No-op if already initialized.
 func (c *ReplicaClient) Init(ctx context.Context) (err error) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
diff --git a/sftp/replica_client.go b/sftp/replica_client.go
index fd0912c..8566ab0 100644
--- a/sftp/replica_client.go
+++ b/sftp/replica_client.go
@@ -52,12 +52,12 @@ func NewReplicaClient() *ReplicaClient {
 	}
 }
 
-// Type returns "gcs" as the client type.
+// Type returns "sftp" as the client type.
 func (c *ReplicaClient) Type() string {
 	return ReplicaClientType
 }
 
-// Init initializes the connection to GCS. No-op if already initialized.
+// Init initializes the connection to SFTP. No-op if already initialized.
 func (c *ReplicaClient) Init(ctx context.Context) (_ *sftp.Client, err error) {
 	c.mu.Lock()
 	defer c.mu.Unlock()



Test patch:
diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go
index 1850b46..bc75bf8 100644
--- a/cmd/litestream/main.go
+++ b/cmd/litestream/main.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/benbjohnson/litestream"
 	"github.com/benbjohnson/litestream/abs"
-	"github.com/benbjohnson/litestream/gcs"
+	"github.com/benbjohnson/litestream/gs"
 	"github.com/benbjohnson/litestream/http"
 	"github.com/benbjohnson/litestream/s3"
 	"github.com/benbjohnson/litestream/sftp"
@@ -396,8 +396,8 @@ func NewReplicaFromConfig(c *ReplicaConfig, db *litestream.DB) (_ *litestream.Re
 		if client, err = newS3ReplicaClientFromConfig(c); err != nil {
 			return nil, err
 		}
-	case "gcs":
-		if client, err = newGCSReplicaClientFromConfig(c); err != nil {
+	case "gs":
+		if client, err = newGSReplicaClientFromConfig(c); err != nil {
 			return nil, err
 		}
 	case "abs":
@@ -525,13 +525,13 @@ func newS3ReplicaClientFromConfig(c *ReplicaConfig) (_ *s3.ReplicaClient, err er
 	return client, nil
 }
 
-// newGCSReplicaClientFromConfig returns a new instance of gcs.ReplicaClient built from config.
-func newGCSReplicaClientFromConfig(c *ReplicaConfig) (_ *gcs.ReplicaClient, err error) {
+// newGSReplicaClientFromConfig returns a new instance of gs.ReplicaClient built from config.
+func newGSReplicaClientFromConfig(c *ReplicaConfig) (_ *gs.ReplicaClient, err error) {
 	// Ensure URL & constituent parts are not both specified.
 	if c.URL != "" && c.Path != "" {
-		return nil, fmt.Errorf("cannot specify url & path for gcs replica")
+		return nil, fmt.Errorf("cannot specify url & path for gs replica")
 	} else if c.URL != "" && c.Bucket != "" {
-		return nil, fmt.Errorf("cannot specify url & bucket for gcs replica")
+		return nil, fmt.Errorf("cannot specify url & bucket for gs replica")
 	}
 
 	bucket, path := c.Bucket, c.Path
@@ -554,11 +554,11 @@ func newGCSReplicaClientFromConfig(c *ReplicaConfig) (_ *gcs.ReplicaClient, err
 
 	// Ensure required settings are set.
 	if bucket == "" {
-		return nil, fmt.Errorf("bucket required for gcs replica")
+		return nil, fmt.Errorf("bucket required for gs replica")
 	}
 
 	// Build replica.
-	client := gcs.NewReplicaClient()
+	client := gs.NewReplicaClient()
 	client.Bucket = bucket
 	client.Path = path
 	return client, nil
diff --git a/cmd/litestream/main_test.go b/cmd/litestream/main_test.go
index d3d0af8..f37ff46 100644
--- a/cmd/litestream/main_test.go
+++ b/cmd/litestream/main_test.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/benbjohnson/litestream"
 	main "github.com/benbjohnson/litestream/cmd/litestream"
-	"github.com/benbjohnson/litestream/gcs"
+	"github.com/benbjohnson/litestream/gs"
 	"github.com/benbjohnson/litestream/s3"
 )
 
@@ -170,11 +170,11 @@ func TestNewS3ReplicaFromConfig(t *testing.T) {
 	})
 }
 
-func TestNewGCSReplicaFromConfig(t *testing.T) {
-	r, err := main.NewReplicaFromConfig(&main.ReplicaConfig{URL: "gcs://foo/bar"}, nil)
+func TestNewGSReplicaFromConfig(t *testing.T) {
+	r, err := main.NewReplicaFromConfig(&main.ReplicaConfig{URL: "gs://foo/bar"}, nil)
 	if err != nil {
 		t.Fatal(err)
-	} else if client, ok := r.Client().(*gcs.ReplicaClient); !ok {
+	} else if client, ok := r.Client().(*gs.ReplicaClient); !ok {
 		t.Fatal("unexpected replica type")
 	} else if got, want := client.Bucket, "foo"; got != want {
 		t.Fatalf("Bucket=%s, want %s", got, want)
diff --git a/cmd/litestream/replicate.go b/cmd/litestream/replicate.go
index 80b7359..708ca8b 100644
--- a/cmd/litestream/replicate.go
+++ b/cmd/litestream/replicate.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/benbjohnson/litestream"
 	"github.com/benbjohnson/litestream/abs"
-	"github.com/benbjohnson/litestream/gcs"
+	"github.com/benbjohnson/litestream/gs"
 	"github.com/benbjohnson/litestream/http"
 	"github.com/benbjohnson/litestream/s3"
 	"github.com/benbjohnson/litestream/sftp"
@@ -133,7 +133,7 @@ func (c *ReplicateCommand) Run(ctx context.Context) (err error) {
 				log.Printf("replicating to: name=%q type=%q path=%q", r.Name(), client.Type(), client.Path())
 			case *s3.ReplicaClient:
 				log.Printf("replicating to: name=%q type=%q bucket=%q path=%q region=%q endpoint=%q sync-interval=%s", r.Name(), client.Type(), client.Bucket, client.Path, client.Region, client.Endpoint, r.SyncInterval)
-			case *gcs.ReplicaClient:
+			case *gs.ReplicaClient:
 				log.Printf("replicating to: name=%q type=%q bucket=%q path=%q sync-interval=%s", r.Name(), client.Type(), client.Bucket, client.Path, r.SyncInterval)
 			case *abs.ReplicaClient:
 				log.Printf("replicating to: name=%q type=%q bucket=%q path=%q endpoint=%q sync-interval=%s", r.Name(), client.Type(), client.Bucket, client.Path, client.Endpoint, r.SyncInterval)
diff --git a/integration/replica_client_test.go b/integration/replica_client_test.go
index 8761965..b76034b 100644
--- a/integration/replica_client_test.go
+++ b/integration/replica_client_test.go
@@ -16,7 +16,7 @@ import (
 
 	"github.com/benbjohnson/litestream"
 	"github.com/benbjohnson/litestream/abs"
-	"github.com/benbjohnson/litestream/gcs"
+	"github.com/benbjohnson/litestream/gs"
 	"github.com/benbjohnson/litestream/s3"
 	"github.com/benbjohnson/litestream/sftp"
 )
@@ -45,8 +45,8 @@ var (
 
 // Google cloud storage settings
 var (
-	gcsBucket = flag.String("gcs-bucket", os.Getenv("LITESTREAM_GCS_BUCKET"), "")
-	gcsPath   = flag.String("gcs-path", os.Getenv("LITESTREAM_GCS_PATH"), "")
+	gsBucket = flag.String("gs-bucket", os.Getenv("LITESTREAM_GS_BUCKET"), "")
+	gsPath   = flag.String("gs-path", os.Getenv("LITESTREAM_GS_PATH"), "")
 )
 
 // Azure blob storage settings
@@ -480,8 +480,8 @@ func NewReplicaClient(tb testing.TB, typ string) litestream.ReplicaClient {
 		return litestream.NewFileReplicaClient(tb.TempDir())
 	case s3.ReplicaClientType:
 		return NewS3ReplicaClient(tb)
-	case gcs.ReplicaClientType:
-		return NewGCSReplicaClient(tb)
+	case gs.ReplicaClientType:
+		return NewGSReplicaClient(tb)
 	case abs.ReplicaClientType:
 		return NewABSReplicaClient(tb)
 	case sftp.ReplicaClientType:
@@ -508,13 +508,13 @@ func NewS3ReplicaClient(tb testing.TB) *s3.ReplicaClient {
 	return c
 }
 
-// NewGCSReplicaClient returns a new client for integration testing.
-func NewGCSReplicaClient(tb testing.TB) *gcs.ReplicaClient {
+// NewGSReplicaClient returns a new client for integration testing.
+func NewGSReplicaClient(tb testing.TB) *gs.ReplicaClient {
 	tb.Helper()
 
-	c := gcs.NewReplicaClient()
-	c.Bucket = *gcsBucket
-	c.Path = path.Join(*gcsPath, fmt.Sprintf("%016x", rand.Uint64()))
+	c := gs.NewReplicaClient()
+	c.Bucket = *gsBucket
+	c.Path = path.Join(*gsPath, fmt.Sprintf("%016x", rand.Uint64()))
 	return c
 }