From 9a8b3be0c4d443a6149ba434f57661b00ca27862 Mon Sep 17 00:00:00 2001 From: Ellion Blessan Date: Mon, 20 Apr 2026 03:05:15 +0700 Subject: [PATCH] Add Google Cloud Storage Support for Active Storage (#1523) * feat: add gcs for activestorage service provider * docs: add gcs keys to .env.example --- .env.example | 10 ++++++++- .env.local.example | 7 ++++++ Gemfile | 1 + Gemfile.lock | 55 ++++++++++++++++++++++++++++++++++++++++++++++ config/storage.yml | 10 +++++++-- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index b11153b27..64d0f4810 100644 --- a/.env.example +++ b/.env.example @@ -145,7 +145,7 @@ POSTHOG_HOST= # Active Storage Configuration - responsible for storing file uploads # ====================================================================================================== # -# * Defaults to disk storage but you can also use Amazon S3 or Cloudflare R2 +# * Defaults to disk storage but you can also use Amazon S3, Cloudflare R2, or Google Cloud Storage # * Set the appropriate environment variables to use these services. # * Ensure libvips is installed on your system for image processing - https://github.com/libvips/libvips # @@ -174,3 +174,11 @@ POSTHOG_HOST= # GENERIC_S3_BUCKET= # GENERIC_S3_ENDPOINT= # GENERIC_S3_FORCE_PATH_STYLE= <- defaults to false +# +# Google Cloud Storage +# ==================== +# ACTIVE_STORAGE_SERVICE=google <- Enables Google Cloud Storage +# GCS_PROJECT= +# GCS_BUCKET= +# GCS_KEYFILE_JSON= <- JSON content of service account key (preferred) +# GCS_KEYFILE= <- path to service account JSON key file diff --git a/.env.local.example b/.env.local.example index 1795cc8b9..e03ce605f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -95,3 +95,10 @@ AI_DEBUG_MODE = # SSL_DEBUG: "true" # volumes: # - ./my-ca.crt:/certs/my-ca.crt:ro + +# Active Storage Configuration +# ACTIVE_STORAGE_SERVICE=google +# GCS_PROJECT= +# GCS_BUCKET= +# GCS_KEYFILE_JSON= +# GCS_KEYFILE= diff --git a/Gemfile b/Gemfile index de23ef7d8..a49de166f 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,7 @@ gem "skylight", groups: [ :production ] # Active Storage gem "aws-sdk-s3", "~> 1.208.0", require: false +gem "google-cloud-storage", "~> 1.59", require: false gem "image_processing", ">= 1.2" # Other diff --git a/Gemfile.lock b/Gemfile.lock index b962e3d28..e4210d504 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -158,6 +158,7 @@ GEM debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) + declarative (0.0.20) derailed_benchmarks (2.2.1) base64 benchmark-ips (~> 2) @@ -177,6 +178,8 @@ GEM ruby2_keywords thor (>= 0.19, < 2) diff-lcs (1.6.2) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) docile (1.4.1) doorkeeper (5.8.2) railties (>= 5) @@ -231,6 +234,43 @@ GEM ffi (~> 1.0) globalid (1.3.0) activesupport (>= 6.1) + google-apis-core (1.0.2) + addressable (~> 2.8, >= 2.8.7) + faraday (~> 2.13) + faraday-follow_redirects (~> 0.3) + googleauth (~> 1.14) + mini_mime (~> 1.1) + representable (~> 3.0) + retriable (~> 3.1) + google-apis-iamcredentials_v1 (0.26.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.61.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.3.1) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.6.0) + google-cloud-storage (1.59.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + google-logging-utils (0.2.0) + googleauth (1.16.2) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) hashdiff (1.2.0) hashery (2.1.2) hashie (5.0.0) @@ -361,6 +401,7 @@ GEM mocha (2.7.1) ruby2_keywords (>= 0.0.5) msgpack (1.8.0) + multi_json (1.20.1) multi_xml (0.8.0) bigdecimal (>= 3.1, < 5) multipart-post (2.4.1) @@ -441,6 +482,7 @@ GEM tzinfo validate_url webfinger (~> 2.0) + os (1.1.4) ostruct (0.6.2) pagy (9.3.5) parallel (1.27.0) @@ -563,6 +605,11 @@ GEM regexp_parser (2.10.0) reline (0.6.1) io-console (~> 0.5) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.4.1) rexml (3.4.2) rotp (6.3.0) rouge (4.5.2) @@ -681,6 +728,11 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 7.0.0, < 9.0.0) thor (>= 1.0, < 3.0) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -720,6 +772,7 @@ GEM unicode-display_width (>= 1.1.1, < 4) thor (1.4.0) timeout (0.6.1) + trailblazer-option (0.1.2) tsort (0.2.0) ttfunk (1.8.0) bigdecimal (~> 3.1) @@ -728,6 +781,7 @@ GEM railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + uber (0.1.0) unaccent (0.4.0) unicode (0.4.4.5) unicode-display_width (3.1.4) @@ -804,6 +858,7 @@ DEPENDENCIES faraday-multipart faraday-retry foreman + google-cloud-storage (~> 1.59) hotwire-livereload hotwire_combobox httparty diff --git a/config/storage.yml b/config/storage.yml index aa9050108..9c3f21ddb 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -8,7 +8,7 @@ test: amazon: service: S3 - access_key_id: <%= ENV["S3_ACCESS_KEY_ID"] %> + access_key_id: <%= ENV["S3_ACCESS_KEY_ID"] %> secret_access_key: <%= ENV["S3_SECRET_ACCESS_KEY"] %> region: <%= ENV["S3_REGION"] || "us-east-1" %> bucket: <%= ENV["S3_BUCKET"] %> @@ -25,9 +25,15 @@ cloudflare: generic_s3: service: S3 - access_key_id: <%= ENV["GENERIC_S3_ACCESS_KEY_ID"] %> + access_key_id: <%= ENV["GENERIC_S3_ACCESS_KEY_ID"] %> secret_access_key: <%= ENV["GENERIC_S3_SECRET_ACCESS_KEY"] %> region: <%= ENV["GENERIC_S3_REGION"] %> bucket: <%= ENV["GENERIC_S3_BUCKET"] %> endpoint: <%= ENV["GENERIC_S3_ENDPOINT"] %> force_path_style: <%= ActiveModel::Type::Boolean.new.cast(ENV.fetch("GENERIC_S3_FORCE_PATH_STYLE", "false")) %> + +google: + service: GCS + project: <%= ENV["GCS_PROJECT"] %> + credentials: <%= ENV.fetch("GCS_KEYFILE_JSON") { ENV["GCS_KEYFILE"] } %> + bucket: <%= ENV["GCS_BUCKET"] %>