OpenSourcePolitics / decidim-ubx

Decidim instance for Bordeaux University
https://participation.u-bordeaux.fr/
GNU Affero General Public License v3.0
0 stars 0 forks source link

What is storage in this context? #2

Open github-actions[bot] opened 3 months ago

github-actions[bot] commented 3 months ago

file \= "#{@options[:backup_dir]}/#{@options[:backup_timestamp_file]}"

@return an array of files that have been modified

path +\= "-#{timestamp}" if need_timestamp?

https://github.com/OpenSourcePolitics/decidim-ubx/blob/c2b1f0f61691f8cf761a2287c23eb90e80a77339/app/services/decidim/backup_service.rb#L99


# frozen_string_literal: true

require "sys/filesystem"
require "fog/aws"
require "digest"
require "fileutils"

module Decidim
  class BackupService
    def initialize(options)
      @options = default_options.merge(options)
      @local_files = []
    end

    def self.run(options = {})
      new(options).execute
    end

    def execute
      create_backup_dir

      if has_enough_disk_space?
        create_backup_export

        if @options[:s3sync]
          Decidim::S3SyncService.run(
            datestamp: Time.zone.now.strftime("%Y-%m-%d"),
            local_backup_files: @local_files
          )
        end

        Decidim::S3RetentionService.run if @options[:s3retention]

        clean_local_files unless @options[:keep_local_files]

        true
      else
        Rails.logger.error "Not enough space left for backup : #{available_space} Go available for #{@options[:disk_space_limit]} Go needed"

        false
      end
    end

    def default_options
      {
        disk_space_limit: Rails.application.config.backup[:disk_space_limit],
        db_conf: Rails.configuration.database_configuration[Rails.env].deep_symbolize_keys,
        backup_dir: Rails.application.config.backup[:directory],
        backup_prefix: Rails.application.config.backup[:prefix],
        backup_timestamp_file: Rails.application.config.backup[:timestamp_file],
        timestamp_in_filename: true,
        s3sync: Rails.application.config.backup.dig(:s3sync, :enabled),
        s3retention: Rails.application.config.backup.dig(:s3retention, :enabled),
        keep_local_files: true,
        scope: :all
      }
    end

    def create_backup_dir
      backup_dir = Rails.root.join(@options[:backup_dir])
      return [Rails.root.join(backup_dir).to_s] if File.exist?(backup_dir)

      FileUtils.mkdir_p(backup_dir)
    end

    def create_backup_export
      backup_database if check_scope?(:db)
      backup_uploads if check_scope?(:uploads)
      backup_env if check_scope?(:env)
      backup_git if check_scope?(:git)
      generate_timestamp_file unless @options[:timestamp_in_filename]
    end

    def backup_database
      if can_connect_to_db?
        file_path = generate_backup_file_path("db", "dump")

        cmd = "pg_dump -Fc"
        cmd += " -h '#{@options[:db_conf][:host]}'" if @options[:db_conf][:host].present?
        cmd += " -p '#{@options[:db_conf][:port]}'" if @options[:db_conf][:port].present?
        cmd += " -U '#{@options[:db_conf][:username]}'" if @options[:db_conf][:username].present?
        cmd = "PGPASSWORD=#{@options[:db_conf][:password]} #{cmd}" if @options[:db_conf][:password].present?
        cmd += " -d '#{@options[:db_conf][:database]}'" if @options[:db_conf][:database].present?
        cmd += " -f '#{file_path}'"

        Rails.logger.info("Started backup_database with #{cmd}")

        execute_backup_command(file_path, cmd)
      else
        Rails.logger.error "Cannot connect to DB with configuration"
        Rails.logger.error @options[:db_conf].except(:password)
        Rails.logger.error "DB password was #{@options[:db_conf][:password].present? ? "present" : "missing"}"
        # do not exit here because we can still try to do the other backups
        false
      end
    end

    def backup_uploads
      # TODO: What is storage in this context?
      if file_exists?("storage")
        file = generate_backup_file_path("storage", "tar.bz2")

        cmd = "tar -jcf #{file} storage"

        execute_backup_command(file, cmd)
      else
        Rails.logger.warn "uploads directory not found"
        # do not exit here because we can still try to do the other backups
        false
      end
    end

    def backup_env
      if file_exists?(".env")
        file = generate_backup_file_path("env", "tar.bz2")

        cmd = "tar -jcf #{file} .env"

        execute_backup_command(file, cmd)
      else
        Rails.logger.warn ".env file not found"
        # do not exit here because we can still try to do the other backups
        false
      end
    end

    def backup_git
      if file_exists?(".git")
        file = generate_backup_file_path("git", "tar.bz2")

        cmd = "tar -jcf #{file} #{git_file_list.join(" ")}"

        execute_backup_command(file, cmd)
      else
        Rails.logger.warn ".git directory not found"
        # do not exit here because we can still try to do the other backups
        false
      end
    end

    def generate_timestamp_file
      Rails.logger.info "Backup time stamp is #{timestamp}"
      file = "#{@options[:backup_dir]}/#{@options[:backup_timestamp_file]}"
      File.write(file, timestamp)
      @local_files << file
    end

    private

    # We are wrapping this in a method to be able to stub it in tests
    def git_file_list
      %w(.git/HEAD .git/ORIG_HEAD).concat(git_delta).select { |file| File.exist? file }
    end

    # We are wrapping this in a method to be able to stub it in tests
    # @return an array of files that have been modified
    def git_delta
      `git status -s`.split("\n").map(&:split).map(&:last)
    end

    # We are wrapping this in a method to be able to stub it in tests
    def file_exists?(file)
      File.exist?(file)
    end

    def can_connect_to_db?
      ActiveRecord::Base.establish_connection
      ActiveRecord::Base.connection
      ActiveRecord::Base.connected?
    end

    def available_space
      return @available_space if @available_space

      disk_stats = Sys::Filesystem.stat(Rails.root.to_s)
      @available_space = disk_stats.block_size * disk_stats.blocks_available / 1024 / 1024 / 1024
    end

    def has_enough_disk_space?
      available_space > @options[:disk_space_limit]
    end

    def has_backup_directory?
      FileUtils.mkdir_p(@options[:backup_dir]) unless File.directory?(@options[:backup_dir])
      File.writable?(@options[:backup_dir])
    end

    def need_timestamp?
      @options[:timestamp_in_filename]
    end

    def timestamp
      @timestamp ||= Time.zone.now.strftime("%Y-%m-%d-%H%M%S")
    end

    def generate_backup_file_path(name, ext)
      path = @options[:backup_dir]
      path += "/#{@options[:backup_prefix]}-#{name}"
      path += "-#{timestamp}" if need_timestamp?

      "#{path}.#{ext}"
    end

    def execute_backup_command(file_path, cmd)
      Rails.logger.info "Command : #{cmd}"
      result = system(cmd)
      @local_files << file_path if result
      Rails.logger.info "Created file #{file_path} with exit code #{result}"

      result
    end

    def clean_local_files
      FileUtils.rm(@local_files)
    end

    def check_scope?(scope)
      return true if @options[:scope] == :all

      @options[:scope] == scope
    end
  end
end
github-actions[bot] commented 4 weeks ago

Stale issue message