From cab4b2fb2880251e1ff301a645d59a22d8bf28b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Turbelin?= Date: Thu, 11 Dec 2025 22:09:09 +0100 Subject: [PATCH 1/2] Add a new cloud storage configuration for s3-compatible alternatives --- app/models/application_record.rb | 5 +++- config/application.rb | 11 ++++++- .../initializers/content_security_policy.rb | 2 +- config/storage.yml | 17 +++++++++++ spec/models/application_record_spec.rb | 30 +++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 spec/models/application_record_spec.rb diff --git a/app/models/application_record.rb b/app/models/application_record.rb index daccc28cf0..212f623112 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -12,7 +12,10 @@ class ApplicationRecord < ActiveRecord::Base self.include_root_in_json = true def self.image_service - ENV["S3_BUCKET"].present? ? :amazon_public : :local + return :local if ENV["S3_BUCKET"].blank? + return :amazon_public if ENV["S3_ENDPOINT"].blank? + + :s3_compatible_storage_public end # We might have a development environment without S3 but with a database diff --git a/config/application.rb b/config/application.rb index 6de448f6eb..5071eab71b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -211,7 +211,16 @@ module Openfoodnetwork Rails.autoloaders.main.ignore(Rails.root.join('app/webpacker')) - config.active_storage.service = ENV["S3_BUCKET"].present? ? :amazon : :local + config.active_storage.service = + if ENV["S3_BUCKET"].present? + if ENV["S3_ENDPOINT"].present? + :s3_compatible_storage + else + :amazon + end + else + :local + end config.active_storage.content_types_to_serve_as_binary -= ["image/svg+xml"] config.active_storage.variable_content_types += ["image/svg+xml"] config.active_storage.url_options = config.action_controller.default_url_options diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 6b0cb839ae..f12339ccc1 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -7,7 +7,7 @@ Rails.application.config.content_security_policy do |policy| policy.default_src :self, :https policy.font_src :self, :https, :data, "fonts.gstatic.com" - policy.img_src :self, :https, :data, "*.s3.amazonaws.com" + policy.img_src :self, :https, :data, ENV.fetch("S3_CORS_POLICY_DOMAIN", "*.s3.amazonaws.com") policy.img_src :self, :http, :data, ENV["SITE_URL"] if Rails.env.development? policy.object_src :none policy.frame_ancestors :none diff --git a/config/storage.yml b/config/storage.yml index 8e0bb583a8..5a134df4e9 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -23,3 +23,20 @@ amazon_public: secret_access_key: <%= ENV["S3_SECRET"] %> bucket: <%= ENV["S3_BUCKET"] %> region: <%= ENV.fetch("S3_REGION", "us-east-1") %> + +s3_compatible_storage: + service: S3 + endpoint: <%= ENV["S3_ENDPOINT"] %> + access_key_id: <%= ENV["S3_ACCESS_KEY"] %> + secret_access_key: <%= ENV["S3_SECRET"] %> + bucket: <%= ENV["S3_BUCKET"] %> + region: <%= ENV["S3_REGION"] %> + +s3_compatible_storage_public: + service: S3 + public: true + endpoint: <%= ENV["S3_ENDPOINT"] %> + access_key_id: <%= ENV["S3_ACCESS_KEY"] %> + secret_access_key: <%= ENV["S3_SECRET"] %> + bucket: <%= ENV["S3_BUCKET"] %> + region: <%= ENV["S3_REGION"] %> diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb new file mode 100644 index 0000000000..0c1737ae5d --- /dev/null +++ b/spec/models/application_record_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ApplicationRecord do + describe ".image_service" do + subject { described_class.image_service } + + it { is_expected.to eq(:local) } + + context "with a S3 bucket defined" do + before do + expect(ENV).to receive(:[]).with("S3_BUCKET").and_return("test-bucket") + expect(ENV).to receive(:[]).with("S3_ENDPOINT").and_return(nil) + end + + it { is_expected.to eq(:amazon_public) } + end + + context "with a S3 bucket and endpoint defined" do + before do + expect(ENV).to receive(:[]).with("S3_BUCKET").and_return("test-bucket") + expect(ENV).to receive(:[]).with("S3_ENDPOINT") + .and_return("https://s3-compatible-alternative.com") + end + + it { is_expected.to eq(:s3_compatible_storage_public) } + end + end +end From 252943e9de1eae08147cbc42e4fd13bc041139a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Turbelin?= Date: Fri, 12 Dec 2025 14:45:01 +0100 Subject: [PATCH 2/2] Adjust context using allow method for Application Record spec --- spec/models/application_record_spec.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index 0c1737ae5d..c07138b9f1 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -10,21 +10,20 @@ RSpec.describe ApplicationRecord do context "with a S3 bucket defined" do before do - expect(ENV).to receive(:[]).with("S3_BUCKET").and_return("test-bucket") - expect(ENV).to receive(:[]).with("S3_ENDPOINT").and_return(nil) + allow(ENV).to receive(:[]).with("S3_BUCKET").and_return("test-bucket") + allow(ENV).to receive(:[]).with("S3_ENDPOINT").and_return(nil) end it { is_expected.to eq(:amazon_public) } - end - context "with a S3 bucket and endpoint defined" do - before do - expect(ENV).to receive(:[]).with("S3_BUCKET").and_return("test-bucket") - expect(ENV).to receive(:[]).with("S3_ENDPOINT") - .and_return("https://s3-compatible-alternative.com") + context "with a S3 endpoint defined" do + before do + allow(ENV).to receive(:[]).with("S3_ENDPOINT") + .and_return("https://s3-compatible-alternative.com") + end + + it { is_expected.to eq(:s3_compatible_storage_public) } end - - it { is_expected.to eq(:s3_compatible_storage_public) } end end end